diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/codex-thread-ids.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/codex-thread-ids.md new file mode 100644 index 0000000000..32a9d841d2 --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/codex-thread-ids.md @@ -0,0 +1,19 @@ +# rfc-command-kit — Codex implementation thread + +- **Thread / session id:** `019fe242-2c45-7e03-a428-eebfb968eda0` +- **Rollout:** `/home/codex/.codex/sessions/2026/08/08/rollout-2026-08-08T18-43-35-019fe242-2c45-7e03-a428-eebfb968eda0.jsonl` +- **Worktree:** `/home/codex/repos/ns-rfc-command-kit` +- **Branch:** `docs/rfc-command-composition-kit` @ `fac9e3390` (NO upstream by design). +- **Push rule:** explicit refspec only — `git push origin HEAD:refs/heads/docs/rfc-command-composition-kit`. +- **Requested route:** provider=openai · model=gpt-5.6-sol · effort=xhigh +- **Observed route:** provider=openai · model=gpt-5.6-sol · effort=xhigh +- **Route verdict:** matched +- **Runtime:** approval=never · sandbox=dangerFullAccess +- **Brief (staged):** `/home/codex/rfc-command-kit-brief.md` + +## Steering (same thread — never a second send-message-v2 at this worktree) +```bash +codex exec resume 019fe242-2c45-7e03-a428-eebfb968eda0 -- "" +``` + +_Written by `.llm/tools/agentic/codex/launch-codex-slice.ts`._ diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/context-pack.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/context-pack.md new file mode 100644 index 0000000000..ccb25ba0da --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/context-pack.md @@ -0,0 +1,108 @@ +# Context Pack: production command composition kit RFC + +## Run Metadata + +| Field | Value | +| -------------- | --------------------------------------------------------------- | +| Run ID | `docs-rfc-command-composition-kit--rfc` | +| Branch | `docs/rfc-command-composition-kit` | +| Current phase | `plan-eval-ready-cycle-2` | +| Archetype | Docs delivery describing A4/A2/A3/A5/A6 implementation surfaces | +| Scope overlays | `SCOPE-docs`, `SCOPE-service` | + +## Current State + +The run remains on the requested branch/base in the sole daemon-attached Codex thread. Draft PR +#1389 is open, draft, and ready for root-steered PLAN-EVAL cycle 2. Fable cycle 1 remains preserved +at evaluator commit `122301d25` with its authoritative `FAIL_PLAN`; no author verdict overrides it. +RFC remediation content commit `c98c08ada` resolves F-B1–F-B7 by locking provider claims/timeouts, +the service → database relay split, queue non-reuse/runtime-DDL reconciliation, identity drift, +MySQL/SQLite capability truth, generated transaction typing, and the correction batch. Proportionate +format/type/link/diff/PR gates are green. No product code or export changed. + +## Completed + +- Verified `HEAD == merge-base(HEAD, origin/main) == fac9e339042c...` and branch name. +- Preserved the staged `implement.md` and `codex-thread-ids.md` receipts. +- Read the RFC process/template, all current doctrine chapters, Archetypes 1–6, docs/service + overlays, lane/doc-audit policy, gate matrix, plan gate, and PLAN/IMPL evaluator protocols. +- Confirmed live #1361–#1364 and PR #1347 exist; #1361 is open and ratification-only. +- Opened draft PR #1389 with required labels, `status:research`, and research phase comment. +- Proved the transaction callback typing defect and typed-error defect with removed focused probes. +- Locked future package ownership, logical row ownership, JCS hashing/codec contract, adapter + refusal rules, worker/saga boundary, telemetry redaction, and implementation decomposition. +- Authored `rfcs/0000-command-composition-kit.md` from the repository template, keeping `0000` and + `Draft` through maintainer discussion. +- Tightened the S2 surface with attempt identity across receipt/audit/outbox, schema-backed outbox + codecs, deterministic scope inputs, and explicit relay retry/terminal dispositions. +- Passed docs links/accuracy, scoped format/diff, RFC structural/terminology, review-thread, and PR + check reconciliation; captured current package doc-lint findings as future implementation bars. +- Read `plan-eval.md` completely and preserved its evaluator commit/verdict. +- Re-verified queue, service/database dependency, MySQL, worker, telemetry, CLI, Prisma generated + type, and primary provider-lock surfaces for F-B1–F-B7. +- Reconciled the plan's open-decision sweep so no provider/package/migration/generator choice is + silently deferred. +- Committed and pushed the RFC remediation slice as `c98c08adabbd992a557ff7c596deae68b9c9cd62` using + the required explicit refspec. +- Passed scoped format, RFC assertion, current public-entrypoint Deno check, docs links/accuracy, + diff/lock hygiene, review-thread, and PR-check reconciliation for the remediation content. + +## Ready Handoff + +- The final evidence commit, PR body, exact `status:plan-eval` label, and non-verdict phase comment + are reconciled by the closing commands of this authoring turn. The exact resulting branch HEAD is + carried in that PR comment and the root response. + +## Next Steps + +1. Root orchestrator resumes the existing Fable session with the exact prompt in `final-handoff.md`. +2. Fable records the formal cycle-2 verdict without changing the authored RFC. +3. The later independent Qwen adversarial pass remains root-owned; this generator launches neither. + +## Key Decisions + +| Decision | Source | Notes | +| ------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------ | +| Docs-only RFC; no package/plugin changes. | brief, #1361 | Implementation is separate. | +| One-store atomicity is the maximum portable claim. | brief, doctrine A11–A13 | Cross-store and exactly-once claims refused. | +| Generator will not launch PLAN/IMPL evaluators. | brief | Root orchestrator owns Fable/Qwen sessions. | +| No weak command store; KV is refused in v1. | Deno KV/runtime research | Same-commit truth is encoded by conformance, not a boolean callers can ignore. | +| Service/database/contracts/telemetry focused subpaths. | doctrine + dependency analysis | No new package or root export growth; avoids package cycle. | +| Consumer-owned generated schemas with JCS codecs. | JSR/Prisma/provider analysis | No hidden migrations or provider-JSON fiction. | +| Provider-specific receipt claims and bounded waits. | PLAN-EVAL F-B1 + primary docs | No generic duplicate catch or callback retry. | +| Relay runtime in service over raw database persistence. | PLAN-EVAL F-B2 + doctrine | New direct service → database edge; no reciprocal dependency. | +| No direct queue reuse in v1. | PLAN-EVAL F-B3 + queue source | Share algorithm/tests; reconcile runtime DDL before future code sharing. | +| Scope/name drift executes as new. | PLAN-EVAL F-B4 + unique key | Determinism tests and replay migration/alias required. | +| Generated transaction type + honest isolation split. | PLAN-EVAL F-B5–F-B7 + probes | MySQL allow-list; SQLite default-only FCP; explicit root-method omission. | + +## Files Changed + +| Path | Status | Notes | +| -------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------- | +| `.llm/runs/docs-rfc-command-composition-kit--rfc/` | modified | Evaluator artifact preserved; remediation evidence/handoff and launcher metadata reconciled. | +| `rfcs/0000-command-composition-kit.md` | modified | Draft RFC remediation only; no framework/product implementation. | + +## Gates + +| Gate family | Current status | Evidence | +| ----------- | ------------------------------------------------------------- | --------------------------- | +| Static | PASS; format/diff/docs/RFC/PR gates green | `worklog.md` gate table | +| Fitness | PASS_DESIGN with existing doc-lint baseline findings recorded | `plan.md` and `research.md` | +| Runtime | N/A for docs PR | no product mutation | +| Consumer | current CLI/scaffold analysis PASS; generated proof deferred | S1 re-baseline | + +## Open Questions + +- FCP policy only: idempotency default, acceptance/timing of SQLite's default-only capability, + correlation telemetry plus existing-precedent cleanup, and retention defaults. Provider claims, + relay ownership, and queue reuse are resolved and not open. + +## Drift and Debt + +- Drift: runtime identity, cycle-1 undeclared boundary decisions, queue runtime DDL, identity drift, + isolation truth, and launcher metadata reconciliation are recorded in `drift.md`. +- Debt: no new or deepened architecture debt; existing package verdicts constrain the RFC. + +## Commits + +- See the draft PR's commit list + per-slice PR comments (V3 retired `commits.md`). diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/drift.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/drift.md new file mode 100644 index 0000000000..32f5b4ba64 --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/drift.md @@ -0,0 +1,137 @@ +# Drift Log: production command composition kit RFC + +Drift is append-only. Record facts that diverge from the carried proposal, plan, doctrine, or +current-state documentation. + +## 2026-08-08 — Runtime identity correlation unavailable + +- **What:** The desired-state runtime controller could not match this worktree to a persisted + runtime identity. +- **Source:** `deno task agentic:runtime status --worktree /home/codex/repos/ns-rfc-command-kit`. +- **Expected:** A read-only session snapshot associated with the pre-staged Codex thread. +- **Actual:** Exit 3, `MISSING_IDENTITY`, zero sessions, `changed: no`. +- **Severity:** minor. +- **Action:** accept for this run; preserve the checked-in thread receipt and do not repair/restart + an active daemon-attached session. +- **Evidence:** `codex-thread-ids.md`; `supervisor.md`. + +## 2026-08-08 — Owner-controlled evaluator routing + +- **What:** Formal review/evaluation is reserved for existing external sessions steered by the root + orchestrator. +- **Source:** Owner implementation brief. +- **Expected:** Harness default would route a selected formal PLAN/IMPL evaluation via its canonical + separate-session lanes. +- **Actual:** This generator must prepare inputs, stop at `status:plan-eval`, and must not trigger + PLAN-EVAL/IMPL-EVAL itself; root will steer Fable cross-RFC review and a final Qwen adversarial + pass. +- **Severity:** significant. +- **Action:** accept as explicit owner override; do not self-certify and do not launch a rival + session. +- **Evidence:** `implement.md`; `supervisor.md`. + +## 2026-08-08 — Proposal outbox inventory is stale + +- **What:** The proposal says the repository has no outbox primitive. +- **Source:** Carried RFC-B §1.1 and §3.5. +- **Expected:** No `outbox` match in packages/plugins. +- **Actual:** `packages/plugin-sagas-core/src/ports/saga-outbox-port.ts` publicly defines a reserved + T2 `SagaOutboxPort`; it has no adapter/caller and cannot join a command transaction. +- **Severity:** significant. +- **Action:** cite it as narrow prior art, do not reuse it as atomicity proof, and define a + command-specific transaction-bound row/store contract. +- **Evidence:** `research.md` R9 and proposal-adjudication table. + +## 2026-08-08 — Proposal overstates current adapter/helper portability + +- **What:** The proposal treats `withTransaction` as reusable verbatim, SQLite as a current adapter, + `maxIsolation` as a truthful scalar, and #1293 as a MySQL command blocker. +- **Source:** Carried RFC-B §§3.1, 4, and 6. +- **Expected:** A sound transaction-client callback and current exported adapters for the full + matrix. +- **Actual:** The helper asserts the full root client; focused check compiled invalid root-only + calls; no SQLite adapter subpath exists; isolation is a provider set/configuration; #1293 targets + another package's class/error hook while `@netscript/database/adapters/mysql` already exists. +- **Severity:** significant. +- **Action:** require a true `TTx`, exact supported-level sets, separate feasibility/current-support + columns, treat SQLite as unproven, and classify #1293 as adjacent. +- **Evidence:** `research.md` R2/R3/R12/R13; focused probe transcript in `worklog.md`. + +## 2026-08-08 — Proposal concurrency and telemetry APIs are unsafe as written + +- **What:** The proposal offers `expectVersion(current)` and emits raw scope/key/version attributes. +- **Source:** Carried RFC-B §§3.3 and 3.6. +- **Expected:** A race-free portable version check and safe command telemetry. +- **Actual:** Read-then-compare does not make the mutation conditional; raw identifiers and versions + are high-cardinality and may contain sensitive application data. +- **Severity:** significant. +- **Action:** make CAS repository-specific with a zero-row typed conflict, and restrict default + telemetry to stable names, enums, booleans, and counts. +- **Evidence:** Prisma OCC docs; OTel attribute requirement/convention guidance; `research.md`. + +## 2026-08-08 — PLAN-EVAL cycle 1 exposed three undeclared boundary decisions + +- **What:** The first RFC draft left receipt duplicate recovery/timeout, relay type ownership, and + queue reuse-versus-rejection to implementation inference. +- **Source:** Authoritative `plan-eval.md` findings F-B1–F-B3; verdict `FAIL_PLAN`. +- **Expected:** The plan's open-decision sweep should have resolved every choice that could rework + provider or package boundaries. +- **Actual:** The core laws were sound, but these three decisions were absent from both locked plan + and FCP questions. +- **Severity:** critical. +- **Action:** lock provider-specific claim algorithms, split database raw relay persistence from + service decoded runtime/sinks, reject queue-package reuse in v1, and propose queue runtime-DDL + reconciliation before future code sharing. +- **Evidence:** RFC normative claim/relay/queue sections; `research.md` R21–R25; `plan.md` L14–L16. + +## 2026-08-08 — Receipt identity drift was described more strongly than the key can enforce + +- **What:** The first draft said scope must be stable but did not state that a changed scope or + renamed command creates a different unique-key namespace. +- **Source:** `plan-eval.md` F-B4. +- **Expected:** Same raw idempotency key would appear protected by the key-reuse law. +- **Actual:** `(scope, commandName, keyHash)` changes, so no conflicting receipt exists and the + handler honestly executes as new. +- **Severity:** significant. +- **Action:** add deterministic identity fixtures, execute-as-new negative conformance, and a + breaking replay migration/alias rule for scope/name changes. +- **Evidence:** RFC canonical identity, semantic law 13, conformance items 5–6. + +## 2026-08-08 — Isolation vocabulary needs provider allow-lists and default-only truth + +- **What:** Existing planning treated MySQL/SQLite capability as a supported-level set only. +- **Source:** `plan-eval.md` F-B5/F-B7f and current adapter source. +- **Expected:** MySQL omits unsupported `Snapshot`; SQLite could advertise its serializable engine + level directly. +- **Actual:** The lower MySQL adapter union includes/interpolates `SNAPSHOT`; the evaluator found + SQLite's Prisma selection surface unavailable even though its engine default is serializable. +- **Severity:** significant. +- **Action:** replace the capability with `selectableIsolationLevels` plus `defaultIsolation`, make + MySQL four-level allow-list/removal a Stage-6 gate, and put SQLite's default-only acceptance in + FCP Q2. +- **Evidence:** RFC capability matrix; `research.md` R27/R29; `plan.md` L18/L20. + +## 2026-08-08 — Queue runtime DDL is baseline drift, not command-kit precedent + +- **What:** `@netscript/queue` already creates PostgreSQL queue/DLQ schema from runtime adapter + initialization. +- **Source:** `packages/queue/adapters/postgres.adapter.ts` and dead-letter adapter; F-B3. +- **Expected:** The RFC's no-hidden-migration law might read as a repository-wide current invariant. +- **Actual:** It is a normative rule for new command-kit/relay paths; the sibling queue behavior is + existing drift requiring separate reconciliation before reuse. +- **Severity:** significant. +- **Action:** scope the immediate law to command-kit paths and propose a migration-free queue slice; + create no issue in this RFC run. +- **Evidence:** RFC queue-decision/rejected-alternative/board sections. + +## 2026-08-08 — Launcher metadata formatting arrived with evaluator handoff + +- **What:** `.llm/runs/docs-rfc-command-composition-kit--rfc/codex-thread-ids.md` had a pre-existing + launcher metadata formatting edit when remediation began. +- **Source:** worktree at evaluator HEAD `122301d25`. +- **Expected:** Evaluator commit plus clean authored state. +- **Actual:** The metadata lines were reflowed without changing thread/session identities. +- **Severity:** minor. +- **Action:** preserve and include the formatting reconciliation with run-artifact updates; do not + discard or regenerate launcher identity state. +- **Evidence:** final raw diff and commit slice S4. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/final-handoff.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/final-handoff.md new file mode 100644 index 0000000000..eb2203b353 --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/final-handoff.md @@ -0,0 +1,148 @@ +# Final Handoff: production command composition kit RFC + +## Delivery identity + +| Field | Value | +| ------------------------------------ | ---------------------------------------------------------------------------- | +| Draft PR | [rickylabs/netscript#1389](https://github.com/rickylabs/netscript/pull/1389) | +| Branch | docs/rfc-command-composition-kit | +| Pinned base | origin/main@fac9e339042c5394bf882311657d8981d353a1c3 | +| Cycle-1 evaluator commit | 122301d25f4055bfebffefeb3aec2b23c707cfca | +| Cycle-2 RFC remediation content HEAD | c98c08adabbd992a557ff7c596deae68b9c9cd62 | +| Run directory | .llm/runs/docs-rfc-command-composition-kit--rfc/ | +| RFC | rfcs/0000-command-composition-kit.md (0000, Draft) | +| Review state | Cycle 2 ready; no generator verdict | + +The immutable final branch HEAD is the commit containing this handoff and its validation evidence. A +Git commit cannot embed its own not-yet-computed object ID. The exact post-push branch HEAD is +therefore recorded in the final non-verdict PLAN-EVAL PR comment and the root response; +c98c08adabbd992a557ff7c596deae68b9c9cd62 is the exact RFC remediation content under review. + +## Locked decisions + +1. One conformant store transaction is the maximum atomicity claim. Business writes, the replay + receipt, audit rows, and outbox rows use the same generated transaction client and commit or roll + back together. +2. Receipt identity is (scope, commandName, SHA-256(idempotencyKey)); request identity is SHA-256 + over RFC 8785 JCS bytes for the exact versioned semantic request. Same scope/name/key plus a + changed request or definition version is mismatch; a changed scope or command name is a new + namespace and executes as new. +3. PostgreSQL claims with INSERT ... ON CONFLICT DO NOTHING RETURNING plus an indexed select and a + transaction-local lock_timeout. MySQL claims with a savepoint/plain INSERT, recovers only 1062, + aborts on 1205/1213, and restores or discards the session after changing + innodb_lock_wait_timeout. SQL Server claims with the generated named unique index and + UPDLOCK/HOLDLOCK, bounded by SET LOCK_TIMEOUT. A busy result is callback-terminal and every + timeout path rolls back. +4. The service package owns command semantics and decoded relay delivery/sinks/supervision. The + database package owns the true transaction, raw receipt/audit/outbox rows, raw relay + lease/token/release types, and provider adapters. The only new edge is service → database; + database imports no service type. +5. V1 does not wrap or depend on @netscript/queue. It reuses the verified lease algorithm and + conformance pattern, not queue's delete-on-ack/DLQ/public-loop contract or runtime schema + creation. A separate queue runtime-DDL reconciliation is proposed before future code sharing. +6. The explicit netscript db command-store add generator owns consumer Prisma models, reviewable + migrations, the bridge, and CommandTransactionClient derived from the generated + Prisma.TransactionClient with root/lifecycle methods explicitly omitted. Hidden migrations and a + framework fake transaction client are rejected. +7. Capabilities distinguish selectableIsolationLevels from defaultIsolation. MySQL is blocked until + SNAPSHOT is removed/rejected and all SQL tokens are allow-listed. A future SQLite shape selects + no explicit levels and reports a Serializable default; explicit Serializable remains refused. +8. Optimistic concurrency is an application repository conditional mutation. Zero matched rows is a + typed conflict. The executor performs no read-then-compare shortcut and never retries the + callback automatically. +9. Remote I/O is outside the transaction. A committed outbox message retains its stable ID across + at-least-once relay attempts; publish-then-crash may duplicate delivery. Workers, sagas, streams, + and receivers own downstream deduplication and compensation. +10. Command-visible contract errors use the literal-preserving four-generic ContractBuilder spelling + owned by #1350. Contract and service surfaces are A4; database/telemetry adapters A2; relay + runtime discipline A3; thin integrations A5; generators A6. +11. Default command telemetry is bounded and redacted. Existing messaging correlation and saga + idempotency attributes are acknowledged as an operator-experience asymmetry, not precedent for + raw command identifiers. +12. The RFC remains documentation-only, Draft, and numbered 0000. Numbering, acceptance, issue + creation/closure, milestone changes, merge, and later implementation remain maintainer/root + work. + +## Unresolved FCP questions + +1. Whether idempotency is required for every v1 command or may be explicitly optional for controlled + internal callers. Recommendation: required by default; explicit opt-out only. +2. Whether the future SQLite default-only capability is accepted and whether it is required for the + first release. Recommendation: accept the honest shape, release later, and make no support claim + until real contention/crash/lease tests pass. +3. Whether validated correlation may be an opt-in command telemetry attribute, and whether the + existing messaging/saga identifier attributes should be separately deprecated or redacted. + Recommendation: durable-row/policy-controlled logs only for commands by default, plus a separate + vocabulary cleanup. +4. Receipt retry-window and published-outbox cleanup defaults. Recommendation: require explicit + deployment values until operational evidence exists; audit retention remains application policy. + +Provider claim algorithms, relay ownership, queue reuse, identity-drift behavior, generated +transaction typing, and MySQL isolation truth are resolved design inputs, not FCP questions. + +## Validation evidence + +| Evidence | Result | +| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| Scoped deno fmt --check over authored RFC/remediation/handoff Markdown | PASS; generated launcher metadata kept in its launcher-emitted form and audited separately | +| git diff --check and origin/main...HEAD changed-file/lock audit | PASS; only the RFC and mandatory run artifacts; no package, plugin, schema, generated product, or deno.lock change | +| rtk proxy deno task docs:links | PASS; 102 docs, 0 broken links, 0 broken anchors, 0 orphans | +| rtk proxy deno task docs:accuracy | PASS; repository accuracy/discoverability assertions | +| Focused RFC frontmatter/F-B1–F-B7/forbidden-vocabulary assertion | PASS; 17 required claims, 3 forbidden-pattern classes | +| rtk proxy deno check --no-lock --unstable-kv on database/service/contracts/telemetry public entrypoints | PASS; existing peer/build-script warnings only | +| Focused Prisma 7.8 generated-client probe | PASS; Prisma.TransactionClient exists and current deny-list retains nested $transaction; probe removed | +| agentic:review-threads on #1389 | PASS; 0 threads, 0 unanswered | +| agentic:pr-checks on #1389 at c98c08ada | PASS; 15 reconciled checks, 0 current failures; docs-only lanes skipped | +| Product/CLI runtime E2E | N/A by scope; no package, generator, schema, scaffold, runtime, export, or lock changed | + +No evaluator verdict is claimed. Existing contracts/telemetry doc-lint findings remain recorded +future implementation bars, not new findings or waivers in this docs PR. + +## Board reconciliation proposal + +No issue, milestone, or board item was created, closed, or mutated by this generator. + +- Keep #1361 as the 0.0.6 RFC ratification record; this PR references it without a closing keyword. +- Keep #1363 as the 0.0.8 implementation umbrella. +- Keep #1350 as the 0.0.7 literal-error prerequisite. +- Let #1362 own generated service layering; command-handler generation depends on that shape. +- Let #1364 own the outbound HTTP/webhook recipe and consume stable command-outbox IDs. +- Keep #1293 adjacent; it does not repair the distinct MySQL SNAPSHOT/allow-list defect. +- Propose, for maintainer filing after acceptance, a PR-sized queue runtime-DDL reconciliation child + before command-relay/queue code sharing. This RFC run does not create it. +- Keep optional SQLite conformance as the FCP-timed child and cross-reference #1278 without + importing its broader type-soundness scope. +- Do not assign the RFC number, close an umbrella, or move any milestone from this PR. + +## Exact Fable cycle-2 re-evaluation handoff + +The root orchestrator should resume the existing Claude Fable 5 evaluator session and send exactly: + +> Run PLAN-EVAL cycle 2 for draft PR rickylabs/netscript#1389. Stay in the existing Fable 5 +> evaluator session; do not launch or delegate to another evaluator. Read +> .llm/runs/docs-rfc-command-composition-kit--rfc/plan-eval.md completely and treat every cycle-1 +> finding F-B1 through F-B7 and every Required for PASS item as the acceptance checklist. Review the +> current branch HEAD named in the latest non-verdict [PHASE: PLAN-EVAL] PR comment; the exact RFC +> remediation content commit is c98c08adabbd992a557ff7c596deae68b9c9cd62, based on evaluator commit +> 122301d25f4055bfebffefeb3aec2b23c707cfca. Read the RFC and +> supervisor/research/plan/worklog/context-pack/drift/final-handoff artifacts, and compare the +> carried proposal, current origin/main@fac9e339042c, relevant source, and primary provider +> documentation. Verify specifically: (1) PostgreSQL/MySQL/SQL Server receipt claims, bounded lock +> waits, rollback/no-poison and session restoration; (2) raw database relay ownership versus decoded +> service runtime ownership and the new one-way service → database dependency; (3) the explicit +> rejection of direct queue reuse and the hidden-runtime-migration reconciliation; (4) deterministic +> scope/fingerprint behavior plus execute-as-new changed-scope/renamed-command conformance and +> migration law; (5) MySQL's four-level isolation gate distinct from #1293; (6) generator-owned +> CommandTransactionClient typing; and (7) the complete archetype, ContractBuilder, vocabulary, +> milestone, worker, SQLite, telemetry, and CLI correction batch. Preserve the one-store +> transaction, receipt, relay, capability-honesty, and refusal laws. Record the formal cycle-2 +> verdict and evidence in plan-eval.md and the structured PLAN-EVAL PR comment according to the +> evaluator protocol. Do not edit the RFC or other author artifacts, number the RFC, approve on +> behalf of the generator, merge, close/create issues, mutate milestones, or trigger IMPL-EVAL/Qwen. + +## Generator stop condition + +After the handoff evidence commit is pushed, the PR body is reconciled, its sole lifecycle label is +status:plan-eval, and the final non-verdict PR comment carries the exact branch HEAD, generator work +stops. Fable cycle 2 and the later Qwen adversarial evaluation remain external and pending; the PR +stays draft and unmerged. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/implement.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/implement.md new file mode 100644 index 0000000000..ddcbace37c --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/implement.md @@ -0,0 +1,81 @@ +use harness + +# RFC-B implementation brief — production command composition kit + +You are the sole generator for a production-grade NetScript RFC. Work autonomously in +`/home/codex/repos/ns-rfc-command-kit` on branch `docs/rfc-command-composition-kit`, based on +`origin/main` at `fac9e339042c`. You are Codex GPT-5.6 Sol at xhigh reasoning, full-access/bypass +permissions. Do not create a rival session or delegate the core RFC authorship. + +## SKILL + +- `netscript-harness` — activate the full harness and RFC/docs design run; maintain every mandatory + run artifact. +- `netscript-doctrine` — evaluate the cross-package command seam, runtime boundaries, extension + axes, layering, anti-patterns, and debt. +- `netscript-pr` — open and maintain a draft RFC PR to `main`, with correct labels, phase comments, + and no premature merge/closure. +- `netscript-tools` — use repo-native validation and compact evidence tooling. +- `netscript-deno-toolchain` — inspect current database/service/contracts/telemetry APIs through + native Deno tools. +- `jsr-audit` — assess public export, subpath, slow-type, and publish-surface consequences. +- `netscript-cli` — analyze generated service/schema/worker surfaces and the proposed explicit + generators without changing them. +- `rtk` — reduce read-heavy command output without changing semantics. +- `codex-wsl-remote` — preserve the daemon-attached, mobile-visible session and explicit-refspec + push safety. + +## Objective + +Turn Claude Fable 5's RFC-B proposal into the actual lightweight RFC required by `rfcs/README.md`. +The input is a starting proposal, not ground truth: + +- proposal: + `/home/codex/repos/netscript-fable5-remediation-plan/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/rfcs/RFC-B-command-composition-kit.md` +- tracking issue: `#1361` +- related filed issues: `#1362`–`#1364`, plus live dependencies named by the proposal +- source roadmap PR: `#1347` + +Re-baseline every claim against current `origin/main`, current docs, doctrine, exports, database +adapters, services SDK, contracts, telemetry, workers, sagas, tests, and live issue/PR state. +Determine the smallest honest public composition seam for one-store transactional commands. Prove +where atomicity ends, how optimistic concurrency and idempotent replay compose, how audit/outbox +records share the commit, and how the relay boundary joins workers/sagas without exactly-once +fiction. Resolve or explicitly frame store capabilities, receipt ownership/schema generation, +canonical request hashing, actor/correlation propagation, typed errors, isolation, failure +injection, telemetry cardinality/redaction, adapter portability, and the precise refusal boundary. +No billing-specific vocabulary and no speculative distributed transaction framework. + +## Required output + +1. Activate a new harness run at `.llm/runs/docs-rfc-command-composition-kit--rfc/` with + `supervisor.md`, `research.md`, `plan.md`, `worklog.md` (including `## Design`), + `context-pack.md`, `drift.md`, and a final handoff. Select `SCOPE-docs` plus every underlying + archetype required by the described service/database/contracts/telemetry/runtime surfaces; read + the RFC process, doctrine, gate matrix, plan gate, and evaluator protocol completely. +2. Produce `rfcs/0000-command-composition-kit.md` from `rfcs/0000-template.md`, keeping `0000` until + maintainer acceptance. It must include exact contracts and examples; semantic laws; adapter + capability matrix; transaction/idempotency/audit/outbox invariants; worker/saga boundary; + security/privacy; typed failure model; OTEL vocabulary; injected-failure conformance plan; + compatibility/migration; rejected alternatives; unresolved FCP questions; staged implementation; + docs/scaffold impacts; and issue/epic decomposition. +3. Challenge Claude's proposal against actual code and primary database/runtime documentation. Use + focused non-product probes when needed to validate adapter or type claims. Do not implement + product/framework code in this RFC PR. +4. Make an initial harness/bootstrap commit, push only with an explicit refspec, and open a draft PR + against `main` in the same session. Reference `#1361` without closing it; carry `rfc`, + `type:docs`, `area:service`, `area:database`, `priority:p1`, `ci:skip-e2e`, `ci:skip-scaffold`, + and exactly one lifecycle status. Move to `status:plan-eval` only when ready for Fable review. + Never assign an RFC number, merge, create/close issues, or mutate milestones. +5. Commit coherent slices with run-artifact updates and PR phase comments. Run applicable docs/RFC + gates and record exact evidence. Do not trigger PLAN-EVAL/IMPL-EVAL yourself: the root + orchestrator will steer the existing Claude Fable 5 session for cross-RFC review and then run the + final Qwen adversarial pass. +6. Finish with `final-handoff.md`: PR URL/number, HEAD SHA, run-dir, locked decisions, unresolved + questions, validation evidence, board reconciliation proposal, and exact Fable-review handoff. + Report back to the root orchestrator. + +Quality bar: the RFC must make the seam implementable without hiding store limitations, must reuse +existing NetScript primitives, and must preserve contract-first/type-safe/observable composition. It +is a narrow production command kit—not a billing framework, ORM, event-sourcing platform, or +distributed transaction claim. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/plan-eval.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/plan-eval.md new file mode 100644 index 0000000000..181385b461 --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/plan-eval.md @@ -0,0 +1,174 @@ +# PLAN-EVAL — docs-rfc-command-composition-kit--rfc (RFC-B, PR #1389) + +## CYCLE 2 — VERDICT: **PASS** (PR vocabulary: APPROVED) — 2026-08-08 + +Evaluator: same owner-designated Fable 5 · high session as cycle 1. Evaluated: RFC @ remediation +commit `c98c08ada` (branch HEAD `6c2043d91`), updated research/plan/worklog/drift/final-handoff. +Verification: section-level re-read of every finding anchor against the amended text plus the +handoff's evidence table (author's Prisma 7.8 `Prisma.TransactionClient` probe accepted with the +worktree `Omit` spelling verified at L802-811). + +### Mechanical re-evaluation F-B1…F-B7 + +| # | Cycle-2 status | Evidence (amended RFC) | +| --- | --- | --- | +| F-B1 | **RESOLVED — exceeds the asked bar** | Normative per-provider claim table (L935-949): PG `INSERT … ON CONFLICT DO NOTHING RETURNING` + indexed `SELECT`, transaction-local `set_config('lock_timeout')` with save/restore, `55P03`→busy, `40001` retryable; MySQL savepoint + **only** 1062 recovered (`INSERT IGNORE` explicitly forbidden with the warning-downgrade reason), session `innodb_lock_wait_timeout` save/restore with failed-restore-discards-connection; MSSQL `UPDLOCK, HOLDLOCK` on the named generated unique index + `SET LOCK_TIMEOUT` restore, `MERGE`/catch-2627 rejected. Busy is callback-terminal; every timeout path rolls back. Conformance item added (L1553). Primary-source citations appended | +| F-B2 | **RESOLVED** | Ownership completed: `@netscript/service/commands/relay` owns decoded delivery/sink/supervisor (L309); database owns raw rows + lease/token/release types; "the only cross-package edge is `service → database`" with the direct-dependency declaration requirement stated (L1333, L1600); stage split 5a/5b reflects it | +| F-B3 | **RESOLVED** | §"Decision: do not wrap `@netscript/queue` in v1" (L1375-1390) + rejected-alternatives entry (L1837): reuses the verified lease algorithm/test pattern, not queue's delete-on-ack/DLQ/public-loop contract or runtime schema creation; a queue runtime-DDL reconciliation child is proposed for maintainer filing before any code sharing | +| F-B4 | **RESOLVED** | Execute-as-new law stated where identity is defined (L866-867, L983) + negative conformance items 5 and 6 (same key/changed scope; same key/renamed command; changed definitionVersion = mismatch) (L1543-1545); scope determinism requirement mirrors fingerprint's | +| F-B5 | **RESOLVED** | Capability matrix: MySQL "exactly those four selectable values; current lower adapter's exported `SNAPSHOT` and unchecked SQL interpolation must be fixed before conformance" (L1018); Stage 6 cannot pass while `prisma-adapter-mysql` exports `SNAPSHOT` or interpolates unvalidated tokens (L1031-1033); #1293 correctly kept adjacent | +| F-B6 | **RESOLVED** | `CommandTransactionClient = Omit` generator-owned with engine-module re-export (L802-811); in the `db command-store add` deliverables (L1641); `withTransaction` root-client repair retained | +| F-B7 | **RESOLVED (all eight)** | (a) stage archetypes corrected to A4 for contracts/service (L1690-1696); (b) locked decision 10 pins #1350's literal-preserving `ContractBuilder` spelling; (c) vocabulary unified on `claimed` (L468); (d) frontmatter footnote: 0.0.6 = ratification-only (L24); (e) Stage 5 must correct `WorkerIdempotencyPort`'s "exactly-once-effective" doc comment (L1350); (f) SQLite future shape `selectableIsolationLevels: []` + `defaultIsolation: 'Serializable'` folded into FCP Q2 (L1020) — with the improved `selectable`/`default` capability split resolving the refusal-law wart; (g) generator renamed `db command-store add` with the `db init` distinction stated (L785-787); (h) FCP Q3 + locked decision 11 acknowledge the messaging/saga attribute asymmetry and propose the separate vocabulary cleanup | + +### FCP-question adjudication (amended set of 4) + +All four are policy-only and safe to defer; the three cycle-1 rework-forcing gaps are now resolved +design inputs, not FCP items — exactly the required repair shape. + +### Residual advisories (non-blocking) + +1. The `selectableIsolationLevels`/`defaultIsolation` split is an improvement over cycle 1's + reviewed shape — carried into the store capabilities without breaking any law; no action. +2. PR labels (`type:docs` + skips) remain honest for this branch: the diff is RFC + run artifacts + only (changed-file audit confirms no package/lock change). + +**PASS.** One-store transaction, receipt, relay, capability-honesty, and refusal laws all +preserved; the plan is implementable without guessing. Hard-stop lifted for stages 0–2 pending +owner ratification and the root-owned Qwen adversarial acceptance pass. Accepted content SHA of +record: **`c98c08adabbd992a557ff7c596deae68b9c9cd62`** (branch HEAD +`6c2043d915ea2afbe7020bbb88d90ea4d0ea16a5`). + +--- + +# Cycle 1 record (2026-08-08, superseded by the PASS above) + +| Field | Value | +| --- | --- | +| Verdict | **FAIL_PLAN** (PR-comment vocabulary: CHANGES_REQUESTED) — cycle 1 of 2 | +| Evaluator | Claude Fable 5 · high — owner-designated cross-family PLAN-EVAL authority (in-turn owner directive, 2026-08-08), separate session/family from the generator (Codex, per run supervisor.md) | +| Route override | Lane-policy Minimax route superseded by explicit owner direction (this Fable session evaluates; OpenRouter/OpenHands/Qwen prohibited here; root owns the later Qwen pass) | +| Evaluated | RFC `rfcs/0000-command-composition-kit.md` @ `ad16c42f8` (branch HEAD `62304176f`), run artifacts, PR #1389, live board (#1361–#1364, #1350, #1293, #1278, #1362), worktree source at baseline `fac9e339042c` | +| Delegations | Workflow `wf_b3416478-edf` (script committed pre-execution on the seed-run branch): `dd:rfc-b-semantics`, Opus 5 · xhigh, read-only/return-only; evidence reviewed and adopted by this evaluator | + +## Plan-gate checklist walk + +| Box | Status | Evidence | +| --- | --- | --- | +| Research present and current | ✓ | 20 baseline findings (R1–R20), several execution-verified (probe `deno check` runs, `deno doc` on the locked oRPC contract builder); carried proposal adjudicated row-by-row with 12 rejections | +| Decisions locked | ✓ | L0–L13 with rationale; refusal boundary and not-open list are exemplary honesty | +| Open-decision sweep | **✗** | The four FCP questions are individually safe (adjudicated below), but the sweep found **three undeclared decisions that force rework if deferred** (Findings 1–3) — automatic unchecked box | +| Commit slices | ✓ | Docs-run slices evidenced; implementation staged 0–9 with owning archetypes and gates | +| Risk register | ✓ | Present with mitigations | +| Gate set selected | ✓/− | Conformance suite (17-item matrix + fault seams) is the strongest artifact in either RFC; two gaps folded into Findings 4–5 | +| Deferred scope explicit | ✓ | Non-scope + not-open lists precise | +| jsr-audit | ✓ | Per-subpath publish-consequence table with gates; `@standard-schema/spec` direct-dependency requirement verified correct | + +## Verdict: FAIL_PLAN — findings (severity-ranked) + +**The core promise is verified honest and internally consistent**: one store transaction, explicit +`TTx` (no ambient state — grep-verified against the repo's existing ALS usage), DB-constraint +idempotency, at-least-once relay with stable identity, no hidden migrations, no exactly-once. The +failures are three boundary decisions taken silently plus citable corrections. + +### F-B1 (critical) — `claimReceipt`'s unique-violation-inside-transaction algorithm is unspecified + +The design correctly rejects check-then-act, but never says how a losing insert becomes +`{kind:'replay'}` **without aborting the enclosing transaction**. On PostgreSQL a unique +violation poisons the transaction (25P02) — a savepoint or `INSERT … ON CONFLICT DO NOTHING +RETURNING` + follow-up `SELECT` is mandatory; SQL Server (error 2627, statement-level) and MySQL +(`INSERT IGNORE`) diverge. The RFC grants adapter-algorithm freedom only for the **relay** claim, +and `callbackAttempts: 'one'` + "call `store.transaction()` once" forbids the retry-outside +strategy. The bounded `busy`/`retryAfterMs` path likewise needs provider lock-timeout mechanics +(`SET LOCAL lock_timeout` / `innodb_lock_wait_timeout` / `SET LOCK_TIMEOUT`) — unmentioned. +**Repair:** add a normative "receipt claim algorithm" subsection (per-provider strategies + +lock-timeout mechanics + conformance items proving no transaction poisoning), or add it as an +explicit FCP question with the provider strategies enumerated. Deferring silently reworks Stage 3. + +### F-B2 (critical) — Relay-port package ownership is a hole that can invert the dependency arrow + +`CommandOutboxRelayStore`/`CommandOutboxSink`/`CommandOutboxDelivery` have **no row** in the +ownership table; `CommandOutboxDelivery.payload: CommandJson` + `trace?: CommandTraceContext` +reference **service-owned** types; Stage 5 says archetype A3/A5 while the child-issue row says +`area:service, area:database`. Placing the relay ports in `database/commands` forces +database→service — the cycle the RFC's own L3 forbids. **Repair:** add the ownership rows and +decide the package now (e.g. relay store contract in `@netscript/database/commands` with the +delivery value types moved to a shared neutral location, or relay wholly in +`@netscript/service/commands` with database providing only the store adapter). Also state +explicitly that `service → database` is a **new** package edge and `packages/service/deno.json` +must declare it (verified absent today). + +### F-B3 (major) — Reuse-vs-reinvent against `@netscript/queue` is undecided and unrecorded + +`packages/queue/adapters/postgres.adapter.ts` already implements Postgres claim +(`FOR UPDATE SKIP LOCKED`, :373-396), visibility-timeout lease, redelivery, max-attempts, and a +dead-letter store — the exact mechanics the relay re-derives. The rejected-alternatives list has +no entry for it (A7 wrap-don't-reinvent; AGENTS.md rule 3). The *insert* side genuinely cannot +reuse the queue (it must join the business transaction); the *claim/lease/DLQ* side has no such +constraint. **Repair:** add the rejected-alternative entry with a real decision — reuse the +queue's claim/lease/DLQ mechanics behind the relay store port, or record precisely why not. +Related (same section): the queue's `ensureSchema()` issues runtime `CREATE TABLE IF NOT EXISTS` +(:319-369; dead-letter store :160-172) — the very pattern this RFC's no-hidden-migrations rule +forbids. Scope the rule to the command kit explicitly or call for the reconciliation issue; +otherwise the RFC ships doctrine a sibling package already violates. + +### F-B4 (major) — Scope instability silently defeats idempotency; no negative test + +`scope({input, actor})` is application code inside the receipt unique key. A scope that varies +across retries of the same intent lands the same idempotency key in a **different** receipt row — +the handler runs again and no law fires (key-reuse law is keyed on the full receipt key). Prose +says "must not vary" with no enforcement; conformance item 4 covers only same-key/different-hash. +**Repair:** add negative conformance items — same key + different scope, and same key + renamed +command — asserting the outcome the RFC intends (execute-as-new is the honest answer; say it), +plus a determinism requirement on `scope()` mirroring `fingerprint()`'s. + +### F-B5 (medium) — MySQL `SNAPSHOT` is an undeclared prerequisite of "conforming v1 target" + +`packages/prisma-adapter-mysql/src/adapter.ts:476-481` declares `'SNAPSHOT'` in its isolation +union and `:370-372` interpolates the level into SQL with no allow-list at the execution point — +an isolation MySQL cannot honor. Under the RFC's own isolation-refusal law the MySQL command +store must publish a `supportedIsolationLevels` that contradicts the type one layer down. +**Repair:** name this fix in Stage 6's exit condition (and note #1293 adjacency remains correct — +this is a different defect than #1293's surface work). + +### F-B6 (medium) — `PrismaTransactionClient` does not exist; the generator must own it + +The flagship example imports `PrismaTransactionClient` from `@database` — zero repo hits; the +`@database` alias maps to the root-client surface, and the `db command-store init` deliverable +list does not include emitting or typing the transaction-client type. **Repair:** add the +transaction-client type emission (or its documented derivation, e.g. +`Parameters[0]` equivalent) to the generator's deliverables, and +keep the already-correct requirement to fix `withTransaction()`'s root-client assertion. + +### F-B7 (minor, batch) + +(a) Stage-1 archetype: contracts is A4 per doctrine `06-archetypes.md:375`, not A1. (b) +`commandBaseContract` needs the literal-preserving explicit annotation — state that it must reuse +#1350's spelling or it re-introduces the erasure the RFC depends on #1350 to fix. (c) Vocabulary: +`CommandExecution.idempotency: 'committed'` vs telemetry `'claimed'` — one word per state. +(d) Frontmatter `target-milestone: 0.0.6` is the *ratification* milestone while children sit in +0.0.8 and blocking #1350 in 0.0.7 — add one clarifying sentence so the frontmatter is not read as +an implementation date. (e) `WorkerIdempotencyPort`'s doc-comment claims "exactly-once-effective" +(`plugin-workers-core/src/ports/worker-idempotency-port.ts:30`) — the exact claim-window this RFC +rejects; require its correction in Stage 5's docs. (f) Fold into FCP Q2: Prisma rejects +`isolationLevel` on SQLite entirely, so a future SQLite store's `supportedIsolationLevels` would +be `[]` and the refusal law would refuse `Serializable` — the one level SQLite always provides. +(g) `db command-store init` collides with the existing `db init` noun — name the sub-noun choice +deliberately. (h) Telemetry asymmetry: existing vocabulary already emits `netscript.correlation.id` +and `netscript.idempotency.key` (`attributes/messaging.ts:15`, `attributes/saga.ts:12`) while +command spans exclude both — FCP Q3 should acknowledge the operator-experience asymmetry and the +existing-precedent cleanup question. + +## FCP-question adjudication + +Q1 (idempotency default), Q3 (correlation telemetry, with F-B7h noted), Q4 (retention): **safe to +defer** — typed both ways, policy-only. Q2 (SQLite timing): safe to defer **after** F-B7f is +folded in. The three genuinely rework-forcing questions (F-B1 claim algorithm, F-B2 relay +ownership, F-B3 queue reuse) are **missing from FCP** and must be resolved in the RFC text or +added as explicit FCP questions before FCP closes. + +## Required for PASS (cycle 2) + +Resolve F-B1–F-B3 in the RFC text (or as enumerated FCP questions with the option space fully +specified); add F-B4's negative conformance items; land F-B5/F-B6 as stage-condition amendments; +F-B7 as a batch edit. No structural redesign is requested — the transaction/receipt/relay laws, +capability honesty, and refusal boundary all survived adversarial verification. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/plan.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/plan.md new file mode 100644 index 0000000000..52a482072b --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/plan.md @@ -0,0 +1,244 @@ +# Plan: production command composition kit RFC + +## Run Metadata + +| Field | Value | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| Run ID | `docs-rfc-command-composition-kit--rfc` | +| Branch | `docs/rfc-command-composition-kit` | +| Phase | `plan-eval-ready-cycle-2` | +| Target | `rfcs/0000-command-composition-kit.md` plus harness evidence | +| Delivery profile | `SCOPE-docs` with `SCOPE-service` consumer analysis | +| Described implementation | A4 contracts/service DSL, A2 database/telemetry adapters, A3 runtime discipline, A5 thin plugin consumers, A6 CLI generators | + +## Goal + +Author an implementable, domain-neutral RFC for a production command seam whose maximum guarantee is +one transaction on one store: business writes, optional replay receipt, audit rows, and outbox rows +commit together; delivery after commit is at least once; unsupported stores and remote effects are +refused rather than disguised. + +## Scope + +- Re-baseline Claude's proposal against current exports, APIs, tests, generators, live issues, and + primary database/runtime specifications. +- Freeze exact TypeScript contracts, durable logical rows, canonical hash bytes, semantic laws, + capability/refusal behavior, typed failures, telemetry, security, and examples. +- Define provider feasibility versus current NetScript support, adapter conformance, injected + faults, migration/compatibility, rollout stages, docs/scaffold impact, and board decomposition. +- Maintain the draft RFC PR with evidence and stop at `status:plan-eval` for root-owned review. + +## Non-Scope + +- No product, package, plugin, schema, generated-workspace, CI, or release implementation. +- No domain-specific vocabulary, ORM/repository framework, event-sourcing platform, two-phase + commit, distributed transaction, cross-store rollback, or exactly-once delivery claim. +- No RFC numbering, merge, issue creation/closure, milestone mutation, evaluator launch, or second + Codex/Claude session. + +## Doctrine and archetype mapping + +| Future slice | Owner/archetype | Doctrine consequence | +| ------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| Command contract error fragment | `@netscript/contracts/commands`, A4 | Literal-preserving builder annotation from #1350; no global base-map growth. | +| Transaction command/relay stores and SQL adapters | `@netscript/database/commands`, A2 | Raw ports/adapters own provider truth; no service or generated-client type leaks. | +| Command definition/executor | `@netscript/service/commands`, A4 | Public DSL is a subpath and one explicit composition root; no hidden global runtime. | +| Relay supervisor/sinks | `@netscript/service/commands/relay`, A4 with A3 runtime discipline folded | Service consumes database persistence; owned lifecycle, leases, cancellation, at-least-once boundary. | +| Command telemetry | existing telemetry attributes subpath, A2 | Stable bounded vocabulary; privacy/high cardinality excluded by default. | +| Worker/saga/webhook consumers | A3 core runtimes plus thin A5 integrations | Existing runtime primitives compose after commit; no semantic forks in plugins. | +| Explicit schema/command/relay generators | CLI A6 | `db command-store add` emits discoverable consumer-owned files/types; no hidden migration. | + +The docs PR itself uses `SCOPE-docs`; it does not pretend a documentation file is all six +archetypes. Each implementation child must activate only its owning profile plus needed overlays. + +## Axioms in play + +| Axiom | Application | +| ------- | ------------------------------------------------------------------------------------------------- | +| A1–A3 | Contract, guide example, and reference laws precede implementation. | +| A6–A7 | RFC 8785 + Web Crypto SHA-256 replace bespoke unstable hashing. | +| A9–A11 | Package ownership, composition root, and adapter extension axes are explicit. | +| A12–A13 | Abort/timeout/failure states and relay/saga crash boundaries are named. | +| A14 | Adapter conformance, injected failures, surface gates, and consumer proof are part of acceptance. | + +## Locked decisions + +| ID | Decision | Rationale | +| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| L0 | This PR is ratification documentation only and keeps RFC number `0000`. | RFC process, brief, and #1361. | +| L1 | Atomicity ends at one transaction on one store. | Prevents distributed-transaction fiction. | +| L2 | A `CommandStorePort` is conformant only when business, receipt, audit, and outbox writes share that commit. There is no weak port mode. | Capability truth is simpler and safer than an optional guarantee callers can overlook. | +| L3 | Command semantics live at `@netscript/service/commands`; transaction persistence/adapters at `@netscript/database/commands`; opt-in errors at `@netscript/contracts/commands`; telemetry extends `@netscript/telemetry/attributes`. | One-way service → database dependency; no database/service cycle; no new package or root export growth. | +| L4 | Receipt/audit/outbox tables are consumer-owned logical schemas emitted by explicit provider-aware generators. | Published packages cannot own product migrations or user Prisma models. | +| L5 | The request fingerprint is SHA-256 of RFC-8785 JCS bytes for an exact I-JSON object containing command name/version, scope, application fingerprint, actor subject, and string expected version. | Stable replay comparison without hashing roles, claims, keys, or raw transport bytes. | +| L6 | Replay output requires an explicit codec to/from canonical JSON text. | `unknown`, Date, BigInt, class instances, and provider JSON differences cannot silently corrupt receipts. | +| L7 | Optimistic concurrency is a command repository CAS, not `expectVersion(current)` and not a generic store flag. | Only the repository knows the model/version predicate; read-then-compare races. | +| L8 | Transaction callbacks are never automatically retried by the command executor. | Hidden callback replay is unsafe for captured state and makes failure injection ambiguous; retry is caller-driven with the same idempotency key. | +| L9 | External I/O in the command callback is unsupported. Remote intent is a same-commit outbox row; the relay is at least once. | A network effect cannot roll back with SQL. | +| L10 | Deno KV is not a v1 `CommandStorePort`; SQLite is unclaimed until a NetScript adapter passes conformance. | KV is atomic-batch, not interactive; current repo has no SQLite adapter export. | +| L11 | Command errors are route-opt-in and depend on #1350 for client-visible type preservation. | Avoids widening every contract and shipping fictional type safety. | +| L12 | Default telemetry excludes raw scope, actor, idempotency key/hash, request hash, expected version, payload, and arbitrary correlation identifiers. | Cardinality, PII, and secret leakage. | +| L13 | Worker and saga idempotency remain separate consumer-side delivery concerns; an outbox message keeps a stable ID across relay attempts. | Commit-once does not imply deliver-once or effect-once. | +| L14 | Receipt-claim algorithms are provider-specific and locked: PostgreSQL conflict-safe insert, MySQL savepoint/1062 recovery, SQL Server named-index range lock; all have bounded lock waits. | A generic unique-error catch would poison or misclassify transactions and force Stage-3 rework. | +| L15 | Relay raw rows/store/release types live in database; decoded delivery/sink/supervisor types live in `service/commands/relay`; service adds the new direct database dependency. | Preserves the only dependency arrow `service → database`; database never imports service. | +| L16 | V1 does not wrap or depend on `@netscript/queue`; it reuses the proven lease algorithm/test pattern and proposes a separate runtime-DDL reconciliation before future code sharing. | Queue ack/DLQ/schema semantics differ from retained token-settled outbox rows. | +| L17 | Scope/fingerprint are pure deterministic identity functions; different scope or command name is a new receipt namespace and executes as new. | This is the honest unique-key behavior; renames/scope changes require compatibility aliases or receipt migration. | +| L18 | Capabilities separate explicitly selectable isolation levels from the omitted-option default. SQLite's future shape is selectable `[]`, default `Serializable`, pending FCP. | Avoids claiming an explicit API the evaluator found unavailable and keeps the refusal law coherent. | +| L19 | `db command-store add` emits `CommandTransactionClient` from generated `Prisma.TransactionClient` with root/lifecycle methods explicitly omitted. | The type is model-specific and cannot be a framework fake; current Prisma deny-list retains nested `$transaction`. | +| L20 | MySQL cannot pass Stage 6 until `SNAPSHOT` is removed/denied and SQL isolation tokens are allow-listed; #1293 remains separate. | Current lower adapter contradicts engine truth and interpolates the upstream union. | + +## Open-decision sweep after PLAN-EVAL cycle 1 + +| Decision | Disposition | Rework risk | +| -------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| Provider receipt claim + bounded wait | Resolved by L14 and normative provider table. | None deferred. | +| Relay port/type package ownership | Resolved by L15; service adds a direct database dependency, never the reverse. | None deferred. | +| Queue reuse vs rejection / runtime DDL | Resolved by L16; direct reuse rejected, reconciliation proposed before future sharing. | None deferred. | +| Scope/command identity drift | Resolved by L17 and explicit execute-as-new conformance/migration law. | None deferred. | +| MySQL `SNAPSHOT` contradiction | Stage-6 prerequisite locked by L20. | Cannot be waived by #1293. | +| Generated transaction-client type | Generator ownership/type derivation locked by L19. | Cannot be deferred past emitted command samples. | +| Idempotency required-by-default | Safe FCP policy question. | Both modes already typed; no package boundary change. | +| SQLite default-only acceptance/timing | Safe FCP after L18 capability shape. | Adapter release timing only; no false current support. | +| Cross-runtime correlation telemetry | Safe FCP privacy/operations policy question with existing-asymmetry cleanup named. | No command atomicity or package ownership change. | +| Receipt/outbox retention defaults | Safe FCP operations policy question. | Safety floors are fixed; only durations remain. | + +No undeclared package, algorithm, migration, or generator decision remains before implementation. + +## Exact design skeleton + +The RFC will specify, with compilable examples: + +1. `defineCommand(spec)` with stable `name`, positive `definitionVersion`, isolation requirement, + idempotency policy, input-fingerprint function, replay codec, audit policy, and async handler. +2. `createCommandExecutor({ store, telemetry, clock, ids, limits })` and + `execute(definition, envelope, { signal }) -> CommandExecution`. +3. A narrowed envelope: validated input, actor subject/scheme, bounded correlation id, validated W3C + trace context, optional high-entropy idempotency key, and optional string expected version. +4. A transaction-bound `CommandTransaction` exposing `business`, receipt claim/complete, audit + append, and outbox append; every async port operation accepts cancellation. +5. Synchronous handler recorders `audit(...)` and `publish(...)`, plus a typed `conflict()` helper; + buffers flush before receipt completion and commit. +6. Logical receipt/audit/outbox/relay-lease schemas, a per-attempt `executionId` joining side + records, and the exact unique keys/invariants. +7. An opt-in command contract error map plus the internal typed failure union and presentation map. +8. Command span/attribute constants with exact allowed values and redaction rules. +9. Schema-backed codecs for replay outputs and outbox payloads, plus an explicit retry/terminal + relay release union. +10. Exact provider receipt-claim SQL/lock-timeout/session-restoration algorithms and no-poisoning + conformance. +11. Split relay ownership with raw database rows below decoded service deliveries, plus the explicit + queue non-reuse/hidden-migration reconciliation decision. +12. Generator-owned `CommandTransactionClient` derivation and positive/negative emitted fixtures. + +## Semantic laws to encode + +- **Atomic commit:** any pre-commit failure leaves no business, receipt, audit, or outbox delta. +- **Receipt replay:** same receipt key + same request hash returns the first decoded value and emits + no new business/audit/outbox writes. +- **Key mismatch:** same receipt key + different request hash never executes and returns the typed + idempotency-reuse conflict. +- **Concurrent claim:** one claimant commits; followers replay after it commits, proceed after its + rollback, or receive a bounded retryable busy result. No incomplete receipt is normally visible. +- **CAS:** a zero-match conditional mutation produces an optimistic conflict and rolls back every + buffered side record. +- **No hidden retry:** a serialization/deadlock/busy failure aborts the attempt; executor never + invokes the handler again itself. +- **Stable delivery identity:** one committed outbox row retains its ID across lease expiry and + relay redelivery; publish-then-crash may duplicate delivery. +- **Boundary refusal:** absent same-commit support, unsupported isolation, cross-store state, or + external effects fail composition/execution before an overbroad guarantee can be claimed. +- **Identity drift:** same raw key under a different scope/name executes as new; changed version in + the same receipt namespace mismatches; deterministic identity fixtures make this visible. + +## Capability and adapter plan + +The RFC matrix will have two independent columns: storage/Prisma feasibility and current NetScript +integration. It will not label a provider “supported” merely because its engine has transactions. + +| Target | Planned position | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| PostgreSQL + Prisma | Reference adapter; full semantics after conformance. | +| MySQL + Prisma | Feasible only after four-level allow-list and removal/rejection of current `SNAPSHOT`; #1293 remains separate. | +| SQL Server + Prisma | Feasible; `Snapshot` only when enabled; response/side payloads use text rather than a portable `Json` assumption. | +| SQLite + Prisma | Future default-only shape: selectable `[]`, default `Serializable`; release timing and acceptance remain FCP. | +| Deno KV | Explicit v1 refusal; existing worker idempotency stays separate. | +| SQL + KV/stream/HTTP, or two SQL stores | No atomic adapter; use outbox and saga/consumer compensation. | + +## Error and compatibility plan + +- Internal failures distinguish optimistic conflict, key reuse, in-flight/busy, unsupported + capability/isolation, codec/fingerprint failure, corrupt receipt, store failure, relay failure, + and caller cancellation. +- Contract-visible command errors are only the safe client actions: conflict, key reuse, and + retryable in-progress. Configuration/codec/store bugs map to existing service-unavailable/internal + handling without leaking payloads or driver details. +- Existing CRUD/service code is unchanged; adoption is opt-in. No breaking label is required for + this RFC. New exports require coordinated minor releases and surface-diff review. +- Public command handler/client ergonomics do not release until #1350 is fixed. Core implementation + may be developed behind non-exported code, but no half-typed public promise ships. + +## Risk register + +| Risk | Mitigation | +| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| Generic API leaks Prisma/generated types. | Package-owned structural port; generated bridge owns delegates; `deno doc` consumer review. | +| Receipt claim races, poisons the callback, or leaks session timeout. | Locked provider algorithms; duplicate/timeout/leader tests; connection restore/discard conformance. | +| Canonicalization accepts non-I-JSON values. | Explicit fingerprint/response codecs, JCS validation, negative Date/BigInt/non-finite-number tests. | +| Long transactions or hidden retries duplicate captured effects. | No remote I/O, timeout/AbortSignal, no automatic callback retry. | +| Telemetry leaks IDs or explodes cardinality. | Closed low-cardinality attributes, forbidden-field tests, optional redaction policy. | +| Relay is mistaken for exactly-once. | Stable IDs, lease/ack crash tests, explicit at-least-once wording, downstream dedupe guidance. | +| Queue reuse imports hidden runtime migrations or wrong settlement. | Reject direct dependency; share tests/algorithm only; propose explicit queue reconciliation. | +| RFC grows into a workflow platform. | One callback/one store; multi-step or compensating work hands off to sagas. | + +## Anti-patterns + +| Pattern | Resolution | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| AP-3 god port | Separate command store, relay store, sink, telemetry; no backend switch inside executor. | +| AP-8/AP-9 framework machinery | No DI container, ORM, distributed coordinator, or speculative package. | +| AP-11/AP-12 hidden globals | Explicit executor composition root with injected store/clock/IDs/telemetry. | +| AP-14 upstream leakage | No Prisma/oRPC/OTel types in package-owned public contracts except existing `IsolationLevel` ownership in database. | +| AP-19 implicit permissions | Handler authorization precedes execution; relay/store permissions documented separately. | +| AP-24 switch dispatch | Provider adapters register at composition, not inside command execution. | + +## Implementation/board decomposition proposal + +No board mutation is authorized. The RFC will recommend retaining #1363 as the umbrella and carving +implementation into PR-sized children: typed contracts; service executor; reference PostgreSQL +store; telemetry; relay; MySQL/MSSQL adapters; optional SQLite adapter; CLI/schema/service +generator; and docs/consumer proof. #1362 owns generated service layering, #1364 owns the +outbound-webhook recipe, and #1350 is the typed-error prerequisite. #1293 remains adjacent and +should not be made a false blocker. A separate queue runtime-DDL reconciliation is proposed before +future code sharing; this RFC run creates no issue. + +## Fitness and validation gates + +| Gate | This RFC PR | Future implementation | +| ----------------------------------- | --------------------------------- | ---------------------------------------------------------------------------- | +| Docs source/scope/links/terminology | Required | Docs additions required per slice. | +| F-5/F-6/F-7 public surface/JSR | Design audit | Surface diff, full-entrypoint doc lint, dry run, published consumer proof. | +| Type/slow-type soundness | API spellings reviewed | Positive/negative type fixtures with isolated declarations. | +| Adapter conformance | Exact plan only | Real provider runs and named fault injection. | +| F-13 runtime semantics | Laws and relay boundary | Crash/restart/lease/cancellation tests. | +| CLI consumer | Current command/template analysis | `check:emitted-samples` and full `scaffold.runtime` once at merge readiness. | + +Final docs gates: scoped Markdown format check, `docs:links`, `docs:accuracy` where applicable, +focused term/path/source assertions, raw `git diff --check`, diff/lock hygiene, and PR +metadata/thread audit. Product E2E is intentionally skipped for this docs-only PR under its CI +labels. + +## Plan-gate readiness + +| Question | Answer | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| Is the problem and non-scope precise? | Yes; one-store command atomicity and explicit refusal boundary. | +| Are target contracts named before implementation? | Yes; service command/relay, database command/provider/testing, contracts, and telemetry subpaths with a one-way dependency graph. | +| Are failure and cancellation semantics explicit? | Yes; no hidden retry, bounded busy, rollback, signal, relay lease. | +| Are extension axes and anti-patterns handled? | Yes; provider claims are exact, relay ownership is split, queue non-reuse is decided, and generated bridges are separate. | +| Are consumers and gates identified? | Yes; services, contracts, DB adapters, telemetry, workers/sagas, CLI/scaffold. | +| Are open questions non-blocking to drafting? | Yes; idempotency default, SQLite release timing, correlation telemetry, retention durations are isolated FCP policy choices. | + +Cycle-1 `FAIL_PLAN` is not self-overridden. F-B1–F-B7 and every Required-for-PASS item are now +addressed in the authored plan/RFC and the remediation gates are green. The draft returns to +`status:plan-eval` for the root orchestrator to steer the existing Fable session through cycle 2. +This author does not launch or approve that evaluation. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/research.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/research.md new file mode 100644 index 0000000000..3f02ace13f --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/research.md @@ -0,0 +1,148 @@ +# Research — docs-rfc-command-composition-kit--rfc + +## Re-baseline + +- Carried proposal: + `/home/codex/repos/netscript-fable5-remediation-plan/.llm/runs/plan-fable5-remediation-roadmap--seed/fable-5-remediation-plan/rfcs/RFC-B-command-composition-kit.md`. +- Authority baseline: `origin/main` at `fac9e339042c5394bf882311657d8981d353a1c3`, re-derived on + 2026-08-08. +- Live board: RFC issue #1361 (`0.0.6`), implementation #1363 (`0.0.8`), generated-service layout + #1362 (`0.0.8`), outbound-webhook recipe #1364 (`0.0.8`), typed-error repair #1350 (`0.0.7`), and + source roadmap PR #1347. +- Required authority read in full: `rfcs/README.md`, RFC template, current doctrine chapters, + archetypes 1–6, `SCOPE-docs`, `SCOPE-service`, gate matrix, plan gate, run loop, doc-audit, lane + policy, and both evaluator protocols/verdict definitions. + +## Baseline findings + +| # | Finding | Evidence | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R1 | Branch, HEAD, and merge-base began exactly at the requested baseline. | Raw `git rev-parse HEAD` and `git merge-base HEAD origin/main` both returned `fac9e339...`; branch was `docs/rfc-command-composition-kit`. | +| R2 | `withTransaction` has no production caller and its callback is publicly typed as the full root client through an assertion, not Prisma's narrower transaction client. | `packages/database/mod.ts:128-139`; focused `deno check --unstable-kv packages/database/rfc-command-probe.ts` exited 0 while the callback called root-only and nested `$transaction` methods. Probe was removed after execution. | +| R3 | `TransactionOptions` is usable, but `maxIsolation` would be a false capability abstraction: supported isolation levels are a provider-specific set and SQL Server `Snapshot` additionally depends on database configuration. | `packages/database/ports/database-client.ts:59-77`; Prisma transaction matrix; SQL Server official isolation docs. | +| R4 | The service builder dynamically injects `db`, `traceHeaders`, and authenticated `principal` into an open record. The public `ServiceContext` is a Hono `Context`, not that oRPC record. | `packages/service/src/builder/service-builder-impl.ts:256-281`; `packages/service/src/types.ts:239-272`. | +| R5 | A command subpath does not exist. Current service exports are root, `./auth`, and `./rpc-path`; database has no `./commands`; contracts has no command error fragment; telemetry's command vocabulary is absent. | The four package `deno.json` export maps plus `deno doc` on the public entrypoints. | +| R6 | The proposal's global `CONFLICT` approach would widen every route and is blocked by a real type defect. `baseContract` erases the literal error map, and `safe()` loses the error generic. | `packages/contracts/src/application/contract-primitives.ts:21-81`; `packages/sdk/src/client/errors.ts:49-90`; #1350. Focused SDK probe failed with `TS2339: Property 'code' does not exist on type 'never'`. | +| R7 | oRPC's current contract builder can add a route-local error fragment with `.errors(...)`; a global common-map addition is unnecessary. | Native `deno doc --filter ContractProcedureBuilder npm:@orpc/contract@1.14.6` shows sparse-merged `errors(errors)` on the builder. | +| R8 | Worker idempotency is a delivery claim/apply/release lifecycle in KV. It is neither response replay nor enrollable in a relational business transaction. | `packages/plugin-workers-core/src/ports/worker-idempotency-port.ts`; `src/stores/kv-worker-idempotency-store.ts`. | +| R9 | The proposal's “no outbox” claim is stale: a public `SagaOutboxPort` exists. It is explicitly reserved for saga T2 durability, has no transaction-bound handle or `AbortSignal`, and has no implementation/caller, so it cannot be reused as proof of command atomicity. | `packages/plugin-sagas-core/src/ports/saga-outbox-port.ts` and focused reference search. | +| R10 | Current telemetry exports `SpanNames` and builders from `@netscript/telemetry/attributes`, not the root. There is no command span or builder. Existing saga helpers can emit raw idempotency/correlation values, which is precedent to improve, not copy. | `packages/telemetry/deno.json`; `src/attributes/spans.ts`, `helpers.ts`, `saga.ts`. | +| R11 | Current generated Prisma CRUD handlers call delegates directly; they do not use a transaction or application layer. Explicit CLI surfaces already exist for `contract add-route` and `service add-handler`, but there is no command/schema/relay generator. | `packages/cli/src/kernel/assets/service/routers/v1.ts.template`; executed CLI `contract --help`, `service --help`, `db --help`, and `generate --help`. | +| R12 | The MySQL claim in the proposal conflates packages. `@netscript/database/adapters/mysql` currently exports `MysqlAdapter`; #1293 concerns the lower-level `@netscript/prisma-adapter-mysql` class/error hook and is adjacent, not a command-transaction blocker. | `packages/database/deno.json`; `adapters/mysql.adapter.ts`; live #1293. | +| R13 | PostgreSQL, MySQL, SQL Server, and SQLite can support one-store transaction semantics in Prisma, but current NetScript only publishes Postgres, MSSQL, and MySQL adapter subpaths. SQLite is named in `DatabaseProvider` yet has no NetScript adapter export. | `packages/database/deno.json`; provider type; Prisma transaction matrix. | +| R14 | Deno KV is an optimistic atomic batch, not an interactive transaction. Its checks/mutations and operation-size limits cannot host an arbitrary Prisma-style command callback or enroll SQL rows. | Deno KV transaction and API documentation. | +| R15 | SQLite permits multiple readers but only one simultaneous writer. “Serializable” in a matrix does not imply the same contention, timeout, or interactive-callback behavior as the SQL client/server adapters. | SQLite transaction documentation; Prisma matrix. | +| R16 | Exactly-once delivery cannot be inferred from a committed outbox row. A relay may publish and crash before marking the row, so message identity must be stable and consumers remain idempotent. | Doctrine chapter 08; current worker/saga delivery code; queue boundary analysis. | +| R17 | Trace propagation already uses W3C `traceparent`/`tracestate` across service, workers, and sagas. OTel producer/consumer spans or links can join deferred delivery without storing arbitrary trace attributes. | Service builder, worker dispatcher, saga delivery; W3C Trace Context and OTel tracing API. | +| R18 | #1361 is ratification-only and already has an implementation issue. #1362–#1364 are open at `0.0.8`; #1350 is open at `0.0.7`; this RFC PR creates/mutates no issue. Cycle-1 review additionally requires a proposed queue runtime-DDL reconciliation before future code reuse. | Live GitHub issue reads on 2026-08-08 plus authoritative `plan-eval.md`; proposal only, no board mutation. | +| R19 | The package-neutral codec signature is structurally sound with Zod, but `@netscript/service` does not currently declare `@standard-schema/spec`; a future public signature must add that dependency directly. | Focused S2 type-shape probe initially failed import resolution, passed after using the workspace's locked `jsr:@standard-schema/spec@1.1.0`, and was removed; `deno why` confirmed the locked module. | +| R20 | Current full-entrypoint doc-lint is clean for service/database but already reports private-type/JSDoc findings in contracts and telemetry. The future subpaths must not hide behind that baseline and must add zero new findings. | Structured `run-deno-doc-lint.ts`: service `0`, database combined `0`, contracts `9` private-type findings, telemetry `7` combined findings (`6` private-type, `1` missing JSDoc); all report commands exited `0`. | + +## PLAN-EVAL cycle-1 remediation findings + +`plan-eval.md` was read completely at evaluator commit `122301d25`. Its F-B1–F-B7 findings and +Required-for-PASS list are authoritative for this authorship cycle. + +| # | Finding | Evidence and design consequence | +| --- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R21 | A plain losing receipt insert cannot be normalized generically inside the transaction. | PostgreSQL uses `ON CONFLICT DO NOTHING RETURNING` with transaction-local `lock_timeout`; MySQL uses a savepoint/plain insert and recovers only 1062 with session `innodb_lock_wait_timeout`; SQL Server uses a named-index `UPDLOCK,HOLDLOCK` range lookup with `SET LOCK_TIMEOUT`. Timeout paths terminate and roll back the callback. | +| R22 | PostgreSQL's conflict-safe insert avoids 25P02, while stronger isolation may still choose a serialization victim. | PostgreSQL 18 `INSERT` docs, `lock_timeout`, `set_config`; the RFC permits retryable 40001 without callback replay and adds no-poisoning/leader commit/leader rollback integration cases. | +| R23 | MySQL timeout and duplicate behavior is statement-scoped by default, but server configuration can roll back more. | MySQL 8.4 InnoDB error handling says 1062 rolls back the statement and lock timeout rolls back the statement unless `innodb_rollback_on_timeout`; the adapter always aborts/rolls back the outer callback and restores the session timeout before pool return. | +| R24 | Relay ownership must preserve the one-way edge. | `packages/service/deno.json` has no direct database import today. Database will own raw relay-row/lease/release types and adapters; `@netscript/service/commands/relay` owns decoded delivery/sink/supervisor types and adds the direct service → database dependency. | +| R25 | `@netscript/queue` is algorithmic prior art, not a compatible relay adapter. | `postgres.adapter.ts:319-369` performs runtime schema creation; `:373-396` claims with `SKIP LOCKED`; ack deletes rows and nack uses queue DLQ semantics. V1 rejects a queue dependency, reuses the lease/test pattern, and proposes a separate migration-free queue reconciliation before future code sharing. | +| R26 | Scope or command-name drift changes the receipt key and honestly executes as new. | The unique key is `(scope, commandName, keyHash)`. Determinism fixtures and negative conformance now assert changed-scope/renamed-command execute-as-new, while same key/name/scope plus changed definition version mismatches. | +| R27 | MySQL currently advertises a false isolation token. | `packages/prisma-adapter-mysql/src/adapter.ts:476-481` includes `SNAPSHOT`; `startTransaction()` interpolates the upstream token. Stage 6 must remove it, allow-list/map the four MySQL levels, and add type/runtime negatives; #1293 remains separate. | +| R28 | The generated Prisma client supplies the consumer-specific transaction source type. | A temporary Prisma 7.8 `prisma-client` probe generated `Prisma.TransactionClient = Omit`. The current deny-list omits nested `$transaction`, so `db command-store add` must emit `CommandTransactionClient = Omit` and compile positive/negative samples. Probe artifacts were removed. | +| R29 | SQLite requires separate selectable/default capability truth. | Per the evaluator, a future SQLite command store advertises `selectableIsolationLevels: []` and `defaultIsolation: 'Serializable'`; explicit `Serializable` remains refused while the omitted default path can be considered by FCP Q2. | +| R30 | Existing telemetry/worker vocabulary contains claims the command kit must not copy. | Messaging emits `netscript.correlation.id`; saga defines `netscript.idempotency.key`; `WorkerIdempotencyPort` says “exactly-once-effective.” FCP Q3 now names the cross-runtime asymmetry, and Stage 5 corrects worker docs to at-least-once claim-window wording. | + +## Primary documentation + +| Topic | Primary source | Constraint carried into the RFC | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| Prisma transactions and isolation | [Prisma transaction reference](https://www.prisma.io/docs/orm/prisma-client/queries/transactions) | Interactive callback; `maxWait`/`timeout`; exact provider isolation sets; database default when omitted. | +| PostgreSQL isolation | [PostgreSQL 18 transaction isolation](https://www.postgresql.org/docs/current/transaction-iso.html) | PostgreSQL maps `ReadUncommitted` to `ReadCommitted`; serializable failures remain possible. | +| PostgreSQL receipt claim | [`INSERT ... ON CONFLICT`](https://www.postgresql.org/docs/current/sql-insert.html), [`lock_timeout`](https://www.postgresql.org/docs/current/runtime-config-client.html), and [`set_config`](https://www.postgresql.org/docs/current/functions-admin.html) | Conflict-safe insert does not poison the callback; a bounded transaction-local lock wait controls the duplicate race. | +| MySQL receipt claim/isolation | [InnoDB error handling](https://dev.mysql.com/doc/refman/8.4/en/innodb-error-handling.html), [`innodb_lock_wait_timeout`](https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html), and [`SET TRANSACTION`](https://dev.mysql.com/doc/refman/8.4/en/set-transaction.html) | Only four isolation levels; savepoint recovery is duplicate-specific; session timeout requires restoration. | +| SQL Server isolation | [SQL Server `SET TRANSACTION ISOLATION LEVEL`](https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-ver17) | `Snapshot` requires database configuration and is not an unconditional capability. | +| SQL Server receipt claim | [`SET LOCK_TIMEOUT`](https://learn.microsoft.com/en-us/sql/t-sql/statements/set-lock-timeout-transact-sql?view=sql-server-ver17), [table hints](https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver17), and [key-range locking](https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver17) | Named-index `UPDLOCK,HOLDLOCK` protects an absent singleton without duplicate-error recovery. | +| SQLite transactions | [SQLite transaction documentation](https://www.sqlite.org/lang_transaction.html) | One simultaneous writer; adapter must test busy/timeout paths rather than claim parity from a label. | +| Deno KV | [Deno KV transactions](https://docs.deno.com/deploy/kv/transactions/) and [Deno.Kv API](https://docs.deno.com/api/deno/~/Deno.Kv) | Optimistic `check` + atomic mutations with bounded checks, mutations, and bytes; refused from v1 command-store conformance. | +| Canonical JSON | [RFC 8785](https://www.rfc-editor.org/info/rfc8785/) | JCS over an I-JSON value before Web Crypto SHA-256; no `JSON.stringify` folklore. | +| Trace propagation | [W3C Trace Context](https://www.w3.org/TR/trace-context/) | Persist validated `traceparent`/`tracestate`, not an invented trace-id/correlation equivalence. | +| Deferred trace relationship | [OTel tracing API](https://opentelemetry.io/docs/specs/otel/trace/api/) | Relay spans are producer spans; deferred consumers may use propagated parent context or links. | +| Attribute safety | [OTel requirement levels](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) and [convention authoring](https://opentelemetry.io/docs/specs/semconv/how-to-write-conventions/) | Privacy-risk/high-cardinality values are not default attributes; enum attributes have a closed documented vocabulary. | + +## Proposal adjudication + +| Proposal statement | Verdict | RFC correction | +| ------------------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Reuse `withTransaction` verbatim. | Reject. | Preserve `TransactionOptions`/`IsolationLevel`, but first correct or bypass the full-client callback assertion; adapter conformance uses a true `TTx`. | +| Expose `maxIsolation`. | Reject. | Publish `selectableIsolationLevels`, separate `defaultIsolation`, and configuration preconditions; levels are not one portable scalar ceiling. | +| Expose `conditionalWriteCount` as a store capability. | Reject. | Optimistic concurrency is a repository mutation contract. The command handler must perform a compare-and-set and map zero affected rows to `CommandFailure.conflict`; a read-then-compare helper is race-prone. | +| `expectVersion(current)` is sufficient. | Reject. | `expectedVersion` is a string in the envelope; the guide uses one conditional write inside the transaction. No generic command kit can synthesize a model-specific CAS. | +| Add `CONFLICT` globally. | Reject. | Add an opt-in `commandErrorMap` on `@netscript/contracts/commands` after #1350, preserving non-command routes. | +| Store an `unknown` replay response. | Reject. | Every command supplies an explicit output codec to/from canonical JSON text; Date/BigInt/class instances cannot silently cross the receipt boundary. | +| Hash “validated input + actor + expected version.” | Narrow. | Hash exact JCS bytes for `{command, definitionVersion, scope, input, actor: {kind, subject}, expectedVersion}` with SHA-256. Roles/scopes/claims and the idempotency key are excluded. | +| In-flight receipt is a durable state. | Reject for the normal path. | Claim and completion are in one transaction, so incomplete claims never commit. Concurrent callers wait, replay, or receive a bounded retryable busy failure; a visible incomplete row is corruption. | +| Emit raw scope, key, and expected version in telemetry. | Reject. | Emit only registered command name, bounded enums/booleans/counts, isolation, and outcome. Raw actor, scope, request/key hashes, payload, version, and correlation are prohibited by default. | +| Deno KV receives weaker command semantics. | Reject from v1. | A store either conforms to same-commit command semantics or is not a `CommandStorePort`. KV delivery idempotency remains a separate runtime pattern. | +| SQLite is already a supported adapter. | Reject as current-state claim. | Matrix separates storage feasibility from shipped NetScript integration; SQLite stays “implementation prerequisite / unproven” until an adapter and fault suite exist. | +| There is no outbox primitive. | Reject as current-state claim. | Cite the reserved saga outbox port as prior art, but define a command-specific transaction-bound outbox record and relay store. | + +## Locked design inputs + +1. Public command semantics live at `@netscript/service/commands`; they are not root re-exports. +2. Transaction-bound persistence types and provider adapters live at `@netscript/database/commands`. + This preserves a one-way service → database dependency and avoids database → service → database. +3. Opt-in transport error schemas/map live at `@netscript/contracts/commands`; they do not enlarge + `baseContract` globally. +4. Command span names/attribute builders extend `@netscript/telemetry/attributes`. +5. Consumer-owned, provider-specific generated Prisma models own receipt/audit/outbox rows. The + framework ships logical row contracts and explicit generators, never hidden schema injection. +6. The command store is a conformant same-commit port. No false/weak capability branch is admitted. +7. A receipt key is `(scope, command name, SHA-256(idempotency key))`; request replay is guarded by + the separate canonical request hash. Keys must be high-entropy, are never logged, and are not + treated as authentication secrets. +8. The envelope carries a narrowed actor (`subject`, optional `scheme`), correlation id, validated + trace context, optional idempotency key, and string expected version. Authorization remains at + the service boundary; roles/scopes/claims are not copied into durable command metadata. +9. The runner does not automatically retry transaction callbacks. Serialization/deadlock/busy + failures are typed retryable outcomes; caller retry with the same key is the recovery path. +10. External I/O is forbidden inside the command callback. The only supported remote-effect path is + a same-commit outbox row followed by at-least-once relay. +11. Raw relay persistence types/adapter live in database; decoded delivery/sink/supervisor live in + service. The new dependency is service → database only. +12. V1 does not depend on or wrap `@netscript/queue`; runtime DDL and incompatible settlement + semantics require a separately proposed queue reconciliation before code reuse. +13. Provider receipt claims and bounded wait mechanics are locked now, not deferred to FCP. + +## JSR/public-surface audit + +| Surface | Planned consequence | Gate for implementation | +| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `@netscript/service/commands`, `./commands/relay`, and `./commands/testing` | New explicit subpaths; no root-budget growth. Every exported generic needs an explicit return type and module/symbol docs. `@standard-schema/spec` and release-matched `@netscript/database` must be direct package dependencies. | Export/dependency-graph diff, `deno doc --lint` across all service entrypoints, dry-run file-list review, consumer import fixture. | +| `@netscript/database/commands`, provider adapter subpaths, and `./commands/testing` | Database publish include must intentionally include the new directory; raw relay/store contracts expose no service, generated Prisma, or driver type. | Surface/dependency diff; slow-type gate; clean consumer with structural fake and real generated client. | +| `@netscript/contracts/commands` | Literal command error keys must survive isolated declarations; depends on #1350 fixing the base error-map widening. | Positive/negative type fixtures, `deno doc --lint`, publish dry-run for contracts. | +| `@netscript/telemetry/attributes` | Existing subpath grows; no new telemetry root export. Enum/value docs must be stable before publish. | Deno doc diff and exact attribute-map tests. | +| CLI-generated models/bridge/relay | Generated consumer files are not package exports, but templates and assets must be publish-safe generated constants. | Emitted-sample check, CLI scaffold runtime E2E in the implementation PR, no import-attribute/runtime file-read trap. | + +This RFC PR changes none of those exports. Its publish consequence is therefore design evidence, not +a claim that the future package surfaces already pass. The implementation must run the repository's +structured doc-lint and publish-dry-run gates on every changed package and must not use a green dry +run as a substitute for the published consumer gate. + +## Remaining FCP questions + +- Must a v1 command definition require idempotency, or may it explicitly declare `optional` and + surface `not_requested`? The RFC recommends required-by-default and permits opt-out only by an + explicit policy. +- Does v1 accept SQLite's default-only shape (`selectableIsolationLevels: []`, + `defaultIsolation: 'Serializable'`), and is that adapter a first-release blocker? The RFC + recommends accepting the shape but releasing later, with no support claim until conformance. +- Should command correlation IDs be opt-in telemetry attributes under a redaction policy, or stay + durable/log-only, and should existing messaging/saga correlation/idempotency attributes be + separately deprecated/redacted to remove operator asymmetry? The RFC recommends durable/log-only + commands and separate cleanup. +- What retention defaults apply to receipts, audit, and published outbox rows? The RFC fixes safety + constraints but leaves durations to FCP because they are operational policy, not atomicity. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/supervisor.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/supervisor.md new file mode 100644 index 0000000000..35e47fd0aa --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/supervisor.md @@ -0,0 +1,37 @@ +# Supervisor Identity — docs-rfc-command-composition-kit--rfc + +Written at run start per `workflow/lane-policy.md` § Supervisor identity. This is a single-generator +RFC/docs design run; no implementation sub-agents or rival worktree sessions are authorized. + +| Field | Value | +| -------- | ------------------------------------------------------------------------- | +| Model | OpenAI GPT-5.6 Sol (`gpt-5.6-sol`), xhigh reasoning | +| Session | Codex thread `019fe242-2c45-7e03-a428-eebfb968eda0` | +| Host | Linux / WSL user `codex`; danger-full-access; approval policy `never` | +| Checkout | `/home/codex/repos/ns-rfc-command-kit` | +| Worktree | `/home/codex/repos/ns-rfc-command-kit` (native WSL filesystem) | +| Branch | `docs/rfc-command-composition-kit` (no upstream by design) | +| Baseline | `origin/main` at `fac9e339042c5394bf882311657d8981d353a1c3` on 2026-08-08 | +| Run ID | `docs-rfc-command-composition-kit--rfc` | + +## Routes in force + +| Task lane | Provider / model / effort | Role in this run | +| ------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------- | +| RFC generator (owner override of complex implementation lane) | OpenAI / GPT-5.6 Sol / xhigh | Sole researcher and RFC author | +| Cross-RFC review | Existing Claude Fable 5 session, steered by root orchestrator | Pending; generator must not launch it | +| Final adversarial evaluation | Qwen session selected and launched by root orchestrator | Pending; generator must not launch it | + +Reference `.llm/harness/workflow/lane-policy.md`; evaluator-session separation remains mandatory. + +## Recorded lane/eval overrides + +- Owner brief pins this generator to GPT-5.6 Sol at xhigh, above the canonical + complex-implementation lane's usual high effort. The observed route matches the requested route; + see `codex-thread-ids.md`. +- Owner brief reserves PLAN-EVAL / cross-RFC review and the final adversarial pass for the root + orchestrator. This generator will prepare complete evaluator inputs and stop at + `status:plan-eval`; it will not self-evaluate or launch a rival session. +- `deno task agentic:runtime status --worktree /home/codex/repos/ns-rfc-command-kit` exited 3 with + `MISSING_IDENTITY` and made no change. The pre-staged thread receipt is the session identity; + daemon repair is neither necessary nor authorized while this attached turn is active. diff --git a/.llm/runs/docs-rfc-command-composition-kit--rfc/worklog.md b/.llm/runs/docs-rfc-command-composition-kit--rfc/worklog.md new file mode 100644 index 0000000000..fa9f43d8fa --- /dev/null +++ b/.llm/runs/docs-rfc-command-composition-kit--rfc/worklog.md @@ -0,0 +1,187 @@ +# Worklog: production command composition kit RFC + +## Run Metadata + +| Field | Value | +| -------------- | ------------------------------------------------------------------------------------------------------------------- | +| Run ID | `docs-rfc-command-composition-kit--rfc` | +| Branch | `docs/rfc-command-composition-kit` | +| Archetype | Docs delivery describing A4 contracts/service, A2 adapters/telemetry, A3 runtime discipline, A5 plugins, and A6 CLI | +| Scope overlays | `SCOPE-docs`, `SCOPE-service` | + +## Design + +The S1 re-baseline locked the RFC contract before prose authoring. S2 has now rendered those +decisions as exact guide/reference contracts in `rfcs/0000-command-composition-kit.md`. This PR +remains docs-only; the product names below are future surfaces whose implementation is decomposed +after ratification. + +### Public Surface + +- `rfcs/0000-command-composition-kit.md` — draft RFC only; no product export changes. +- Future `@netscript/service/commands` executor/DSL plus `./commands/relay` decoded runtime/sink and + testing subpaths. +- Future `@netscript/database/commands` raw command/relay stores, provider adapter subpaths, and + conformance subpath; service adds a direct database dependency and database never imports service. +- Future opt-in `@netscript/contracts/commands` error map and existing telemetry attributes + extension. +- Explicit consumer-owned receipt/audit/outbox models and `db command-store add` generated + relay/command bridge plus `CommandTransactionClient`. +- One attempt-scoped `executionId` joins receipt, audit, and outbox evidence; typed codecs guard + replay outputs and outbox payloads. + +### Domain Vocabulary + +- command — one caller intent applied within one supported store transaction. +- receipt — durable idempotency outcome keyed by command identity and canonical request hash. +- audit record — same-commit actor/correlation evidence, not a mutable event log. +- outbox message — same-commit delivery intent relayed at least once after commit. +- capability/refusal — adapter truth describing which guarantees exist and why execution may refuse. + +### Ports + +- command store — conformant only where one transaction-scoped client can write business state, + receipt, audit, and outbox rows; no weaker implementation satisfies the port. +- outbox relay store — database-owned raw lease/token/release port; no decoded service type. +- relay sink/supervisor — service-owned decoded post-commit runtime seam; it cannot enlarge the + transaction boundary and does not make database depend on service. +- telemetry/redaction policy — injected observability seam with bounded attributes. + +### Constants + +- RFC status: `Draft`; RFC number: `0000` until maintainer acceptance. +- Claim vocabulary: `one-store`, `at-least-once`, `idempotent replay`, `same-commit`, `refused`. +- Lifecycle status progression for this run: `status:research` → `status:plan` → `status:plan-eval`. + +### Commit Slices + +| # | Slice | Gate | Files | +| -- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------- | +| B0 | Activate the harness and expose the run on a draft PR. | raw git identity/status; artifact presence | `.llm/runs/docs-rfc-command-composition-kit--rfc/*` | +| S1 | Re-baseline proposal, code, exports, tests, docs, live board, and primary adapter guarantees; lock the plan. | evidence inventory; `deno doc`; plan-gate readiness check | run `research.md`, `plan.md`, `worklog.md`, `context-pack.md`, `drift.md` | +| S2 | Define the exact public contracts, semantic laws, capability matrix, failure/telemetry/security model, and examples. | source-alignment audit; focused probes | RFC plus run artifacts | +| S3 | Complete worker/saga boundary, conformance/failure injection, compatibility, staging, docs/scaffold impact, issue decomposition, and handoff. | docs/RFC/link/format/diff gates | RFC, run artifacts, `final-handoff.md` | +| S4 | Remediate authoritative PLAN-EVAL cycle-1 findings F-B1–F-B7 without self-evaluation. | provider/source probes; docs/type/link/diff checks | RFC, research/plan/worklog/context/drift/final handoff, launcher metadata | + +### Deferred Scope + +- Product/package/plugin implementation — belongs to #1363 and follow-on implementation slices. +- Cross-store coordination and distributed transactions — explicitly refused. +- Relay deployment/operations — specified only as the narrow boundary necessary to consume outbox + rows. +- Formal cross-RFC review and final adversarial evaluation — launched by the root orchestrator, not + this generator. + +### Contributor Path + +Start at the RFC's semantic laws and capability matrix; implement contract-first in the staged issue +order, then add each adapter only after its injected-failure conformance suite proves the advertised +capabilities. + +## Progress Log + +| Time | Slice | Step | Notes | +| ---------- | ----- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 2026-08-08 | B0 | bootstrap | Read all named skills and required RFC/harness/doctrine/evaluator authority; verified pinned branch/base. | +| 2026-08-08 | B0 | session health | Agentic runtime status made no changes and returned `MISSING_IDENTITY`; preserved the staged attached-thread receipt. | +| 2026-08-08 | B0 | live issue check | Confirmed #1361–#1364 and source PR #1347 exist; detailed re-baseline continues in S1. | +| 2026-08-08 | B0 | publish | Committed `ad643e15d657eb2b8e7a2d741e690c028ce67dc9`, pushed only as `HEAD:refs/heads/docs/rfc-command-composition-kit`, and opened draft PR #1389. | +| 2026-08-08 | B0 | PR metadata | Applied the required docs/RFC/area/priority/CI labels with exactly `status:research`; posted the opening `[PHASE: RESEARCH]` comment. | +| 2026-08-08 | S1 | public API re-baseline | Used native `deno doc` on database/service/contracts/telemetry/workers/sagas/SDK and oRPC; inspected export maps and source where the public surface was insufficient. | +| 2026-08-08 | S1 | focused probes | Proved `withTransaction` exposes root-only methods inside its callback (unexpected PASS) and reproduced #1350's `error.code` → `never` failure; removed both probes after execution. | +| 2026-08-08 | S1 | runtime/store research | Compared Prisma provider isolation, PostgreSQL, SQL Server, SQLite, Deno KV, RFC 8785, W3C Trace Context, and OTel primary documents. | +| 2026-08-08 | S1 | proposal challenge | Found the reserved saga outbox port, dynamic service context, missing SQLite adapter, route-local oRPC error composition, and #1293 package conflation; recorded corrections in `research.md`. | +| 2026-08-08 | S1 | plan lock | Locked package ownership, hash/codec rules, no-weak-port/no-hidden-retry laws, telemetry redaction, adapter truth, and board decomposition; plan gate is ready. | +| 2026-08-08 | S2 | RFC contract draft | Authored the complete template-based RFC with exact service/store/contract/relay types, consumer-owned logical rows, JCS hashing, transaction algorithm, and semantic laws. | +| 2026-08-08 | S2 | production review | Added an attempt `executionId`, schema-backed outbox payloads, deterministic scope constraints, and an explicit retry/terminal relay release union after reviewing the exact contracts. | +| 2026-08-08 | S2 | boundary proof | Kept remote delivery post-commit and at least once; separated the existing saga outbox and worker delivery claims from the command receipt/store guarantee. | +| 2026-08-08 | S2 | surface design | Specified focused service/database/contracts subpaths and telemetry additions, JSR/slow-type/publish consequences, migration ownership, and staged implementation without changing exports. | +| 2026-08-08 | S2 | type-shape probe | Checked a synthetic command definition against current DB types, Zod, and Standard Schema. Initial resolution exposed the missing service dependency; direct JSR import passed; probe removed. | +| 2026-08-08 | S3 | docs/RFC gates | Scoped format/diff, repository docs links/accuracy, RFC frontmatter/section/terminology assertions, and docs-only changed-file audit passed. | +| 2026-08-08 | S3 | JSR baseline | Full-entrypoint doc-lint reported service 0, database combined 0, contracts 9 existing private-type findings, and telemetry 7 existing findings; no package file changed. | +| 2026-08-08 | S3 | PR reconciliation | Review-thread gate passed with 0 threads/unanswered; PR-check gate passed with 15 checks and 0 current failures (docs-only lanes intentionally skipped). | +| 2026-08-08 | S4 | evaluator intake | Preserved evaluator commit `122301d25`; read `plan-eval.md` completely and accepted `FAIL_PLAN`/F-B1–F-B7/Required-for-PASS as authoritative. | +| 2026-08-08 | S4 | provider verification | Re-read doctrine/harness authority; checked queue runtime DDL/lease semantics, service/database dependencies, MySQL isolation, telemetry/worker vocabulary, current CLI `db` help, and primary lock docs. | +| 2026-08-08 | S4 | Prisma type probe | Generated a temporary Prisma 7.8 client, confirmed `Prisma.TransactionClient`, found current deny-list retains `$transaction`, locked the generator-owned explicit `Omit`, and removed the probe. | +| 2026-08-08 | S4 | RFC remediation | Added normative PostgreSQL/MySQL/SQL Server receipt claims/timeouts, relay ownership, queue rejection/reconciliation, identity drift laws, MySQL/SQLite truth, generated typing, and F-B7 batch corrections. | +| 2026-08-08 | S4 | remediation publish | Committed `c98c08adabbd992a557ff7c596deae68b9c9cd62`, pushed only as `HEAD:refs/heads/docs/rfc-command-composition-kit`, and posted a non-verdict `[PHASE: PLAN]` remediation comment. | +| 2026-08-08 | S4 | remediation gates | Scoped authored-Markdown format, 17-claim RFC assertion, public-entrypoint Deno check, docs links/accuracy, diff/lock hygiene, review threads, and PR checks passed. | +| 2026-08-08 | S4 | cycle-2 handoff | Reconciled plan/context/final handoff for root-steered re-evaluation; no evaluator was launched and cycle-1 `FAIL_PLAN` remains authoritative until Fable reports cycle 2. | + +## Decisions + +| Decision | Reason | Source | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| Keep this PR docs-only and RFC number `0000`. | RFC lifecycle and issue #1361 scope. | `rfcs/README.md`, #1361 | +| Select A4 for contracts/service DSLs, A2 for database/telemetry adapters, A3 for relay runtime discipline, A5 for thin plugin integrations, and A6 for generators; A1 was read but is not selected. | The design crosses those implementation surfaces, while this PR changes documentation only. | Doctrine chapter 06; harness archetypes | +| Do not launch evaluators or a second Codex session. | Owner reserves review/evaluation for the root orchestrator; mobile-visible thread must remain singular. | implementation brief; `codex-wsl-remote` | +| Put command semantics in service, transaction persistence in database, opt-in errors in contracts, and attributes in telemetry. | Preserves focused subpaths and one-way service → database dependency without a new package. | code/export re-baseline; doctrine A9–A11 | +| Reject a weak KV command mode and automatic callback retry. | Either all local rows share one commit or the adapter does not conform; caller retry is receipt-safe and observable. | Deno KV docs; doctrine A12–A13 | +| Reject read-then-compare concurrency and unknown receipt values. | CAS is repository-specific; explicit JCS fingerprints/codecs prevent races and serialization ambiguity. | Prisma OCC docs; RFC 8785; focused API analysis | +| Keep remote effects outside the callback and relay stable outbox IDs at least once. | SQL cannot roll back network effects; publish-then-crash can duplicate delivery. | worker/saga code; OTel/W3C docs; doctrine chapter 08 | +| Join receipt, audit, and outbox evidence with one attempt `executionId`. | Correlation alone is neither unique nor a reliable ownership/join key; optional unkeyed attempts still need identity. | S2 exact-row review | +| Require a schema-backed codec for each outbox payload and model terminal relay release explicitly. | Producer type safety and exhaustion handling cannot exist only in prose. | S2 exact-port review | +| Lock provider-specific receipt algorithms and treat claim timeout as callback-terminal. | Prevents PostgreSQL poisoning, MySQL error over-swallowing, SQL Server race, and session timeout leakage. | PLAN-EVAL F-B1; primary provider docs | +| Split relay persistence into database and decoded runtime/sinks into service. | Preserves dependency direction while giving every public relay type an owner. | PLAN-EVAL F-B2; doctrine ports/layering | +| Reject direct queue-package reuse in v1 and propose runtime-DDL reconciliation first. | Queue deletes/acks/DLQs and hidden schema creation do not implement retained token-settled outbox semantics. | PLAN-EVAL F-B3; queue source | +| Treat changed scope/name as execute-as-new and require determinism/compatibility fixtures. | The unique key cannot detect identity in another namespace; migration law must match actual behavior. | PLAN-EVAL F-B4 | +| Separate selectable/default isolation and require MySQL four-level allow-list. | Makes SQLite default-only behavior and MySQL's false `SNAPSHOT` token honest. | PLAN-EVAL F-B5/F-B7f | +| Generate `CommandTransactionClient` from the consumer's Prisma namespace and explicitly omit root methods. | Model delegates are consumer-specific, and current Prisma deny-list retains nested `$transaction`. | PLAN-EVAL F-B6; focused probe | + +## Drift + +| Drift | Severity | Logged in drift.md | +| ----------------------------------------------------------------------------------------------------- | ----------- | ------------------ | +| Runtime controller could not correlate the active staged thread to persisted runtime identity. | minor | yes | +| Proposal says no outbox exists, but a reserved saga outbox port is public. | significant | yes | +| Proposal treats current SQLite/MySQL/package and transaction-helper surfaces too broadly. | significant | yes | +| Cycle-1 evaluator found receipt algorithms, relay ownership, and queue reuse were silently deferred. | critical | yes | +| Queue's existing runtime schema creation conflicts with the new command-kit no-hidden-migration rule. | significant | yes | + +## Gate Results + +### Static Gates + +| Gate | Command or check | Result | Notes | +| ------------------------------ | ----------------------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------- | +| Branch/base identity | raw git `rev-parse`, `merge-base`, `status` | PASS | Branch and base exactly match the brief. | +| Mandatory authority read | complete file reads | PASS | RFC process, doctrine, gate/evaluator files and selected profiles read before RFC authoring. | +| Native public API inspection | `deno doc --filter` on database, service, SDK, telemetry, workers and oRPC | PASS | Exact current signatures and missing subpaths recorded in research. | +| Focused transaction type probe | `deno check --unstable-kv packages/database/rfc-command-probe.ts` | EXPECTED_FAIL_MISSED | Exit 0 proves the callback surface is too broad; temporary probe removed. | +| Focused typed-error probe | `deno check --unstable-kv packages/sdk/rfc-command-probe.ts` | EXPECTED_FAIL | Exit 1 with TS2339 on `error.code`; temporary probe removed. | +| Focused RFC type-shape probe | `deno check --unstable-kv packages/service/rfc-command-contract-probe.ts` | PASS_AFTER_FINDING | First run found missing service Standard Schema mapping; direct locked import passed; probe removed. | +| Focused Prisma generated type | temporary Prisma 7.8 generate/check probe | PASS | Confirmed generated `Prisma.TransactionClient`; current deny-list retains nested `$transaction`; probe removed. | +| Current public entrypoint type | `deno check --no-lock --unstable-kv` over database/service/contracts/telemetry mods | PASS | Existing peer/build-script warnings only; no lock mutation. | +| Markdown format | scoped `deno fmt --check` over authored RFC/remediation/handoff Markdown | PASS | Generated launcher metadata retained in launcher-emitted form and audited separately. | +| Diff hygiene | `git diff --check` and changed-file audit | PASS | Only the RFC and required run directory differ from the pinned base; no lock/product change. | +| Docs links | `rtk proxy deno task docs:links` | PASS | 102 docs; 0 broken links, 0 broken anchors, 0 orphans. | +| Docs accuracy | `rtk proxy deno task docs:accuracy` | PASS | Saga/storefront/path/CLI/Fresh accuracy checks passed. | +| RFC structure/terminology | focused `deno eval` frontmatter/section/forbidden-term assertion | PASS | Draft/0000/#1361 and all required sections present; forbidden domain vocabulary absent. | +| PR review threads | `rtk proxy deno task agentic:review-threads -- ... --pr 1389 --pretty` | PASS | 0 threads; 0 unanswered. | +| PR checks | `rtk proxy deno task agentic:pr-checks -- ... --pr 1389 --pretty` | PASS | At `c98c08ada`: 15 reconciled checks; 0 current failures; docs-only CI lanes skipped. | + +### Fitness Gates + +| Gate | Result | Evidence | Notes | +| --------------------------------- | --------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | +| F-5/F-6/F-7 planned surface audit | PASS_DESIGN | `research.md` JSR/public-surface table | Design-time only; implementation gates specified per subpath. | +| F-13 runtime invariants | PASS_DESIGN | `plan.md` semantic laws and relay boundary | RFC must carry the exact injected-failure matrix in S2/S3. | +| Current package doc-lint baseline | PASS_WITH_BASELINE_FINDINGS | structured full-entrypoint reports | service/database combined 0; contracts 9 and telemetry 7 pre-existing findings; changed surface is docs-only. | + +### Runtime Gates + +| Gate | Result | Evidence | Notes | +| --------------- | ------ | ------------------- | ---------------------------- | +| Product runtime | N/A | RFC/docs-only scope | No product behavior changes. | + +### Consumer Gates + +| Consumer | Result | Evidence | Notes | +| ---------------------------------- | ------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| Existing service/scaffold surfaces | PASS_ANALYSIS | Executed CLI help; CRUD template and add-route/add-handler source inspection | Current explicit commands and missing command/schema/relay paths recorded. | + +## Handoff Notes + +- Final handoff is `final-handoff.md`. The root orchestrator should inspect the RFC's public + contracts, semantic laws, adapter matrix, and refusal boundary first, then use the exact Fable + prompt there. No evaluator was launched by this generator. diff --git a/rfcs/0003-command-composition-kit.md b/rfcs/0003-command-composition-kit.md new file mode 100644 index 0000000000..e0c374a761 --- /dev/null +++ b/rfcs/0003-command-composition-kit.md @@ -0,0 +1,1966 @@ +--- +rfc: 0003 +title: Production command composition kit +status: Accepted +authors: ['@rickylabs'] +created: 2026-08-08 +tracking-issue: https://github.com/rickylabs/netscript/issues/1361 +target-milestone: 0.0.6 +--- + +# Production command composition kit + +## Summary + +This RFC defines a small, opt-in NetScript command composition kit for a single transactional store. +A conforming command executes business writes and writes its idempotency receipt, audit records, and +outbox messages in the same commit. A retry with the same idempotency key and canonical request +returns the stored result without re-running the command. Remote delivery begins only after commit +through an at-least-once outbox relay. Stores that cannot provide the same commit, operations that +span stores, and network effects inside the transaction are refused rather than described as atomic. +This is a command boundary, not an ORM, workflow engine, event-sourcing system, or distributed +transaction protocol. + +The frontmatter `target-milestone: 0.0.6` is the target for **RFC ratification only**. It is not an +implementation availability date: the implementation umbrella is #1363 in `0.0.8`, and the +literal-error prerequisite #1350 is currently targeted to `0.0.7`. + +## Motivation + +NetScript currently exposes useful pieces but no supported way to compose them into a production +command: + +- `@netscript/database` exports `withTransaction()` and transaction options, but the helper has no + production caller and types the callback as the full root client through an assertion. +- generated service handlers call Prisma delegates directly; +- `@netscript/plugin-workers-core` has KV-backed delivery claims, but they cannot join a relational + commit or replay a service response; +- `@netscript/plugin-sagas-core` exposes compensation and a reserved saga outbox port, but that port + has no adapter and is not transaction-bound to service business state; +- service, workers, and sagas propagate W3C trace context, while telemetry has no command + vocabulary; and +- consumers must invent receipt, audit, outbox, relay, failure, and migration conventions. + +The result is not merely duplicated code. Each application can choose a different answer to the hard +parts: whether a retry re-executes, what a reused key means, whether an audit record can commit +after the state it describes, whether an event is published before commit, and whether a remote side +effect is somehow considered transactional. + +The cost of doing nothing is a framework that generates CRUD well but leaves the first non-CRUD +state transition to application folklore. The proposed kit supplies one honest composition seam: + +> one command attempt, one store transaction, one durable answer, and zero remote effects before +> commit. + +It unlocks contract-first state transitions with optimistic concurrency, replay-safe HTTP or RPC +retries, durable operational evidence, and a visible bridge to workers and sagas. It does not make +every effect occur once. It makes the local commit indivisible and makes every boundary after that +commit explicit. + +## Guide-level explanation + +### The mental model + +A command is one validated caller intent that may change state. It has five stages: + +1. the service contract validates input and authenticates/authorizes the caller; +2. the executor computes an idempotency fingerprint and opens one supported store transaction; +3. the handler performs business writes through the transaction client and buffers audit/outbox + records; +4. the store writes the buffers and replay receipt, then commits them with the business state; and +5. a separate relay later publishes committed outbox messages at least once. + +If the operation needs another database, a KV mutation, or a remote call before it can be considered +successful, it is not one command transaction. Commit the local intent to the outbox and continue +through a worker or saga. + +### Define the contract first + +Command errors are opt-in. They do not enlarge every route built from `baseContract`. + +```ts +import { commandBaseContract, type CommandContractRoute } from '@netscript/contracts/commands'; +import { z } from 'zod'; + +export const renameProjectInput = z.object({ + id: z.string().min(1), + name: z.string().min(1).max(120), + expectedVersion: z.string().regex(/^\d+$/), + idempotencyKey: z.string().min(16).max(256), +}); + +export const projectOutput = z.object({ + id: z.string(), + name: z.string(), + version: z.string(), +}); + +export const projectRenamedPayload = z.object({ + projectId: z.string(), + name: z.string(), + version: z.string(), +}); + +export const renameProject: CommandContractRoute< + typeof renameProjectInput, + typeof projectOutput +> = commandBaseContract + .route({ method: 'POST', path: '/projects/{id}:rename' }) + .input(renameProjectInput) + .output(projectOutput); +``` + +`commandBaseContract` is `baseContract.errors(commandErrorMap)`. `commandErrorMap` adds only the +command failures a client can safely act on: `COMMAND_CONFLICT`, `IDEMPOTENCY_KEY_REUSE`, and +`COMMAND_IN_PROGRESS`. Normal authentication, authorization, validation, and service-unavailable +errors remain the existing contract vocabulary. + +### Define one command + +The command definition names its isolation, idempotency, replay codec, audit policy, and handler. +The example performs optimistic concurrency as a conditional update. It does not read a version and +compare it in memory. + +```ts +import { type CommandDefinition, defineCommand, jsonCodec } from '@netscript/service/commands'; +import type { CommandTransactionClient } from '@database'; +import { z } from 'zod'; +import { projectOutput, projectRenamedPayload, renameProjectInput } from '../contracts/projects.ts'; + +export const renameProjectCommand = defineCommand({ + name: 'projects.rename', + definitionVersion: 1, + isolationLevel: 'ReadCommitted', + idempotency: { + mode: 'required', + scope: ({ input }) => input.id, + fingerprint: ({ id, name }) => ({ id, name }), + response: jsonCodec(projectOutput), + }, + records: { + audit: 'required', + outbox: 'optional', + }, + async handle(ctx) { + const updated = await ctx.tx.project.updateMany({ + where: { + id: ctx.envelope.input.id, + version: Number(ctx.envelope.expectedVersion), + }, + data: { + name: ctx.envelope.input.name, + version: { increment: 1 }, + }, + }); + + if (updated.count !== 1) { + ctx.conflict(); + } + + const project = await ctx.tx.project.findUniqueOrThrow({ + where: { id: ctx.envelope.input.id }, + }); + + ctx.audit({ + action: 'project.rename', + subject: { type: 'project', id: project.id }, + data: { version: String(project.version) }, + }); + + ctx.publish({ + destination: 'project-events', + topic: 'project.renamed.v1', + codec: jsonCodec(projectRenamedPayload), + payload: { + projectId: project.id, + name: project.name, + version: String(project.version), + }, + }); + + return { + id: project.id, + name: project.name, + version: String(project.version), + }; + }, +}) satisfies CommandDefinition< + 'projects.rename', + z.infer, + z.infer, + CommandTransactionClient +>; +``` + +The handler may use the transaction client, injected clock/ID source, and synchronous recorders. It +must not call a remote service, enqueue directly, send email, or publish to a saga bus. Capturing +such a client in the closure is possible in TypeScript, but is outside the command guarantee and +fails conformance review. + +### Compose the executor explicitly + +The consumer owns the Prisma schema and generated client. An explicit generator adds the three +logical tables and a small bridge that binds their generated delegates to the package-owned store +port. + +```ts +import { createCommandExecutor } from '@netscript/service/commands'; +import { commandStore } from '../infrastructure/command-store.ts'; + +export const commands = createCommandExecutor({ + store: commandStore, + limits: { + auditRecords: 16, + outboxRecords: 32, + recordBytes: 64 * 1024, + }, +}); +``` + +The oRPC handler derives the envelope from already-validated input and the authenticated service +context. A system-triggered command must supply an explicit system actor; actor absence is never +silently treated as anonymous. + +```ts +import { throwCommandContractError } from '@netscript/contracts/commands'; + +export const renameProjectHandler = renameProject.handler( + async ({ input, context, errors }) => { + try { + const result = await commands.execute( + renameProjectCommand, + { + input, + actor: { + kind: 'principal', + subject: context.principal.subject, + scheme: context.principal.scheme, + }, + correlationId: context.correlationId, + trace: context.traceHeaders, + idempotencyKey: input.idempotencyKey, + expectedVersion: input.expectedVersion, + }, + { signal: context.signal }, + ); + return result.value; + } catch (error) { + throwCommandContractError(error, errors); + } + }, +); +``` + +The route returns exactly its declared output. Replay metadata is available to the handler and +telemetry but does not mutate the response shape. A contract that wants to expose replay state may +declare it in its own output. + +### What a retry does + +For an idempotency key `k`: + +- the first successful request commits the business writes, audit/outbox rows, and encoded response; +- the same key plus the same canonical request returns the first response and creates nothing new; +- the same key plus a different canonical request returns `IDEMPOTENCY_KEY_REUSE`; +- a concurrent follower either waits and replays, proceeds after the winner rolls back, or receives + the retryable `COMMAND_IN_PROGRESS` result when the adapter's bounded wait expires; and +- a failure after commit but before the HTTP response is repaired by the next retry, because receipt + completion was part of the commit. + +An optional-idempotency command may explicitly accept no key. That attempt remains atomic but is not +replay protected; telemetry records `not_requested`. Required-by-default versus permitting this +explicit opt-out is an FCP question. + +### Where the relay and saga begin + +An outbox row says that delivery is owed; it does not say delivery has happened. The generated relay +claims committed rows with leases, publishes each row's stable message ID, then marks it published. +If it publishes and crashes before marking, it publishes the same ID again. A worker, saga handler, +stream consumer, or webhook receiver must deduplicate or be idempotent. + +The command transaction ends before relay publication. A saga begins when later work needs multiple +durable steps, remote calls, time, retries, or compensation. `sagaCompensate()` is not called inside +the command transaction; the outbox can request a saga start after the local commit. + +## Reference-level explanation + +### Scope and terminology + +- **command** — one validated intent applied by one handler inside one supported store transaction; +- **attempt** — one invocation of that handler; the executor never repeats it automatically; +- **receipt key** — command scope, command name, and the SHA-256 digest of an idempotency key; +- **request hash** — SHA-256 of exact RFC-8785 canonical request material; +- **audit record** — immutable same-commit operational evidence supplied by the application; +- **outbox message** — same-commit remote-delivery intent with a stable message ID; +- **relay** — post-commit lease/publish/mark runtime with at-least-once delivery; +- **conformant store** — one that binds all local rows to the same transaction and passes the + injected-failure suite; and +- **refusal** — a typed failure before an unsupported guarantee is claimed. + +“Same commit” refers only to rows in the transaction's store. “Applied” means that transaction +committed. It does not mean a remote consumer observed the message. The RFC makes no end-to-end +exactly-once claim. + +### Package and export placement + +| Surface | Owner | Reason | +| -------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `@netscript/service/commands` | command definition, executor, envelope, context, typed application failures | Commands are a service/application concern. A focused subpath preserves the root export budget. | +| `@netscript/service/commands/relay` | relay supervisor, decoded delivery value, sink port, registry, shutdown handle | Runtime delivery policy consumes database persistence; it does not leak service value types into database. | +| `@netscript/service/commands/testing` | executor and relay fixtures plus fault controls | Testing-only public utilities stay off production roots. | +| `@netscript/database/commands` | transaction-bound command-store and relay-store persistence contracts | Database owns transaction, isolation, raw persisted-row, claim-token, and release vocabulary. It imports no service type. | +| `@netscript/database/commands/adapters/` | PostgreSQL/MySQL/SQL Server command and relay store adapters | Technology-specific adapters remain behind database-owned ports and provider subpaths. | +| `@netscript/database/commands/testing` | command-store and relay-store adapter conformance suites | Provider truth is tested at its owning adapter seam. | +| `@netscript/contracts/commands` | opt-in Zod schemas and command error map | Transport errors remain contract-first without changing all routes. | +| `@netscript/telemetry/attributes` | command span/attribute constants and builder | Extends the existing telemetry vocabulary where job/saga builders already live. | + +No `@netscript/commands` package is introduced. None of these symbols is re-exported from an +existing package root in v1. Internal imports within a package use relative paths, avoiding the +self-referential JSR subpath trap. + +The dependency arrow is exactly `@netscript/service` → `@netscript/database`: the executor and relay +runtime consume database-owned ports. This is a new direct package edge. The implementation must add +a pinned `@netscript/database` entry to `packages/service/deno.json`, prove it with +`deno info`/consumer-import checks, and include it in the service publish surface. Database never +imports `CommandJson`, `CommandTraceContext`, a sink, a worker, or any other service/runtime type. + +### Value, identity, and envelope contracts + +```ts +import type { StandardSchemaV1 } from '@standard-schema/spec'; + +export type CommandJson = + | null + | boolean + | string + | number + | readonly CommandJson[] + | { readonly [key: string]: CommandJson }; + +export type CommandActor = + | Readonly<{ + kind: 'principal'; + subject: string; + scheme?: string; + }> + | Readonly<{ + kind: 'system'; + subject: string; + }>; + +export type CommandTraceContext = Readonly<{ + traceparent: string; + tracestate?: string; +}>; + +export type CommandEnvelope = Readonly<{ + input: TInput; + actor: CommandActor; + correlationId: string; + trace?: CommandTraceContext; + idempotencyKey?: string; + expectedVersion?: string; +}>; + +export interface CommandCodec { + encode(value: T): CommandJson; + decode(value: CommandJson): T; +} + +export function jsonCodec( + schema: StandardSchemaV1, +): CommandCodec; +``` + +`CommandJson` is a static convenience, not sufficient validation by itself. Codec output must be +I-JSON suitable for RFC 8785: object keys are strings; numbers are finite IEEE-754 values; lone +surrogates, `undefined`, sparse arrays, `Date`, `BigInt`, functions, symbols, class instances, and +cyclic values are rejected. Stored values are decoded and validated again; a database row is not +trusted merely because the framework wrote it. + +`actor.subject` reuses the authenticated principal identity, while roles, scopes, and claims do not +cross the durable command boundary. Authorization happens before execution. `scheme` may be stored +in audit evidence but is excluded from request identity so a credential mechanism change does not +turn a retry into another request. + +`expectedVersion` is a string. Consumers normalize integer, UUID, timestamp, or opaque version +tokens at their repository boundary rather than risk JavaScript numeric precision or a second +portable version algebra. + +`jsonCodec()` accepts the package-neutral `StandardSchemaV1` contract. It does not expose Zod in the +service public surface. + +### Command definition and execution contracts + +```ts +export type CommandIdempotencyMode = 'required' | 'optional'; +export type CommandRecordRequirement = 'required' | 'optional' | 'forbidden'; + +export type CommandIdempotency = Readonly<{ + mode: CommandIdempotencyMode; + scope(identity: Readonly<{ input: TInput; actor: CommandActor }>): string; + fingerprint(input: TInput): CommandJson; + response: CommandCodec; +}>; + +export type CommandDefinitionSpec< + TName extends string, + TInput, + TOutput, + TTx, +> = Readonly<{ + name: TName; + definitionVersion: number; + isolationLevel?: IsolationLevel; + idempotency: CommandIdempotency; + records: Readonly<{ + audit: CommandRecordRequirement; + outbox: CommandRecordRequirement; + }>; + handle(context: CommandContext): Promise; +}>; + +export interface CommandDefinition< + TName extends string, + TInput, + TOutput, + TTx, +> { + readonly name: TName; + readonly definitionVersion: number; + readonly isolationLevel?: IsolationLevel; + readonly idempotency: CommandIdempotency; + readonly records: Readonly<{ + audit: CommandRecordRequirement; + outbox: CommandRecordRequirement; + }>; +} + +export type CommandAuditInput = Readonly<{ + action: string; + subject: Readonly<{ type: string; id: string }>; + data?: CommandJson; +}>; + +export type CommandOutboxInput = Readonly<{ + destination: string; + topic: string; + payload: TPayload; + codec: CommandCodec; + dedupeKey?: string; + availableAt?: Date; +}>; + +export interface CommandContext { + readonly tx: TTx; + readonly envelope: CommandEnvelope; + readonly signal: AbortSignal; + now(): Date; + newId(): string; + audit(record: CommandAuditInput): void; + publish(message: CommandOutboxInput): void; + conflict(): never; +} + +export type CommandExecution = Readonly<{ + value: TOutput; + outcome: 'applied' | 'replayed'; + idempotency: 'claimed' | 'replayed' | 'not_requested'; + correlationId: string; +}>; + +export interface CommandExecutor { + execute( + command: CommandDefinition, + envelope: CommandEnvelope, + options?: Readonly<{ signal?: AbortSignal }>, + ): Promise>; +} + +export interface CommandClock { + now(): Date; +} + +export interface CommandIdSource { + next(): string; +} + +export type CommandTelemetryStart = Readonly<{ + name: string; + definitionVersion: number; + isolation: IsolationLevel | 'default'; + provider: string; + idempotency: 'claimed' | 'not_requested'; +}>; + +export type CommandTelemetryResult = Readonly<{ + outcome: 'applied' | 'replayed' | 'conflict' | 'rejected' | 'failed' | 'cancelled'; + idempotency: 'claimed' | 'replayed' | 'not_requested' | 'missing' | 'mismatch' | 'busy'; + auditCount: number; + outboxCount: number; + errorType?: CommandFailure['kind']; +}>; + +export interface CommandTelemetrySpan { + finish(result: CommandTelemetryResult): void; +} + +export interface CommandTelemetryPort { + trace( + start: CommandTelemetryStart, + operation: (span: CommandTelemetrySpan) => Promise, + ): Promise; +} + +export type CommandExecutorOptions = Readonly<{ + store: CommandStorePort; + clock?: CommandClock; + ids?: CommandIdSource; + telemetry?: CommandTelemetryPort; + receiptClaimWaitMs?: number; + limits?: Readonly<{ + auditRecords: number; + outboxRecords: number; + recordBytes: number; + }>; +}>; + +export function defineCommand< + TName extends string, + TInput, + TOutput, + TTx, +>( + specification: CommandDefinitionSpec, +): CommandDefinition; + +export function createCommandExecutor( + options: CommandExecutorOptions, +): CommandExecutor; +``` + +`defineCommand()` validates names and policies once and returns an immutable definition. Command +names are 1–120 characters and match `^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$`; `definitionVersion` is a +positive safe integer. Names and versions are durable protocol data and cannot be changed as a +refactor. + +`now()` and `newId()` use executor-injected ports. `audit()` and `publish()` are synchronous +recorders; they do not write or call a transport. The executor enforces the declared required, +optional, or forbidden record counts before flushing. It also enforces configured count/byte limits +before asking the store to write. + +The exported `CommandDefinition` is read-only and does not expose a public constructor. The actual +handler remains part of the opaque definition returned by `defineCommand()`, preventing callers from +bypassing the executor by invoking a public `handle` member. + +`receiptClaimWaitMs` is a bounded executor policy, not a caller-controlled envelope value. The +executor validates it against the store's advertised minimum, maximum, and granularity before +opening a transaction and passes the normalized value in `CommandTransactionRequest`. An adapter +must report its effective wait when its engine has coarser units (MySQL rounds up to whole seconds). + +### Transaction-bound store contracts + +The database subpath owns the transaction vocabulary and imports no service types. The business +transaction client and side-record methods are one bound handle: + +```ts +export type CommandStoreCapabilities = Readonly<{ + provider: DatabaseProvider; + transactionModel: 'interactive'; + sideRecordAtomicity: 'same_commit'; + callbackAttempts: 'one'; + cancellation: 'cooperative' | 'driver'; + selectableIsolationLevels: readonly IsolationLevel[]; + defaultIsolation: IsolationLevel | 'provider_configured'; + receiptClaimWait: Readonly<{ + minimumMs: number; + maximumMs: number; + granularityMs: number; + }>; +}>; + +export type CommandTransactionRequest = Readonly<{ + options?: TransactionOptions; + receiptClaimWaitMs: number; +}>; + +export type ReceiptClaim = Readonly<{ + id: string; + scope: string; + commandName: string; + commandVersion: number; + keyHash: string; + requestHash: string; + actorKind: 'principal' | 'system'; + actorSubject: string; + correlationId: string; + createdAt: Date; +}>; + +export type StoredCommandReceipt = Readonly<{ + id: string; + requestHash: string; + commandVersion: number; + responseJson: string; + correlationId: string; + completedAt: Date; +}>; + +export type ReceiptClaimResult = + | Readonly<{ kind: 'execute'; receiptId: string }> + | Readonly<{ kind: 'replay'; receipt: StoredCommandReceipt }> + | Readonly<{ kind: 'mismatch' }> + | Readonly<{ kind: 'busy'; retryAfterMs?: number }>; + +export type StoredCommandAudit = Readonly<{ + id: string; + executionId: string; + commandName: string; + commandVersion: number; + action: string; + subjectType: string; + subjectId: string; + actorKind: 'principal' | 'system'; + actorSubject: string; + actorScheme?: string; + correlationId: string; + dataJson?: string; + occurredAt: Date; +}>; + +export type StoredCommandOutbox = Readonly<{ + id: string; + executionId: string; + commandName: string; + commandVersion: number; + destination: string; + topic: string; + payloadJson: string; + dedupeKey: string; + correlationId: string; + traceparent?: string; + tracestate?: string; + availableAt: Date; +}>; + +export interface CommandTransaction { + readonly business: TTx; + claimReceipt( + claim: ReceiptClaim, + signal?: AbortSignal, + ): Promise; + completeReceipt( + completion: Readonly<{ + receiptId: string; + responseJson: string; + completedAt: Date; + }>, + signal?: AbortSignal, + ): Promise; + appendAudit( + records: readonly StoredCommandAudit[], + signal?: AbortSignal, + ): Promise; + appendOutbox( + messages: readonly StoredCommandOutbox[], + signal?: AbortSignal, + ): Promise; +} + +export interface CommandStorePort { + readonly capabilities: CommandStoreCapabilities; + transaction( + request: CommandTransactionRequest, + work: (transaction: CommandTransaction) => Promise, + signal?: AbortSignal, + ): Promise; +} +``` + +A `CommandStorePort` with `sideRecordAtomicity: "same_commit"` must make that a construction +invariant, not a configuration promise. Its `CommandTransaction` is created inside the provider +callback, all four delegates are derived from that callback's `TTx`, and no root client is accepted +by the side-record bridge. + +`callbackAttempts: "one"` forbids an adapter from replaying `work` after serialization, deadlock, or +busy failures. The adapter may retry connection acquisition before invoking `work`, but once invoked +the result is commit, rollback, or a surfaced failure. + +`ReceiptClaimResult.kind: "busy"` is terminal for the current callback. The executor issues no more +store query, throws its internal claim-busy sentinel, waits for `transaction()` to report rollback, +and only then exposes `CommandError({ kind: "in_progress" })`. `retryAfterMs`, when present, is the +adapter's normalized effective wait/backoff hint, not proof that the leader will finish by then. + +`cancellation: "cooperative"` means the adapter checks the signal before beginning and between +framework-controlled steps but the driver cannot interrupt an already-issued query. Such an adapter +must also enforce a transaction timeout. `"driver"` may be advertised only when the actual driver +cancels in-flight operations. Cancellation never means rollback succeeded until the transaction +boundary reports it. + +The existing `withTransaction()` cannot implement this contract verbatim: it types its callback as +the full root client through an assertion. The reference implementation must first give that helper +a true `TRoot`/`TTx` signature or call the correctly typed Prisma callback directly. Nested +`$transaction`, connection lifecycle, and other root-only methods must be absent from `TTx`. + +### Logical row contracts + +The application owns the tables and migrations. The packages own these logical field contracts: + +#### Command receipt + +| Field | Logical type | Constraint | +| ---------------- | ---------------------------- | ----------------------------------------------------------- | +| `id` | UUID/string | primary key; generated before claim | +| `scope` | bounded UTF-8 string | 1–256 bytes; application receipt namespace | +| `commandName` | bounded string | validated command name | +| `commandVersion` | positive integer | definition version that produced the receipt | +| `keyHash` | 64 lowercase hex chars | SHA-256 of the UTF-8 idempotency key; raw key is not stored | +| `requestHash` | 64 lowercase hex chars | exact canonical request hash | +| `actorKind` | `principal \| system` | durable origin class | +| `actorSubject` | bounded string | authenticated/system subject; no roles/scopes/claims | +| `correlationId` | bounded string | durable application correlation value | +| `responseJson` | nullable canonical JSON text | null only inside the uncommitted claim | +| `createdAt` | UTC timestamp | claim time from injected clock | +| `completedAt` | nullable UTC timestamp | set with `responseJson` before commit | + +There is a unique key on `(scope, commandName, keyHash)`. `responseJson` and `completedAt` must be +both null or both non-null. A committed incomplete receipt is corruption, not “in progress”; normal +concurrency cannot observe the uncommitted claim. Provider-aware generated migrations add the check +constraint where the provider supports it, while conformance tests enforce the invariant on every +adapter. + +The idempotency key is not part of `requestHash`. It selects the receipt. Including it in the hash +would add no replay protection. The raw key is never persisted, logged, or emitted to telemetry. + +#### Command audit + +| Field | Logical type | Constraint | +| -------------------------------------------- | ---------------------------- | ------------------------------------ | +| `id` | UUID/string | primary key | +| `executionId` | UUID/string | originating command attempt | +| `commandName` / `commandVersion` | string / integer | durable command identity | +| `action` | bounded string | application-owned action vocabulary | +| `subjectType` / `subjectId` | bounded strings | application-owned target identity | +| `actorKind` / `actorSubject` / `actorScheme` | bounded strings | origin evidence; scheme nullable | +| `correlationId` | bounded string | copied from the envelope | +| `dataJson` | nullable canonical JSON text | application-owned, redacted metadata | +| `occurredAt` | UTC timestamp | injected clock | + +The kit does not define a domain audit taxonomy, authorization model, legal retention policy, or +tamper-proof log. It guarantees only that a valid audit row supplied by the handler shares the +business commit. Applications restrict update/delete privileges and choose retention according to +their requirements. + +#### Command outbox + +| Field | Logical type | Constraint | +| -------------------------------- | -------------------------- | --------------------------------------------------- | +| `id` | UUID/string | stable message ID and default downstream dedupe key | +| `executionId` | UUID/string | originating command attempt | +| `commandName` / `commandVersion` | string / integer | originating command | +| `destination` | bounded string | logical sink registry key | +| `topic` | bounded string | versioned application message type | +| `payloadJson` | canonical JSON text | validated codec output | +| `dedupeKey` | bounded string | handler value or `id` by default | +| `correlationId` | bounded string | durable application correlation | +| `traceparent` / `tracestate` | nullable validated strings | W3C propagation only | +| `availableAt` | UTC timestamp | earliest delivery time | +| `attemptCount` | non-negative integer | relay attempts | +| `claimToken` / `claimUntil` | nullable string/timestamp | expiring relay lease | +| `publishedAt` | nullable UTC timestamp | set only after sink acknowledgement | +| `terminalAt` | nullable UTC timestamp | set only when retry policy is exhausted | +| `lastFailure` | nullable bounded enum | classified failure, not raw payload/stack | + +The command transaction inserts only the immutable delivery intent and initial relay fields. +Lease/attempt/publication updates happen in later relay transactions. There is no arbitrary headers +map: sink adapters construct protocol headers, validate trace context, and retrieve credentials from +runtime configuration rather than durable rows. + +Payloads and responses use canonical JSON text, not a provider `Json` scalar. That avoids assuming +identical Prisma JSON support and native types across PostgreSQL, MySQL, SQL Server, and SQLite. +Provider generators may add efficient native projections, but the portable contract is text. + +### Schema and bridge ownership + +`netscript db command-store add --database ` is the proposed explicit generator. The +sub-noun is deliberate: existing `netscript db init` creates/applies the database's initial Prisma +migration, while `db command-store add` adds one optional feature to an already configured database. +It: + +1. detects the configured Prisma provider; +2. appends provider-specific receipt/audit/outbox models using stable `NetScriptCommand*` names; +3. creates a reviewable migration, including unique/check/index definitions; +4. emits `database//command-store.ts`, which binds the generated delegates and true + transaction client to `CommandStorePort`; +5. emits and re-exports the consumer-specific transaction type shown below; and +6. refuses to overwrite edited models or run a migration without the consumer's normal DB command. + +```ts +// generated in database//command-store.ts +import type { Prisma } from './schema/.generated/client.ts'; + +export type CommandTransactionClient = Omit< + Prisma.TransactionClient, + '$transaction' | '$connect' | '$disconnect' | '$on' | '$use' | '$extends' +>; +``` + +The configured engine module re-exports `CommandTransactionClient` and `commandStore`, so the +workspace's existing `@database` facade supports the guide import. This alias is generated because +it depends on the consumer's models. A focused Prisma 7.8 probe at RFC time confirmed that the +generated client exports `Prisma.TransactionClient`; the explicit `Omit` is still required because +the current Prisma deny-list does not remove nested `$transaction`. Emitted-sample type tests prove +that model delegates are present and that every listed root/lifecycle method is rejected. The +framework packages do not invent a model-agnostic fake transaction-client export. + +The package never injects models during import, service startup, `defineService()`, or relay +startup. The generated bridge is consumer-owned, checked in, and discoverable. Model renames require +an explicit bridge update; they do not change the public store contract. + +### Canonical request identity + +Before opening the transaction, the executor builds this exact value: + +```ts +type CanonicalCommandRequest = Readonly<{ + command: string; + definitionVersion: number; + scope: string; + input: CommandJson; + actor: Readonly<{ + kind: 'principal' | 'system'; + subject: string; + }>; + expectedVersion: string | null; +}>; +``` + +The steps are normative: + +1. validate command/envelope bounds and idempotency policy; +2. call the command's `scope({ input, actor })` and `fingerprint(input)` exactly once; +3. validate the result as I-JSON; +4. serialize the complete value with RFC 8785 JCS to UTF-8; +5. compute SHA-256 with `crypto.subtle.digest("SHA-256", bytes)`; and +6. encode the digest as 64 lowercase hexadecimal characters. + +The key digest separately hashes the UTF-8 idempotency key with the same SHA-256/hex encoding. The +request hash is a consistency token, not an authenticator or password hash. It must not be used for +authorization, message signing, or proof of possession. + +`fingerprint(input)` intentionally selects the semantic command input. Transport-only values such as +the idempotency key are excluded. The command author must include every input field that can change +the effect. Negative conformance mutates each contract field and proves either that the fingerprint +changes or that the field is explicitly classified transport-only. + +`scope()` has the same purity and determinism obligation as `fingerprint()`: for the same deeply +frozen `{ input, actor }` it returns the same bounded UTF-8 string across processes, retries, and +deployments. It may use only that argument, performs no I/O, reads no clock/random/global/config +state, and excludes transport-only idempotency, trace, and correlation values. Scope partitions a +receipt namespace (for example by tenant or aggregate). The restricted argument deliberately +withholds the rest of `CommandEnvelope`. `@netscript/service/commands/testing` supplies an identity +fixture that invokes `scope()` and `fingerprint()` repeatedly with equivalent frozen values and +fails nondeterministic definitions; production execution still evaluates each exactly once. + +The unique key makes the consequence of identity drift explicit. The same raw idempotency key under +a **different scope** or **different command name** is a different receipt key and therefore +executes as new. The framework cannot label it reuse because no row exists in that namespace. This +is not a loophole to exploit: changing scope logic or renaming a command is a breaking replay +migration. Keep the old definition/name as an alias through the maximum receipt retry window, or +migrate receipt keys and prove the mapping before deploying the new identity. + +`definitionVersion` changes when request identity, response decoding, or command meaning changes. +Because the receipt unique key does not include that version, a retry crossing a deployment returns +`IDEMPOTENCY_KEY_REUSE` rather than executing a new command under the old key. This is safer than +silently applying the intent again. + +### Transaction algorithm + +For one `execute()` call: + +1. Validate command metadata, envelope bounds, actor, W3C trace context, required idempotency, store + atomicity, requested isolation, and buffer limits. Unsupported capabilities fail before the + handler. +2. Compute scope, input fingerprint, request hash, and key hash outside the transaction. Codec or + canonicalization failure is a typed non-retryable configuration/input failure. +3. Start `command.execute` as a child of the active RPC/server span when available. +4. Call `store.transaction()` once with the selected isolation/timeout and signal. +5. If a key is present, call `claimReceipt()`: + - `mismatch` becomes `idempotency_key_reuse` without running the handler; + - `busy` becomes `in_progress`; + - `replay` validates the stored hash/version, parses and decodes `responseJson`, and returns + without business, audit, or outbox writes; and + - `execute` continues with its receipt ID. +6. Invoke the handler exactly once with `transaction.business` and empty in-memory buffers. +7. Enforce required/optional/forbidden record policies and count/byte limits. +8. Encode the response, validate audit/outbox values, create one attempt `executionId` plus stable + record IDs, and canonicalize their JSON text. For a keyed attempt, the receipt ID is the + `executionId`; an unkeyed optional attempt still receives an `executionId` for its side records. +9. Append audit rows, append outbox rows, and complete the receipt, in that order, through the bound + transaction. A no-key optional attempt skips claim/completion. +10. Return from the transaction callback and wait for the adapter's commit result. +11. Record `applied` or `replayed` telemetry and return the decoded value. + +Any exception or cancellation before the successful commit rolls back all transaction writes. A +fault after commit but before step 11 may lose the response to that call; retry with the same key +loads the committed receipt. No post-commit “repair write” exists for receipts, audit, or outbox. + +The order in step 9 aids deterministic fault injection but does not create partial visibility: every +append and completion still belongs to the same transaction. A business error thrown by the handler +remains the handler's declared error; the executor does not wrap arbitrary application errors as +storage failures. + +### Normative receipt-claim algorithms + +The generated unique key is the concurrency primitive; adapters do not run an unprotected +select-then-insert and do not retry the transaction callback. `claimReceipt()` is the first +persistence operation in the callback. Every adapter implements the following state machine: + +1. attempt one provider-safe claim for `(scope, commandName, keyHash)` with the bounded claim wait; +2. if this transaction inserted the row, return `execute`; +3. if a committed row won, load it and return `replay` or `mismatch` after comparing + `requestHash`/`commandVersion` and completeness; and +4. if lock acquisition times out, terminate the callback, roll back the whole transaction, and map + the provider error to `busy`. No query is issued in that transaction after `busy`. + +Unexpected constraint/data errors are never treated as duplicates. Serialization/deadlock victims +surface as retryable `store_failure`; `callbackAttempts: "one"` remains true. At stronger isolation, +a provider may choose a serialization victim rather than a replay winner, and the caller retries the +whole request with the same key. + +Provider mechanics are normative for v1: + +| Provider | Claim statement/lock | Bounded-wait mechanics | Leader commits | Leader rolls back | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| PostgreSQL | `INSERT ... ON CONFLICT (scope, command_name, key_hash) DO NOTHING RETURNING id`; empty `RETURNING` is followed by one indexed `SELECT` | Save `current_setting('lock_timeout')`; call parameterized `set_config('lock_timeout', 'ms', true)` before the insert and restore the saved value after a successful claim path. SQLSTATE `55P03` becomes `busy` and the outer transaction rolls back. | The insert waits, returns no row, and the next `READ COMMITTED` statement loads the winner. At `REPEATABLE READ`/`SERIALIZABLE`, a legitimate `40001` is a retryable store failure. | The waiting insert proceeds and returns its generated receipt ID, so the follower executes. | +| MySQL/InnoDB | `SAVEPOINT netscript_receipt_claim`; plain `INSERT`; only error `1062` rolls back to/releases that savepoint and performs the indexed `SELECT`. `INSERT IGNORE` is forbidden because it can downgrade non-duplicate data errors to warnings. | On the leased connection, read `@@SESSION.innodb_lock_wait_timeout`, set the validated `ceil(ms / 1000)` whole-second value before claim, and restore it in connection `finally` after commit/rollback. Error `1205` becomes `busy`; error `1213` is a retryable store failure. The outer transaction always rolls back after either, independent of `innodb_rollback_on_timeout`. A failed restore discards the pooled connection. | The waiting insert receives `1062`; savepoint recovery leaves the transaction usable and the indexed select loads the winner. | The waiting insert succeeds and the follower executes. | +| SQL Server | First select the exact generated unique index with `WITH (UPDLOCK, HOLDLOCK, INDEX([UX_NetScriptCommandReceipt_scope_command_key]))`; return replay/mismatch when found, otherwise insert while the serializable key-range update lock is held. `MERGE` and catch-and-continue on 2601/2627 are not used. | Read `@@LOCK_TIMEOUT`, issue `SET LOCK_TIMEOUT ` on the leased connection, and restore the prior integer after commit/rollback. Error `1222` becomes `busy`; deadlock victim `1205` is a retryable store failure. A failed restore discards the pooled connection. | The follower's range-lock read resumes and observes the committed row. | The follower's range-lock read resumes, observes no row, inserts, and executes. | + +All identifiers in these statements come from generated, provider-quoted schema constants. Values +are bound parameters. The timeout literals are adapter-created decimal integers after range +validation; caller text is never interpolated. PostgreSQL's transaction-local setting resets on +rollback. MySQL and SQL Server settings are session-scoped, so acquiring one connection for the +whole transaction and restoring/discarding it before pool return is part of conformance, not an +optimization. + +The PostgreSQL follow-up select is intentionally tied to `READ COMMITTED` replay behavior. At a +stronger snapshot level, a serialization outcome is safer than executing against an invisible +winner. The SQL Server query's `HOLDLOCK` gives the receipt lookup serializable range semantics even +when the surrounding command uses another advertised isolation; `UPDLOCK` also forces locking under +snapshot modes. Generated migrations must keep the named composite unique index that makes the +singleton/range lock possible. + +### Semantic laws + +Every conforming implementation and adapter satisfies these laws: + +1. **Atomic rollback law.** A fault before commit leaves no business-state delta, completed receipt, + audit row, or outbox row. +2. **Same-commit law.** If any one local row from a successful attempt is visible, all local rows + from that attempt are visible. +3. **Replay law.** Same receipt key and request hash returns the first decoded response, sets + `outcome = replayed`, and produces no new business/audit/outbox rows. +4. **Key-reuse law.** Same receipt key and different request hash never invokes the handler and + returns the typed non-retryable key-reuse failure. +5. **Concurrent-claim law.** Of concurrent identical attempts, at most one handler commits. A + follower replays, runs only after the leader rolled back, or receives a bounded retryable busy + failure. +6. **Receipt-completion law.** A committed receipt is complete and decodable. An incomplete or + invalid committed receipt is corruption and never causes handler execution. +7. **CAS law.** A command requiring optimistic concurrency performs the version predicate in the + mutation. Zero matched rows becomes a conflict and rolls back every buffered record. +8. **No-hidden-retry law.** One executor call invokes the handler no more than once. +9. **Isolation-refusal law.** A requested level absent from `selectableIsolationLevels` is refused + before opening the transaction. +10. **Stable-message law.** A committed outbox row retains one `id`/`dedupeKey` across every relay + claim, lease expiry, retry, and redelivery. +11. **At-least-once relay law.** A successful sink acknowledgement eventually permits `publishedAt`, + but publish-before-crash may result in the same message being observed again. +12. **Boundary law.** No SQL+KV, SQL+stream, SQL+HTTP, two-database, or other cross-store operation + is represented as one command transaction. +13. **Deterministic-identity law.** Equivalent frozen identity values produce identical scope and + fingerprint results. A changed scope or command name is explicitly a new receipt namespace and + executes as new; a changed definition version under the same receipt key is a mismatch. + +“At most one handler commits” is conditional on a supplied idempotency key. An optional attempt +without one retains only the atomic rollback and same-commit laws. + +### Optimistic concurrency and isolation + +The executor propagates `expectedVersion` and supplies `ctx.conflict()`. It cannot manufacture a +portable conditional mutation because only the application repository knows the model, key, version +column, and write set. + +The supported pattern is one provider query whose predicate includes the expected version and whose +result proves whether it matched. A preliminary read may be useful for business decisions but never +replaces the conditional write. The proposal's `expectVersion(current)` is rejected because another +transaction can write between comparison and mutation. + +Isolation is a set, not a scalar “maximum.” `selectableIsolationLevels` means levels the Prisma +transaction API and configured adapter accept explicitly; `defaultIsolation` separately describes +the omitted-option path. The command declares the concrete selectable level its algorithm was tested +with. Omitting `isolationLevel` uses the database default and records `default` in command +telemetry; it does not claim a portable effective level when the capability says +`provider_configured`. Commands maintaining multi-row invariants normally require `Serializable` or +another provider-specific locking design and must test serialization/deadlock outcomes. + +The executor does not automatically retry serialization, deadlock, or SQLite busy failures. Those +become retryable store outcomes. A caller can retry the entire request with the same key, which is +safe whether the original attempt rolled back or committed before its response was lost. + +### Adapter capability matrix + +“Engine feasible” and “currently integrated by NetScript” are deliberately separate: + +| Store | Engine/Prisma transaction facts | Capability truth required by this RFC | RFC position | +| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| PostgreSQL + Prisma | interactive transactions; `ReadUncommitted` maps to `ReadCommitted`; `ReadCommitted`, `RepeatableRead`, `Serializable` available | selectable set omits `Snapshot`; provider/configured default recorded separately | reference v1 adapter after full conformance | +| MySQL + Prisma | interactive; `ReadUncommitted`, `ReadCommitted`, `RepeatableRead`, `Serializable`; no `Snapshot` | exactly those four selectable values. Current lower adapter's exported `SNAPSHOT` and unchecked SQL interpolation must be fixed before conformance | conforming v1 target; #1293 remains adjacent, not this isolation defect | +| SQL Server + Prisma | interactive; all five current `IsolationLevel` values; `Snapshot` requires database enablement | selectable set includes `Snapshot` only after an enablement probe; otherwise the four lock-based levels | conforming v1 target after real configuration/conformance | +| SQLite + Prisma | engine default is serializable; current generated/Prisma surface does not provide a portable explicit isolation-selection path | future adapter advertises `selectableIsolationLevels: []`, `defaultIsolation: 'Serializable'`; explicit `Serializable` is still refused | default-only conformance and release timing remain FCP Q2; no current support claim | +| Deno KV | optimistic atomic checks/mutations with operation limits; no interactive callback | cannot advertise interactive/same-commit SQL capabilities | not a v1 `CommandStorePort`; no weaker command mode | +| two SQL stores or SQL + KV/stream/HTTP | no portable shared commit | no capability can make the commits shared | refused; commit an outbox intent and continue through a consumer/saga | + +Same-commit audit/outbox feasibility follows from using tables in the same database and the exact +transaction client. It does not follow from the provider name alone. Each provider becomes +“supported” only when its generated schema, transaction bridge, contention behavior, fault suite, +doc lint, publish gates, and consumer test pass. + +The current database helper's `IsolationLevel` vocabulary is reused. The RFC does not introduce a +second enum. Provider adapters publish the exact **selectable** subset and separate default truth. +Stage 6 cannot pass while `packages/prisma-adapter-mysql` exports `SNAPSHOT` or interpolates an +unchecked upstream isolation token: it must use a four-value allow-list/mapping before SQL, remove +`SNAPSHOT` from `PrismaMySqlIsolationLevel`, reject it in positive/negative type/runtime fixtures, +and prove all four supported values on a real server. This is a distinct prerequisite from #1293. + +### Typed failure model + +The service subpath exposes a discriminated failure payload on `CommandError`: + +```ts +export type CommandFailure = + | Readonly<{ + kind: 'invalid_envelope'; + retryable: false; + reason: + | 'actor' + | 'correlation' + | 'idempotency_required' + | 'trace_context'; + }> + | Readonly<{ + kind: 'optimistic_conflict'; + retryable: false; + }> + | Readonly<{ + kind: 'idempotency_key_reuse'; + retryable: false; + }> + | Readonly<{ + kind: 'in_progress'; + retryable: true; + retryAfterMs?: number; + }> + | Readonly<{ + kind: 'unsupported_capability'; + retryable: false; + capability: 'store_atomicity' | 'isolation' | 'transaction_model'; + }> + | Readonly<{ + kind: 'codec_failure'; + retryable: false; + phase: 'fingerprint' | 'response_encode' | 'response_decode' | 'side_record'; + }> + | Readonly<{ + kind: 'receipt_corrupt'; + retryable: false; + }> + | Readonly<{ + kind: 'store_failure'; + retryable: boolean; + phase: 'begin' | 'claim' | 'business' | 'flush' | 'complete' | 'commit'; + }> + | Readonly<{ + kind: 'aborted'; + retryable: true; + }>; + +export class CommandError extends Error { + readonly failure: CommandFailure; + + constructor(failure: CommandFailure, options?: ErrorOptions); +} +``` + +Driver codes, SQL text, payloads, actor IDs, scope, hashes, and stored response text are never +placed in the public failure. Adapters classify known serialization/deadlock/busy/timeout failures +behind `store_failure` while retaining the original error as a non-serialized `cause` for trusted +logs. + +Application/business errors thrown by the handler are outside this union and preserve their own +contract mapping. `ctx.conflict()` is the one convenience constructor because optimistic conflicts +are part of command composition rather than a domain-specific taxonomy. + +The opt-in contract fragment is: + +```ts +export const commandErrorMap = { + COMMAND_CONFLICT: { + status: 409, + message: 'The command no longer matches current state', + data: z.object({ + kind: z.literal('optimistic_conflict'), + retryable: z.literal(false), + }), + }, + IDEMPOTENCY_KEY_REUSE: { + status: 409, + message: 'The idempotency key was used for another request', + data: z.object({ + kind: z.literal('idempotency_key_reuse'), + retryable: z.literal(false), + }), + }, + COMMAND_IN_PROGRESS: { + status: 409, + message: 'An identical command is still in progress', + data: z.object({ + kind: z.literal('in_progress'), + retryable: z.literal(true), + retryAfterMs: z.number().int().nonnegative().optional(), + }), + }, +} as const satisfies ErrorMap; + +export type CommandContractErrors = MergedErrorMap< + BaseContractErrors, + typeof commandErrorMap +>; + +export const commandBaseContract: ContractBuilder< + Schema, + Schema, + CommandContractErrors, + Record +> = baseContract.errors(commandErrorMap); + +export type CommandContractRoute< + TInput extends AnySchema, + TOutput extends AnySchema, +> = ContractProcedureBuilderWithInputOutput< + TInput, + TOutput, + CommandContractErrors, + Record +>; + +export interface CommandErrorConstructors { + COMMAND_CONFLICT(options: { + message?: string; + data: { kind: 'optimistic_conflict'; retryable: false }; + }): unknown; + IDEMPOTENCY_KEY_REUSE(options: { + message?: string; + data: { kind: 'idempotency_key_reuse'; retryable: false }; + }): unknown; + COMMAND_IN_PROGRESS(options: { + message?: string; + data: { + kind: 'in_progress'; + retryable: true; + retryAfterMs?: number; + }; + }): unknown; +} + +export function throwCommandContractError( + error: unknown, + errors: CommandErrorConstructors, +): never; +``` + +Invalid envelopes map to existing validation handling. Unsupported capability is a deployment or +composition error and should normally fail startup/registration, not reach a client. Codec, +corruption, and unexpected store failures map through existing internal/service-unavailable +handling. That division keeps safe client action separate from operational detail. + +The client-visible typed-error path cannot ship honestly until #1350 preserves the error generic +through `baseContract`, the service client, `safe()`, and `isDefinedError()`. Runtime executor/store +work may be developed behind an unexported seam, but the command subpaths and docs are not released +as complete until the positive and negative #1350 type fixtures pass. + +The annotation above deliberately repeats #1350's literal-preserving form instead of +`ReturnType` or `ReturnType`. Stage 0 must first make +the base spelling equivalent to: + +```ts +export type BaseContractErrors = MergedErrorMap< + Record, + typeof commonErrorMap +>; + +export const baseContract: ContractBuilder< + Schema, + Schema, + BaseContractErrors, + Record +> = oc.errors(commonErrorMap); +``` + +The command subpath then imports that exported `BaseContractErrors` and uses the same four generic +positions. Type fixtures must prove the three command codes and six base codes narrow precisely and +that an undeclared code is rejected; an open `ErrorMap` index signature is a release blocker. + +### Relay, workers, sagas, and remote sinks + +Relay persistence stays in the database package. These types contain only raw persisted values and +database-owned state vocabulary: + +```ts +// @netscript/database/commands +export type CommandRelayFailureClass = + | 'rejected' + | 'rate_limited' + | 'unavailable' + | 'timeout' + | 'invalid_response' + | 'misconfigured'; + +export type ClaimedCommandOutboxRow = Readonly<{ + id: string; + executionId: string; + destination: string; + topic: string; + payloadJson: string; + dedupeKey: string; + correlationId: string; + traceparent?: string; + tracestate?: string; + attemptCount: number; + claimToken: string; + claimUntil: Date; +}>; + +export type CommandOutboxRelease = + & Readonly<{ + id: string; + claimToken: string; + failure: CommandRelayFailureClass; + }> + & ( + | Readonly<{ disposition: 'retry'; retryAt: Date }> + | Readonly<{ disposition: 'terminal'; terminalAt: Date }> + ); + +export interface CommandOutboxRelayStore { + claim( + request: Readonly<{ + limit: number; + leaseMs: number; + now: Date; + }>, + signal?: AbortSignal, + ): Promise; + markPublished( + request: Readonly<{ + id: string; + claimToken: string; + publishedAt: Date; + }>, + signal?: AbortSignal, + ): Promise; + release( + request: CommandOutboxRelease, + signal?: AbortSignal, + ): Promise; +} +``` + +The service relay subpath owns decoding, runtime policy, sinks, and lifecycle: + +```ts +// @netscript/service/commands/relay +import type { + ClaimedCommandOutboxRow, + CommandOutboxRelayStore, + CommandRelayFailureClass, +} from '@netscript/database/commands'; + +export type CommandOutboxDelivery = Readonly<{ + id: string; + destination: string; + topic: string; + payload: CommandJson; + dedupeKey: string; + correlationId: string; + trace?: CommandTraceContext; +}>; + +export interface CommandOutboxSink { + readonly id: string; + publish( + message: CommandOutboxDelivery, + signal?: AbortSignal, + ): Promise; +} + +export type CommandOutboxRelayOptions = Readonly<{ + store: CommandOutboxRelayStore; + sinks: ReadonlyMap; + clock: CommandClock; + ids: CommandIdSource; + batchSize: number; + leaseMs: number; + maxAttempts: number; + classify(error: unknown): CommandRelayFailureClass; + retryAt(attempt: number, now: Date): Date; +}>; + +export interface RunningCommandOutboxRelay { + drainOnce(signal?: AbortSignal): Promise; + stop(): Promise; +} + +export function createCommandOutboxRelay( + options: CommandOutboxRelayOptions, +): RunningCommandOutboxRelay; +``` + +`@netscript/service/commands/relay` parses `payloadJson` with the command I-JSON limits, validates +W3C fields, constructs `CommandOutboxDelivery`, resolves the configured sink, and maps sink failure +to the bounded database-owned release class. `@netscript/database/commands` never sees decoded +`CommandJson` or `CommandTraceContext`; it leases and settles rows. This keeps the only +cross-package edge `service → database` and gives the database adapter no worker/saga/service +dependency. + +Claims are bounded and leased. `markPublished` and `release` compare the claim token so an expired +worker cannot acknowledge another worker's lease. Provider adapters choose their correct locking +algorithm; the public port does not pretend `FOR UPDATE SKIP LOCKED` is portable SQL. + +`claim()` returns only rows that are unpublished, non-terminal, due, and either unclaimed or have an +expired lease. A retry release clears the lease and advances `availableAt`; a terminal release +clears the lease and sets `terminalAt`. Neither path changes the stable message ID or dedupe key. +Relay configuration owns bounded attempts and backoff; the row stores only the resulting state. + +The explicit generator emits a visible `defineJob("netscript.command-outbox-relay")` drain job and a +sink registry. Deleting or disabling that job visibly stops delivery without affecting command +commits. The job's own worker idempotency governs a job delivery, not an outbox row's remote effect, +and therefore does not replace row leases or stable message IDs. + +Stage 5 must also correct `WorkerIdempotencyPort`'s current “exactly-once-effective” doc comment. +Its claim → effect → mark/release window provides at-least-once delivery with an applied-key guard; +a crash after an external effect and before `markApplied()` can repeat that effect. Worker docs must +use that wording before they are cited by command/relay guidance. + +Sink rules: + +- **worker job:** set the queue/message idempotency or deduplication key to outbox `dedupeKey` and + propagate `traceparent`/`tracestate`; +- **saga:** publish a saga message only after local commit, with the outbox ID as idempotency input; + the saga handler still guards redelivery and owns later compensation; +- **stream:** use the stable producer/message identity supported by the stream and keep the consumer + idempotent; +- **HTTP/webhook:** #1364's recipe signs at publish time using runtime secrets and sends the stable + outbox ID as the receiver's idempotency key. Secrets and signatures are not stored in the row. + +A sink acknowledgement means only that the sink's documented acceptance boundary succeeded. The +relay marks the row afterward. A crash between those actions causes redelivery. Marking before +publish is forbidden because it can lose a message permanently. + +The existing saga outbox port is not merged into this port. It is reserved for atomic saga state and +cascade persistence and lacks this command transaction binding. Future implementation may share +private lease utilities after both contracts exist, but their public semantics remain independently +owned. + +#### Decision: do not wrap `@netscript/queue` in v1 + +The current PostgreSQL queue adapter is valuable prior art but is not a compatible relay port. Its +public `MessageQueue.listen()` owns the loop and exposes handler `ack()`/`nack()`; its PostgreSQL +adapter claims with `FOR UPDATE SKIP LOCKED`, deletes on acknowledgement, moves exhausted messages +to a queue-specific dead-letter store, and hides claim tokens. The command outbox must retain the +original row, compare an expiring token on mark/release, preserve `publishedAt`/`terminalAt`, and +let the service sink registry own publication. More importantly, `ensureClient()` currently calls +`ensureSchema()` and executes runtime `CREATE TABLE IF NOT EXISTS`, so wrapping it would import the +hidden-migration behavior this RFC forbids. The transactional insert side also cannot call queue +enqueue because it must use the business transaction's `TTx`. + +V1 therefore rejects a direct `@netscript/queue` dependency or adapter wrapper. It reuses the proven +algorithmic pattern—bounded `FOR UPDATE SKIP LOCKED` claims, visibility leases, redelivery, attempt +limits, terminal handling, and the corresponding contention tests—behind the different +`CommandOutboxRelayStore` contract. This is reuse of verified behavior and test cases, not a false +type adapter between different durability semantics. + +The no-hidden-migration law is normative for every new command-kit/relay path. The queue package's +existing runtime DDL is acknowledged baseline drift, not precedent and not silently widened into +this RFC. Before any future implementation sharing is proposed, a separate queue reconciliation +slice must externalize its schema/migration lifecycle, retain queue compatibility, and expose a +schema-agnostic lease seam with no runtime DDL. This RFC PR does not create that issue; the board +proposal below names it for maintainer filing. Until that prerequisite lands, the packages may share +conformance cases and SQL design only, not code or a dependency edge. + +### Precise refusal boundary + +The kit refuses these shapes: + +- business rows and receipt/audit/outbox rows cannot be written through the same `TTx`; +- a handler requires an isolation level the configured adapter cannot honor; +- one operation must commit state in two databases, SQL and KV, or any other two stores; +- success requires a remote response before the local transaction can commit; +- a network call, queue publish, saga publish, file write, or process invocation is placed in the + handler's transactional effect path; +- a provider offers only claim → effect → mark semantics rather than a replay receipt in the same + store; or +- a response/payload/fingerprint cannot be represented by the declared codec as canonical I-JSON. + +The remedy is not a “best effort” capability flag. Redesign the local command to commit an intent, +then use a worker or saga for later work. If later work fails, its retry/compensation is a runtime +concern; it cannot roll back the already committed command. + +### Security and privacy + +#### Identity and authorization + +Authentication and authorization complete before `execute()`. The command actor is a narrowed copy, +not a mutable reference to all principal claims. A missing authenticated principal on a +principal-required route is an envelope failure. Background/system commands use an explicit, +allow-listed system subject and do not forge `kind: "principal"`. + +Actor subject, system subject, scope, and correlation values are bounded and validated before any +query. None is interpolated into SQL, topic names, log templates, or telemetry attribute keys. +Correlation is an observability value, never an authorization credential. + +#### Idempotency and canonicalization + +Callers should generate at least 128 bits of idempotency entropy. Keys are 16–256 UTF-8 bytes, +transported only over authenticated/encrypted channels, hashed before storage, and never accepted as +proof of identity. Low-entropy keys remain vulnerable to guessing even when hashed; the digest is +data minimization, not password hardening. + +Fingerprint and response codecs impose depth, item-count, and byte limits before canonicalization. +Non-finite numbers and non-I-JSON values are rejected. Parsing a stored response repeats these +limits to avoid treating the database as trusted input. + +#### Audit/outbox data + +Applications put only the minimum operational data into `data` and `payload`. Credentials, session +tokens, raw authorization claims, encryption keys, and unredacted request bodies are forbidden. +Storage encryption, backups, retention, erasure, and row-level access remain deployment concerns. +The command DB role may insert audit/outbox rows; relay roles may lease/update outbox rows but +should not mutate business/audit/receipt tables. + +The sink registry is configured code, not a destination URL from an outbox row. That prevents an +untrusted command payload from choosing an arbitrary host. HTTP headers are constructed from an +allow-list, with CR/LF and W3C trace-context validation. + +#### Resource exhaustion + +The executor enforces maximum records, canonical bytes, string lengths, and transaction timeout. The +relay enforces batch size, lease duration, maximum attempts, backoff, and cancellation. A +permanently failing row moves to an explicitly configured terminal/dead-letter state; it is never +silently marked published. + +### OpenTelemetry vocabulary + +V1 adds these constants/builders to `@netscript/telemetry/attributes`: + +```ts +export const CommandSpanNames = { + EXECUTE: 'command.execute', + OUTBOX_RELAY: 'command.outbox.relay', + OUTBOX_PUBLISH: 'command.outbox.publish', +} as const; + +export const CommandAttributes = { + NAME: 'netscript.command.name', + DEFINITION_VERSION: 'netscript.command.definition.version', + OUTCOME: 'netscript.command.outcome', + IDEMPOTENCY: 'netscript.command.idempotency', + ISOLATION: 'netscript.command.isolation', + STORE_PROVIDER: 'netscript.command.store.provider', + AUDIT_COUNT: 'netscript.command.audit.count', + OUTBOX_COUNT: 'netscript.command.outbox.count', +} as const; +``` + +Allowed values are closed: + +| Attribute | Values / bound | Default | +| -------------------------------------- | --------------------------------------------------------------------- | ---------------------------- | +| `netscript.command.name` | registered definition name, max 120 | required | +| `netscript.command.definition.version` | positive integer | required | +| `netscript.command.outcome` | `applied`, `replayed`, `conflict`, `rejected`, `failed`, `cancelled` | required | +| `netscript.command.idempotency` | `claimed`, `replayed`, `not_requested`, `missing`, `mismatch`, `busy` | required | +| `netscript.command.isolation` | requested enum value or `default` | required | +| `netscript.command.store.provider` | adapter's bounded provider ID | required | +| audit/outbox counts | non-negative bounded integers | on successful handler/replay | + +The command execution span is `INTERNAL` beneath the active RPC/server span. Relay publish is a +`PRODUCER` span; a later worker/saga/stream creates its normal consumer span using propagated W3C +context or a span link where deferred/batch semantics require it. + +Default command telemetry must not contain raw scope, actor/system subject, idempotency key or +digest, request hash, expected/actual version, receipt/outbox/audit IDs, payload/response data, +arbitrary topic/destination, or correlation ID. Trace IDs already exist in span context. Application +logs may include a validated correlation ID under their redaction/access policy; telemetry +inclusion, if ever allowed, is an explicit opt-in FCP policy. + +This is intentionally stricter than today's neighboring vocabulary. Messaging currently emits +`netscript.correlation.id`, and saga telemetry defines `netscript.idempotency.key`. That creates an +operator-experience asymmetry: a cross-runtime query cannot assume the same identifiers are present +on command spans. FCP Q3 must choose whether command telemetry remains privacy-first and a separate +telemetry cleanup deprecates/redacts those existing attributes, or whether a bounded opt-in policy +is proven for all three domains. Existing emission is not sufficient precedent for putting raw +command keys or unbounded correlation values into spans. + +Metrics may count executions and record duration by command name, outcome, idempotency state, and +provider only. No unbounded identifier becomes a metric dimension. Errors record `error.type` as the +stable failure kind, not a driver message or stack-derived value. + +### Injected-failure conformance + +`@netscript/service/commands/testing` exposes a test-only `CommandFaultController`. Production +constructors do not accept it. The canonical named seams are: + +| Seam | Expected invariant | +| ---------------------------- | -------------------------------------------------------------- | +| `before_transaction` | no store call and no rows | +| `after_claim` | claim rolls back; retry may execute | +| `after_handler` | business writes roll back; no side rows/receipt | +| `after_audit` | business and audit roll back; no outbox/receipt | +| `after_outbox` | business/audit/outbox roll back; no completed receipt | +| `after_receipt_complete` | every write rolls back because commit has not happened | +| `after_commit_before_return` | all rows exist once; same-key retry replays | +| `relay_after_claim` | lease expires/releases; message remains unpublished | +| `relay_after_publish` | message may be observed twice with the same stable ID | +| `relay_after_mark` | row remains published; another relay does not publish it again | + +The shared suite runs the following positive/negative matrix against every provider: + +1. successful applied command with 0/1/many allowed side records; +2. required/forbidden audit and outbox policy failures; +3. same-key/same-hash sequential replay; +4. same-key/different-hash rejection without handler invocation; +5. same raw key plus a changed scope executes as new in a second receipt namespace, while the + deterministic-identity helper rejects time/random/global-dependent scope or fingerprint code; +6. same raw key plus a renamed command executes as new, while same name/scope/key plus a changed + `definitionVersion` is mismatch without handler invocation; +7. concurrent identical claims with a barrier around the handler: leader commit makes the follower + replay and the duplicate-loser path can still execute a harmless subsequent transaction query; +8. leader rollback followed by follower insertion/execution; +9. bounded busy/timeout classification at the advertised unit, whole-callback rollback, pooled + session-setting restoration, and a clean subsequent transaction; +10. provider-specific negative controls: PostgreSQL never emits/continues after a unique violation, + MySQL swallows only 1062 and never warning-converts another error, and SQL Server uses the named + range-locking index rather than catch-and-continue or `MERGE`; +11. CAS zero-match rollback; +12. requested unsupported selectable isolation refusal before begin, plus an omitted-isolation + default-path fixture; +13. serialization/deadlock/busy error with handler invocation count exactly one; +14. invalid/corrupt/incomplete stored receipt refusal without execution; +15. response/payload/fingerprint codec failures, including `Date`, `BigInt`, non-finite numbers, + cyclic values, depth, and byte overflow; +16. abort before begin, during handler, between flush steps, and during relay; +17. every named command fault seam; +18. relay lease expiry, stale-token mark/release, publish-then-crash redelivery, terminal failure, + and graceful shutdown; +19. forbidden telemetry fields and exact allowed enum/count attributes; and +20. a negative control that bypasses the executor or writes one side record with a root client and + proves the same-commit suite fails. + +PostgreSQL is the reference real-provider suite. MySQL and SQL Server must run the same semantic +suite, not mocks with provider labels. SQLite, if proposed, additionally runs multiple-connection +writer contention, busy timeout, process crash/reopen, and lease tests. No adapter earns a +capability row from a type-only fake. + +The suite records database/runtime versions, schema migration digest, exact command, exit code, and +failed test name. Tests clean only resources they positively own. Implementation merge readiness +also includes package tests, doc lint, publish dry run, and the full generated CLI runtime smoke +once after the explicit generators land. + +### Compatibility and migration + +This RFC is additive. Existing service builders, contracts, CRUD handlers, database adapters, worker +jobs, saga definitions, and telemetry keep their current behavior. No existing handler is +automatically wrapped, no table is created at startup, and no default scaffold starts a relay. +Therefore the RFC itself does not require a `breaking` label. + +Adding public subpaths is a coordinated minor release for `@netscript/service`, +`@netscript/database`, and `@netscript/contracts`; the telemetry attributes subpath gains symbols in +the same release train. Every new entrypoint needs module docs/examples, explicit isolated- +declaration-safe exports, `deno doc --lint` over the full package export map, surface-diff review, +clean publish file lists, and a consumer import fixture. The implementation must not make +self-referential bare imports inside those packages. + +Because `jsonCodec()` names `StandardSchemaV1` in a public signature, `@netscript/service` must +declare `@standard-schema/spec` directly in its package import map rather than rely on another +package's transitive resolution. The dependency is already used elsewhere in this workspace but is +not currently declared by the service package. Its exact version follows the normal Deno toolchain +update policy at implementation time. + +The same direct-dependency rule applies to the new `service → database` edge: service declares the +release-matched `@netscript/database` specifier itself; database declares no reciprocal service +dependency. Surface fixtures import every commands/relay/provider subpath from a clean consumer, and +publish dry runs inspect the emitted import specifiers rather than trusting workspace-only +resolution. + +An existing application adopts the seam explicitly: + +1. upgrade after #1350 and the command package slices are published; +2. run `netscript db command-store add --database ` and review/apply the migration; +3. compose one `commandStore` and executor in the service infrastructure layer; +4. convert one non-CRUD mutation to a command, preserving its existing contract shape; +5. choose and document idempotency scope, semantic fingerprint, output codec, CAS, audit data, and + outbox topics; +6. run the adapter/failure suite against the application's database; +7. generate/configure the relay and one sink only if the command emits outbox messages; and +8. deploy the relay before depending on timely delivery, then monitor backlog/age/failure metrics. + +For an existing application-owned receipt or outbox, migration is not an automatic table copy. +Implement a bridge that satisfies the logical contract, prove it with conformance, and migrate keys +and encoded responses deliberately. Changing receipt scope or command name without a migration makes +an old raw key execute as new; changing fingerprint or definition version under the same receipt key +produces key-reuse mismatch; changing a response codec can make committed replay undecodable. Each +is a breaking replay change requiring an old-name/scope compatibility alias or receipt migration, +release notes, and positive/negative compatibility fixtures through the retained retry window. + +Receipt retention must be at least the maximum advertised client retry window. Deleting a receipt +earlier permits the same key to execute again. Published outbox cleanup must not remove rows still +needed for operational reconciliation. Audit retention is application policy. Exact default +durations remain an FCP question. + +### CLI and scaffold impact + +The current CLI already has `contract add-route` and `service add-handler`. V1 extends those +explicit paths rather than changing every scaffold: + +- `netscript contract add-route --command` adds the opt-in command error map + and requires explicit input/output schemas; +- `netscript service add-handler --command --database ` emits a + command definition stub and router binding in the layered service shape owned by #1362; +- `netscript db command-store add --database ` emits provider-specific models, + migration, transaction bridge, `CommandTransactionClient`, and engine-module re-export; and +- `netscript generate command-relay --database ` emits the visible worker drain job and + sink registry. + +`command-store` is a nested database feature noun with the verb `add`; it does not overload or alias +the existing `netscript db init` command. Help and negative CLI tests must show both routes and +refuse `netscript db command-store init` so scripts cannot accidentally invoke the migration-init +flow. + +The proposed service layout is: + +```text +services//src/ + application/commands/.ts + infrastructure/command-store.ts + routers/.ts +database// + schema.prisma + command-store.ts +workers/jobs/ + command-outbox-relay.ts + command-outbox-sinks.ts +``` + +Generators refuse missing #1362 layering, unsupported providers, duplicate models, edited target +files, and routes without declared command errors. They print planned files before writing and never +run database migration/deploy implicitly. + +The normal `netscript init` and generated CRUD example remain unchanged initially. After the +implementation is stable, one neutral non-CRUD command may be added to the full scaffold runtime +fixture as consumer proof. That fixture must demonstrate a lost-response replay, CAS conflict, +same-commit audit/outbox, and relay redelivery without using domain-specific vocabulary. + +Required docs: + +- guide: “Write a transactional command”; +- reference: every new package subpath and logical row/error/telemetry contract; +- operations: relay deployment, backlog, lease, retry, terminal failure, and retention; +- explanation: command versus CRUD versus worker versus saga; +- database matrix with current support/configuration preconditions; +- security/privacy checklist and canonicalization examples; and +- #1364's outbound-webhook recipe using the stable outbox ID and at-least-once wording. + +### Staged implementation + +No product code belongs in this RFC PR. After acceptance, implementation is staged contract-first: + +| Stage | Owning archetype | Deliverable | Required gate | +| ----- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| 0 | A4 (`contracts` + `sdk`) | land #1350's literal-preserving `ContractBuilder` annotation and client error generic | positive/negative real-export type fixtures; contracts/SDK doc lint and publish gates | +| 1 | A4 (`contracts`) | `@netscript/contracts/commands` schemas, literal error map, JCS/codec contracts | isolated declarations, exact code-union negatives, slow-type/doc lint, runtime codec negatives | +| 2 | A4 (`service`) | `@netscript/service/commands` definition/executor over an in-memory conformant fake; add direct database dependency | semantic/identity laws, fault seams, dependency graph, forbidden remote/global design review | +| 3 | A2 (`database`) | true-`TTx` store, generated schema/bridge contract, PostgreSQL receipt-claim reference algorithm | real concurrent leader commit/rollback, lock-timeout/no-poisoning, callback-count, surface/publish gates | +| 4 | A2 (`telemetry`) | command telemetry vocabulary, existing-attribute asymmetry decision, and redaction tests | exact span/attribute assertions, bounded-cardinality and forbidden-field tests | +| 5a | A2 (`database`) | raw relay-store port and PostgreSQL lease/settlement adapter; no queue dependency/runtime DDL | token/lease/crash/redelivery tests plus queue-divergence negative control | +| 5b | A4 (`service`, A3 runtime discipline folded) | decoded delivery/sink ports, relay supervisor/registry, drain/stop lifecycle | failure classification, shutdown, publish-then-crash, no deliver-once claim | +| 5c | A3/A5 (`plugin-*-core` + thin plugins) | worker/saga/stream integration contracts and correction of `WorkerIdempotencyPort` exactly-once wording | at-least-once claim-window docs/tests; thin-plugin/dependency gates | +| 6 | A2 (`database` adapters) | MySQL and SQL Server command/relay adapters | same real-provider suite; MySQL four-level allow-list and `SNAPSHOT` removal; SQL Server snapshot-probe negative | +| 7 | A2, optional (`database`) | default-only SQLite adapter if FCP accepts it | `selectableIsolationLevels: []`, serializable default proof, contention/crash/reopen/lease suite | +| 8 | A6 (`cli`) | `db command-store add`, route/service-command, generated transaction-type, and relay generators | emitted alias/root-method negatives, migration-no-write previews, focused CLI tests, one full `scaffold.runtime` run | +| 9 | docs scope | guide/reference/operations/security/compatibility, queue non-reuse/reconciliation, and #1364 integration | docs links/accuracy, executable snippets, published consumer proof | + +Stages may use separate PRs and can be developed in parallel only where their contracts are already +merged. The public feature is not announced as complete until stages 0–6 and the required parts of +8–9 are green. SQLite is not a completeness requirement unless FCP makes it one. + +### Issue and epic decomposition + +No issues are created or mutated by this RFC PR. The recommended board reconciliation is: + +- keep #1361 as the RFC tracking record through discussion/FCP and follow the RFC process for + acceptance/numbering; +- keep #1363 as the `0.0.8` implementation umbrella rather than duplicating it; +- make #1350 a stage-0 dependency, not a vague “RFC-A” reference; +- let #1362 own generated service layering and make command-handler generation depend on it; +- let #1364 own the remote HTTP recipe/template and consume the command outbox when available; +- keep #1293 adjacent: its surface work is not the MySQL `SNAPSHOT`/allow-list defect, which must be + fixed in the Stage-6 child regardless; propose a separate queue reconciliation child before any + future command-relay/queue code sharing; and +- cross-reference the type-soundness umbrella #1278 without merging its broader scope into this + implementation. + +Suggested PR-sized children under #1363: + +| Proposed child | Labels (plus one lifecycle status) | Milestone | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------ | +| command contracts, errors, JCS codecs | `type:feat`, `area:contracts`, `area:service`, `priority:p1` | `0.0.8` | +| command executor and semantic/fault test kit | `type:feat`, `area:service`, `priority:p1` | `0.0.8` | +| true transaction-client port + PostgreSQL command store | `type:feat`, `area:database`, `priority:p1` | `0.0.8` | +| command OTel vocabulary and privacy tests | `type:feat`, `area:telemetry`, `priority:p1` | `0.0.8` | +| outbox relay and worker/saga sink adapters | `type:feat`, `area:service`, `area:database`, `priority:p1` | `0.0.8` | +| MySQL/SQL Server command-store conformance | `type:feat`, `area:database`, `priority:p1` | `0.0.8` | +| queue runtime-DDL reconciliation (future reuse prerequisite) | `type:fix`, `area:database`, `priority:p2` | `0.0.8` | +| optional SQLite adapter/conformance | `type:feat`, `area:database`, `priority:p2` | FCP decision | +| explicit CLI generators and scaffold consumer | `type:feat`, `area:cli`, `area:service`, `priority:p1` | `0.0.8` | +| command/relay docs and executable examples | `type:docs`, `area:docs`, `area:service`, `priority:p1` | `0.0.8` | + +Each child PR that fully resolves its child issue uses a closing keyword in its body. Children +reference #1363 as an umbrella without closing it. The RFC PR references #1361 without a closing +keyword and never closes an epic/umbrella. + +## Drawbacks + +- The kit adds several public subpaths and durable concepts for what can look like “just a + transaction.” +- Consumer-owned models and migrations are honest but create setup and upgrade work. +- Response codecs and semantic fingerprints are explicit authoring burden. +- Holding an interactive transaction across application logic can increase contention; strict + timeouts and a no-network-I/O rule are necessary. +- Portability requires real-provider conformance, not one mock suite. SQL Server, MySQL, and + potentially SQLite materially increase CI/runtime cost. +- Receipt retention consumes storage and becomes part of API retry compatibility. +- Audit/outbox buffers add memory and transaction writes. +- The type system cannot prevent a closure from capturing a remote client. Review, generated shape, + tests, and doctrine must enforce that boundary. +- An at-least-once relay pushes deduplication responsibility to every downstream effect, which is + correct but not effortless. + +These costs are preferable to a smaller API that silently provides weaker semantics. + +## Rationale and alternatives + +### Why this design + +The design keeps one public promise that adapters can prove: all local records share one store +commit. It reuses current isolation types, Principal-derived identity, Web Crypto, W3C trace +context, telemetry placement, `defineJob`, and saga compensation. New abstractions exist only where +the repository has no equivalent: replay receipts, command-bound audit/outbox rows, an executor, and +adapter conformance. + +The store port is strong rather than configurable because a caller should not need to inspect a +boolean to discover that its audit or receipt is non-atomic. Provider variability still appears in +supported isolation sets, cancellation mode, and conformance status. + +### Rejected alternatives + +#### Reuse `withTransaction()` unchanged + +Rejected because its public callback is asserted as the full root client. It permits code that does +not exist on a real Prisma transaction client. The implementation must correct or bypass the helper +with a true `TTx`. + +#### Put everything in `@netscript/database` + +Rejected because command identity, actor propagation, contract errors, handler policy, and +presentation are application/service concerns. Database owns only the transaction-bound persistence +and providers. + +#### Add `@netscript/commands` now + +Rejected as premature surface growth. A service subpath is sufficient until a second independent +host proves package-level reuse. The database subpath keeps provider code out of service. + +#### Framework-owned hidden tables or startup migration + +Rejected because a published library cannot silently own a product database, migration history, +retention, or provider-specific native types. Explicit generated consumer files are reviewable and +migrable. + +#### Consumer-declared arbitrary receipt adapter without a generator + +Rejected as the only path because drift in unique keys, nullability, response encoding, or lease +fields would undermine conformance. The logical port remains structural, but the supported path +emits a reviewed schema/bridge. + +#### Global `CONFLICT` in `baseContract` + +Rejected because it widens non-command contracts and does not distinguish optimistic conflict from +idempotency misuse. Route-local command errors are more precise and depend explicitly on #1350. + +#### `expectVersion(current)` + +Rejected because read-then-compare is racy. Optimistic concurrency must be enforced by a conditional +mutation or provider-specific locking algorithm. + +#### `maxIsolation` + +Rejected because isolation support is a provider/configuration set, not one uniformly ordered +portable ceiling. SQL Server `Snapshot` alone demonstrates the configuration issue. + +#### Store arbitrary `unknown` responses or payloads + +Rejected because replay requires stable round trips across deployments and providers. Explicit +I-JSON codecs and canonical text make failure and compatibility visible. + +#### Automatic transaction retries + +Rejected because replaying an application callback can repeat captured non-transactional work, +clock/ID reads, or bugs. The receipt makes whole-request retry explicit and observable. + +#### Direct publish before or after commit + +Publishing before commit can announce state that rolls back. Publishing after commit can lose the +message if the process crashes. A same-commit outbox plus at-least-once relay is the narrow durable +answer. + +#### Wrap `@netscript/queue` as the command relay + +Rejected for v1. Its public handler/ack/nack contract deletes acknowledged queue rows, owns a +queue-specific DLQ and runtime listener, does not expose the command outbox's compare-token +settlement, and its PostgreSQL adapter currently creates schema at runtime. The command insert also +must join the business `TTx`, which queue enqueue cannot do. The relay reuses the queue adapter's +proven `SKIP LOCKED`/lease/redelivery/attempt test patterns, while code sharing waits for the +separately proposed migration-free, schema-agnostic queue reconciliation. + +#### Weak KV command mode + +Rejected because claim → effect → mark has a crash window and cannot replay the relational response +inside the business commit. Existing worker idempotency remains valid for its delivery scope; it is +not relabeled as command atomicity. + +#### Use a saga for every local command + +Rejected because a saga is unnecessary overhead for one atomic store mutation and does not replace +the local transaction. Sagas begin at multiple commits, time, remote work, or compensation. + +#### Event sourcing + +Rejected because this RFC does not make events authoritative state, rebuild aggregates, or define an +event schema/runtime. The outbox is a delivery intent derived from an ordinary transaction. + +#### Ambient transaction context + +Rejected because async-local or global context hides ownership and makes tests, nesting, and +cancellation harder to reason about. The transaction client is an explicit handler field. + +### Impact of not doing this + +Applications continue to hand-roll incompatible receipt tables, request hashes, outbox leases, audit +ordering, failure mapping, and telemetry—or omit them. Generated non-CRUD examples cannot teach a +truthful production boundary, and adapters cannot be held to one portable conformance suite. + +## Breaking changes and migration + +The RFC is additive and opt-in. It changes no current export, handler, schema, or scaffold by +itself. Future implementation adds focused subpaths and generator commands in minor releases. +Applications migrate command by command as described above; no automatic CRUD conversion occurs. + +If FCP chooses to change an existing global contract error map or root export instead, that decision +must be re-evaluated for breaking surface impact. This draft does not propose either change. + +## Prior art + +- Prisma interactive transactions, transaction options, documented idempotent API design, and + optimistic concurrency establish the provider-facing mechanics. +- RFC 8785 JCS provides deterministic JSON bytes for hashing instead of a local key-sorting + convention. +- Deno KV versionstamp checks demonstrate optimistic atomic batches and, equally importantly, why + that model is not an interactive SQL transaction. +- NetScript's `WorkerIdempotencyPort` demonstrates claim/apply/release for delivery, while its + different crash boundary explains why command receipts remain separate. +- NetScript's saga message/compensation and queue delivery surfaces own multi-step work after the + command boundary. +- The reserved `SagaOutboxPort` establishes that saga durability needs an outbox, but its T2 + lifecycle and lack of command transaction binding keep the public contracts separate. +- NetScript telemetry already centralizes job/saga/execution attributes and W3C propagation; the + command vocabulary follows that placement with stricter cardinality defaults. + +Primary references: + +- [Prisma transactions and isolation](https://www.prisma.io/docs/orm/prisma-client/queries/transactions) +- [PostgreSQL transaction isolation](https://www.postgresql.org/docs/current/transaction-iso.html) +- [PostgreSQL `INSERT ... ON CONFLICT`](https://www.postgresql.org/docs/current/sql-insert.html) +- [PostgreSQL `lock_timeout`](https://www.postgresql.org/docs/current/runtime-config-client.html) +- [PostgreSQL `current_setting` / `set_config`](https://www.postgresql.org/docs/current/functions-admin.html) +- [MySQL transaction isolation](https://dev.mysql.com/doc/refman/8.4/en/set-transaction.html) +- [MySQL InnoDB error handling](https://dev.mysql.com/doc/refman/8.4/en/innodb-error-handling.html) +- [MySQL `innodb_lock_wait_timeout`](https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html) +- [SQL Server transaction isolation](https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-ver17) +- [SQL Server `SET LOCK_TIMEOUT`](https://learn.microsoft.com/en-us/sql/t-sql/statements/set-lock-timeout-transact-sql?view=sql-server-ver17) +- [SQL Server table hints](https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver17) +- [SQL Server key-range locking](https://learn.microsoft.com/en-us/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver17) +- [SQLite transactions](https://www.sqlite.org/lang_transaction.html) +- [Deno KV transactions](https://docs.deno.com/deploy/kv/transactions/) +- [RFC 8785 JSON Canonicalization Scheme](https://www.rfc-editor.org/info/rfc8785/) +- [W3C Trace Context](https://www.w3.org/TR/trace-context/) +- [OpenTelemetry tracing API](https://opentelemetry.io/docs/specs/otel/trace/api/) +- [OpenTelemetry attribute requirement levels](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) + +## Unresolved questions + +These questions are intentionally reserved for discussion/FCP. None changes the one-store atomicity +contract: + +1. **Idempotency default.** Must every v1 command require a key, or may a definition explicitly + choose `mode: "optional"` and emit `not_requested`? Recommendation: required by default, explicit + optional only for controlled internal callers. +2. **SQLite default-only capability and release timing.** SQLite's engine isolation is serializable, + but the current generated/Prisma surface does not expose a portable explicit selection path. A + future store would therefore advertise `selectableIsolationLevels: []` and + `defaultIsolation: 'Serializable'`: an omitted isolation may run, while an explicitly requested + `Serializable` is refused by the isolation law. Does v1 accept that default-only capability, and + if so must its real contention/crash/lease suite land in the first stable release? + Recommendation: accept the capability shape but release it later; do not delay the client/server + adapters or claim current SQLite support. +3. **Cross-runtime correlation/idempotency telemetry.** Should validated correlation be an opt-in + command attribute under a redaction/cardinality policy, or remain durable-row/log-only? Existing + messaging spans emit `netscript.correlation.id` and saga vocabulary defines + `netscript.idempotency.key`, so either outcome also needs an operator-facing decision: explicitly + document the command asymmetry, or schedule a separate deprecation/redaction cleanup of those + existing attributes. Raw command idempotency keys remain forbidden. Recommendation: keep command + correlation durable-row/log-only by default and reconcile the older telemetry surface separately. +4. **Retention defaults.** What minimum receipt retry window and published-outbox cleanup defaults + should generators document? Recommendation: require explicit deployment values until operational + evidence establishes safe defaults; audit remains application policy. + +The following are not open: no hidden schema ownership, no weak KV semantics, no global conflict +error, no automatic callback retry, no read-then-compare concurrency, no remote I/O in the command +transaction, and no exactly-once delivery claim. PostgreSQL/MySQL/SQL Server receipt claims use the +normative algorithms above; relay runtime ownership is service over database-owned persistence; and +v1 does not wrap or depend on `@netscript/queue`. + +## Future possibilities + +Possible later work, requiring its own evidence and possibly another RFC: + +- a second host proving that `@netscript/service/commands` should become an independent package; +- provider-native JSON projections behind the canonical-text contract; +- a formal schema registry for outbox payloads; +- configurable opt-in correlation telemetry with tested redaction; +- receipt archival/partition helpers; +- additional stores that can genuinely satisfy the same-commit port; +- tooling that statically flags known remote clients captured by command handlers; and +- a dashboard view joining a trace to durable command/audit/outbox state. + +These possibilities do not expand v1's transaction boundary.