Agentic AI Atlasby a5c.ai
OverviewWikiGraphFor AgentsEdgesSearchWorkspace
/
GitHubDocsDiscord
iiRecord
Agentic AI Atlas · Proof-Based Policy Enforcement — Outcome Proofs (Execution Attestation) Design Specification
page:docs-design-proof-based-policy-enforcement-outcome-proofsa5c.ai
Search record views/
Record · tabs

Available views

II.Record viewspp. 1 - 1
overviewarticlejsongraph
II.
Page JSON

page:docs-design-proof-based-policy-enforcement-outcome-proofs

Structured · live

Proof-Based Policy Enforcement — Outcome Proofs (Execution Attestation) Design Specification json

Inspect the normalized record payload exactly as the atlas UI reads it.

File · wiki/docs/design/proof-based-policy-enforcement-outcome-proofs.mdCluster · wiki
Record JSON
{
  "id": "page:docs-design-proof-based-policy-enforcement-outcome-proofs",
  "_kind": "Page",
  "_file": "wiki/docs/design/proof-based-policy-enforcement-outcome-proofs.md",
  "_cluster": "wiki",
  "attributes": {
    "nodeKind": "Page",
    "sourcePath": "docs/design/proof-based-policy-enforcement-outcome-proofs.md",
    "sourceKind": "repo-docs",
    "title": "Proof-Based Policy Enforcement — Outcome Proofs (Execution Attestation) Design Specification",
    "displayName": "Proof-Based Policy Enforcement — Outcome Proofs (Execution Attestation) Design Specification",
    "slug": "docs/design/proof-based-policy-enforcement-outcome-proofs",
    "articlePath": "wiki/docs/design/proof-based-policy-enforcement-outcome-proofs.md",
    "article": "\n# Proof-Based Policy Enforcement — Outcome Proofs (Execution Attestation) Design Specification\n\nStatus: **Draft 3** (2026-07-04). Frozen input: `.a5c/processes/outcome-proofs.brief.md`.\nSupersedes Draft 1 (adversarial review score 34 — **failed** on a FATAL producer-seam flaw, §0.1) and\nDraft 2 (score 78 — fixed the producer flaw with an SDK-owned proven-shell runner, but a second review\nfound **two residual hardening gaps** for an execution-owning seam, §0.3). Draft 3 **closes those two\ngaps precisely** and leaves the sound Milestone-F/H verify/consume mechanics unchanged. This spec adds\nthe **outcome-proof / execution-attestation** direction to the shipped proof-based policy-enforcement\nsystem (Milestones A–E, the *precondition* direction). Every decision cites the exact file it extends;\nevery acceptance criterion (AC) has a stable id `OP-N` that is independently testable.\n\nRead the base spec first: [`docs/design/proof-based-policy-enforcement.md`](./proof-based-policy-enforcement.md)\n(especially §4.2 authoritative-vs-correlation-grade, §6 producer strategy + honesty boundaries,\n§10.1 the out-of-agent config root of trust) and the overview\n[`docs/proof-based-policy-enforcement-overview.md`](../proof-based-policy-enforcement-overview.md) §5.\n\n---\n\n## 0. Summary\n\nThe shipped feature answers *\"may this command run?\"* via **precondition proofs**: evidence\n(human-approval, model-decision, delegation) → `verifyEnvelopeTrusted` → `CommandAuthorization` →\ngate. This increment answers the complement — *\"can I prove this command ran, and what it\nreturned?\"* — via **outcome proofs**: a signed `ToolResultAttestation` binding an exact invocation\nto its exact observed outcome, produced by an executor that **actually ran the command**, using a\nkey the *orchestrating / deciding* agent does not hold. Two consumers:\n\n- **(A) Policy evidence** — a later policy chain requires a `tool-result` step (e.g. authorize\n  `aws deploy` only if a signed proof that `npm test` provably exited 0 exists).\n- **(B) Babysitter-process gate** — a process step gates on `ctx.requireProof(...)`; the\n  orchestrating agent cannot fabricate \"tests passed\" to advance, because the journal must carry a\n  valid **executor-signed** attestation over an execution the SDK itself performed and observed.\n\n**The core property is DATA PROVENANCE, not key-reachability.** A proof is worth exactly as much as\n*who observed the execution*. An attestation over a value the executor did **not** itself observe is\nworthless no matter how well its signing key is isolated. Draft 1 got this backwards; §0.1 is the\ncorrection.\n\n### 0.1 What Draft 1 got FATALLY wrong (the producer-seam flaw)\n\nDraft 1 named two producers and treated both as authoritative-capable. **Both were unimplementable\nfor the mandated guarantee, because neither producer ever observes the command whose outcome it\nclaims to attest.** Grounded in the shipped code:\n\n- **The babysitter SDK runtime does NOT execute shell commands.** A `ctx.task({ kind: 'shell', … })`\n  compiles to a `TaskDef` and an `EFFECT_REQUESTED` journal event (`runtime/intrinsics/task.ts:120`,\n  `:191-212`) — a *request*. The orchestrating agent runs the command out-of-band and reports the\n  outcome back via `babysitter task:post --status ok --value … --stdout-file …`\n  (`cli/main/taskCommands.ts:123-249`). `commitEffectResult` then signs — in Draft 1 — `options.result`\n  (`commitEffectResult.ts:44`, `buildResultPayload` `:444-466`), i.e. **agent-fabricated data**. A\n  compromised agent posts `exitCode:0` for a command it never ran, or that failed, and the\n  \"SDK-commit signer\" faithfully signs the lie. *Key isolation is irrelevant:* the signer signs\n  attacker-chosen content regardless of where the key lives, because the content **is** the\n  `task:post` payload. Draft 1's \"SDK-commit signer\" is therefore **NOT an outcome proof of any\n  grade.** It is removed in Draft 2 (§0.2, OP-11-DEL).\n- **The spawn-layer signer attests the HARNESS invocation, not an inner command.** `spawn-runner.ts`\n  launches the *harness/agent* as its child (`spawn(invocationCmd.command, invocationCmd.args, …)`,\n  `spawn-runner.ts:231`, where `invocationCmd.command` is e.g. `claude`/`codex` or `docker run … <image>`\n  `spawn-invocation.ts:276-388`) and observes *that process's* exit (`child.on('exit', …)` `:591-613`).\n  It genuinely observes an execution — but the execution is **the harness**, whose argv is `claude …`,\n  **not** an inner `npm test` the agent may (or may not) have run inside it. So the spawn-layer signer\n  can honestly attest *\"the harness ran with this exact invocation and exited N\"* — a real, useful,\n  **coarser** fact — but it **cannot** satisfy the mandated `npm test` example. Draft 1 mislabeled it\n  as able to. Draft 2 keeps it with honest, correct scope (§5.3).\n\n**Consequence:** the mandated example — *authorize `aws deploy` only if `npm test` provably passed* —\nwas **unimplementable by either Draft-1 producer.** The fix is not better key custody. The fix is a\nproducer that **owns execution**: an SDK seam that *itself* runs `npm test`, captures the real\noutcome, and signs over what it observed.\n\n### 0.2 The Draft 2 producer model (redesigned)\n\nOne new trust-root kind `tool-executor`. One `ToolExecutorSigner` interface, with **two** concrete\nsigners whose scopes are now HONEST and DISJOINT:\n\n1. **Proven-execution runner (NEW, authoritative for the inner-command guarantee).** A new attested\n   execution seam — a `kind: 'proven-shell'` effect — that the babysitter **SDK runtime itself\n   executes** via `node:child_process` (`execFileSync`) in the SDK's own trust domain, captures the\n   REAL `exitCode`/`stdout`/`stderr` it observed, and signs a `ToolResultAttestation` over **what IT\n   ran and observed** — never over any `task:post`-supplied value. This is the authoritative producer\n   for *\"did THIS command actually run and return X\"* and is what makes the `npm test` example\n   implementable. §5.2 specifies it precisely; it is the only genuinely new execution seam and the\n   heart of Draft 2.\n2. **Spawn-layer signer (kept, honest coarser scope).** The adapters spawn layer that actually\n   launches a process (`spawn-runner.ts`) signs, at process exit, over the **exact process it\n   launched** — the harness invocation argv. It attests *\"the harness ran with this exact invocation\n   and exited N\"*, which is a **different (coarser) granularity** than an inner command and is\n   therefore **NOT** what satisfies the `npm test` example. Useful where the whole harness run *is*\n   the thing to attest. §5.3.\n\n**REMOVED (OP-11-DEL):** the Draft-1 \"SDK-commit signer that signs the agent-reported `task:post`\nvalue.\" It is deleted entirely — not kept as a weak variant. **Signing an agent-reported value is not\nan outcome proof of any grade**, and the OP-B1 \"correlation-grade\" framing that overclaimed it in\nDraft 1 is deleted with it. `commitEffectResult` is NOT modified to sign `options.result` (§5.4).\n\nBoth remaining signers unify under one `ToolExecutorSigner` interface and one `tool-executor`\ntrust-root kind so the policy evaluator + process gate consume attestations identically regardless of\nproducer (§4).\n\n### 0.3 What Draft 2 left open (two residual hardening gaps, closed here)\n\nDraft 2's proven-execution runner genuinely OWNS execution, so the *content* of a proven-shell\nattestation is no longer agent-fabricated. But an execution-owning seam has two further surfaces the\nagent can influence, and Draft 2 did not fully close either. Draft 3 closes both.\n\n- **GAP-1 — the executor itself is PATH/env-hijackable (§5.2, OP-N1).** Draft 2 had the runner take\n  `cwd`+`env` from the agent (`ctx.provenShell({ command, args, cwd?, env? })`). A bare-program\n  `command` such as `'npm'` resolves through `PATH`, so an agent-supplied `env.PATH` can point `npm`\n  at a workspace shim that exits 0. The runner then *honestly* signs an inner-command proof — the\n  observation is real, but the **binary/argv/cwd/env provenance sits with the agent, not the SDK**,\n  defeating the authoritative guarantee **without ever touching `package.json`** (a strictly different\n  attack than OP-T8's script-content tampering). FIX: the runner resolves the program to a **trusted\n  absolute path itself** and runs under a **SANITIZED env derived from the SDK's own environment** —\n  argv0/PATH provenance moves to the SDK. §5.2 + OP-N1 specify the resolution and sanitization rules;\n  OP-T8 is narrowed so only the *contents of a legitimately-resolved script* remain out of scope.\n- **GAP-2 — no kind gate on the agent producer path + unstated replay reconciliation (§5.4, OP-N2).**\n  Draft 2 says proven-shell resolves in-line (no `EFFECT_REQUESTED`, no `task:post`) but never\n  *mandates* rejecting an agent `task:post`/`commitEffectResult` for a `proven-shell` effect, and never\n  reconciles in-line resolution with the request/resolve replay + effect-index model. Grounded in the\n  shipped code, `handleTaskPost` gates only on `record.status === 'requested'`\n  (`cli/main/taskCommands.ts:170`) and `commitEffectResult` only on `record.status !== 'requested'`\n  (`runtime/commitEffectResult.ts:35`) — **neither has a kind gate**, so if a `proven-shell` effect ever\n  entered `requested`, an agent could `task:post` a fabricated `value` + `metadata.toolResultAttestation`\n  for it. FIX: add an explicit `kind === 'proven-shell'` **rejection** to both `task:post` and\n  `commitEffectResult`, and specify that a `proven-shell` effect **never enters an agent-observable\n  `requested` state** — it resolves in-line and its result is journaled once; on replay the journaled\n  result is reused and the command is NOT re-run. §5.4 + OP-N2 specify the gate and the\n  effect-index/replay interaction.\n\n### 0.4 Reuse posture\n\n**Reuse posture.** No new package. The crypto payload extends the shipped primitive\n`ToolResultPayload`/`signToolResult`/`verifyToolResult` in `@a5c-ai/trust-core`\n(`packages/trust-core/src/tool-signing.ts`) rather than forking it; the trust-root kind, the\ntrusted-store resolver, and the policy step extend `@a5c-ai/policy-adapter`; the proven-execution\nrunner and the process gate are new modules **inside `babysitter-sdk`** (§5.2, §7); the spawn-layer\nsigner extends `spawn-runner`. Everything fails closed, no fallbacks (repo rule).\n\n---\n\n## 1. Goals, non-goals, milestones\n\n### 1.1 Goals\n- Make **execution itself** attestable by an executor that **observed** it: a signed proof that a\n  specific command, with specific args, ran and returned a specific outcome — where the signer is the\n  process that actually ran it.\n- A **proven-execution seam owned by the SDK** (`kind: 'proven-shell'`) so the authoritative\n  inner-command guarantee (the `npm test` example) is implementable, not just described.\n- Two consumers of the proof, over the **same** verified `ToolResultAttestation`: a `tool-result`\n  policy step (A) and a `ctx.requireProof(...)` process gate (B).\n- Byte-identical binding with the precondition path: `argsHash`/`commandHash` via the **shared**\n  `canonicalizeArgs`/`commandHash` (`packages/adapters/policy/src/canonicalize-args.ts`), so an\n  outcome proof and a `CommandAuthorization` for the same call agree bit-for-bit.\n- Pluggable executor: one interface, two signers of disjoint honest scope, one trust-root kind, one\n  verify path.\n\n### 1.2 Non-goals (**OP-30**)\n- **No new package** (extend `trust-core` + `policy-adapter`; add the proven-execution runner +\n  `requireProof` intrinsic to `babysitter-sdk`). A new package would need `private:true` +\n  metadata-passing (`scripts/check-package-metadata.cjs`); we avoid it.\n- **No third engine.** The `tool-result` step is evaluated by the existing `evaluatePolicy`\n  (`packages/adapters/policy/src/policy-evaluator.ts`), not a new evaluator.\n- **No online revocation of executor keys** beyond the shipped `revoked`/`expiresAt` + manifest-epoch\n  mechanism (same posture as A–E, §10.1 AC-27).\n- **No result-*content* semantic validation** beyond the declared predicates (exitCode, hash/pattern,\n  freshness, command/args binding). We do not interpret stdout meaning; we bind its hash.\n- **No attestation of a tool the SDK/spawn layer did not itself execute.** The proof is only ever\n  over an execution one of the two seams **performed and observed**. An agent-reported `task:post`\n  value is never attested (OP-11-DEL). Anything else is unproven → the gate/step denies (§7 OP-B4).\n- **NOT in scope: workspace-script CONTENTS integrity (NARROWED in Draft 3).** The proven-execution\n  runner attests that *the exact absolute binary it resolved, with the exact argv it ran, under the\n  SDK's sanitized env* returned exit 0. **Which binary/argv/cwd/env resolves is IN scope** and is\n  controlled by the runner, not the agent (OP-N1, §5.2): the runner resolves the program to a trusted\n  absolute path and sanitizes env, so an agent-supplied `PATH` shim, a relative program, or an escaping\n  `cwd` are **rejected / not run**. The **only** thing out of scope is the *meaning of the CONTENTS of a\n  legitimately-resolved script*: if the agent has write access to the workspace's own `package.json`\n  `test` script, it can make a genuinely-resolved `npm test` a no-op that exits 0. That residual\n  supply-chain concern is §8 OP-T8, out of scope here and stated plainly — but a hijacked\n  `PATH`/binary/cwd/env is **not** covered by OP-T8; it is closed by OP-N1.\n\n### 1.3 Milestones\n- **Milestone F — attestation-core** (`trust-core` + `policy-adapter`): the payload, the\n  `tool-executor` trust-root kind, the trusted-store verify path. **Unchanged from Draft 1 — sound.**\n- **Milestone G — producers** (`babysitter-sdk` proven-execution runner + `adapters/core` spawn\n  signer): the SDK **executes and observes** the command and signs (proven-shell); the spawn layer\n  signs over the harness process it launched. **Redesigned in Draft 2.**\n- **Milestone H — consumers** (`policy-adapter` step + `babysitter-sdk` process gate): the\n  `tool-result` policy step + result predicates, and `ctx.requireProof` + optional proof-gated\n  breakpoint auto-approval. **Unchanged from Draft 1 — sound.**\n\nThe **F → G → H** map for every `OP-N` is in §9.\n\n---\n\n## 2. Architecture overview\n\n```\n                       ┌──────────────────── deployment config (off-workspace, signed) ─────────┐\n                       │  POLICY_CONFIG_ROOT_FP + POLICY_CONFIG_MIN_EPOCH  (env/deploy pin)      │\n                       │  → .policy/config-manifest.json (epoch, AC-46) → .policy/trust-roots.json│\n                       │      (now also `kind:'tool-executor'` roots)                            │\n                       │  executor PRIVATE key: off-agent (deploy secret / KMS), NOT in workspace │\n                       └────────────────────────────────────────────────────────────────────────┘\n                                                    │ (anchors both key + trust roots)\n   ┌─────────────── PRODUCERS (Milestone G, fail-closed, anchor-pinned) ───────────────────────────┐\n   │  (1) PROVEN-EXECUTION RUNNER  babysitter-sdk  — the SDK RUNTIME ITSELF runs `execFileSync`,    │\n   │        captures REAL exitCode/stdout/stderr, signs ToolResultAttestation over WHAT IT OBSERVED │\n   │        (never over any task:post value) → StoredTaskResult.metadata. AUTHORITATIVE for the     │\n   │        inner-command guarantee.                                                                │\n   │  (2) SPAWN-LAYER SIGNER  spawn-runner.ts (process exit) — signs over the exit of the EXACT      │\n   │        HARNESS PROCESS it launched (commandHash = launched argv). Attests the HARNESS run,      │\n   │        a coarser fact — NOT an inner command.                                                  │\n   │  (X) [REMOVED] the Draft-1 \"sign agent-reported task:post value\" producer — OP-11-DEL.          │\n   └───────────────────────────────────────────────────────────────────────────────────────────────┘\n                                                    │ ToolResultAttestation (SignedEnvelope)\n                          ┌─────────────────────────┴──────────────────────────┐\n   ┌────── CONSUMER A (policy) ───────┐                         ┌────── CONSUMER B (process) ──────┐\n   │ policy-evaluator.ts: `tool-result`│                        │ ctx.requireProof({command,result})│\n   │ step → verifyToolResultTrusted →  │                        │ → reads journal StoredTaskResult  │\n   │ result predicates → chain grant   │                        │   .metadata → verifyToolResultTrusted│\n   │ (evidence for CommandAuthorization)│                       │   → predicates → advance / BLOCK   │\n   └───────────────────────────────────┘                        └───────────────────────────────────┘\n                          (both call the SAME verify path: verifyEnvelopeTrusted(kind:'tool-executor'))\n```\n\nThe crypto stays in `trust-core` (support-systems leaf, `private:true`, `type:commonjs`,\n`packages/trust-core/package.json`). `policy-adapter` (ESM) hosts the trusted-store resolver, the\n`ToolExecutorSigner` resolver, and the policy step. `babysitter-sdk` consumes `policy-adapter` via\n**dynamic `import()`** — the exact pattern `trusted-breakpoint-policy.ts:218-226` already uses — so no\nstatic ESM edge is added to the CJS SDK build and no `dispatch-core → orchestration-core` edge is\ncreated (§8, OP-28).\n\n---\n\n## 3. The `ToolResultAttestation` payload (Milestone F — unchanged from Draft 1)\n\n### 3.1 OP-1 — Extend `ToolResultPayload`, do not fork\n\nThe shipped primitive is `ToolResultPayload` + `signToolResult`/`verifyToolResult` in\n`packages/trust-core/src/tool-signing.ts:4-34`. It is present but wired to nothing (brief §\"What\nalready exists\"). `signToolResult` is a thin wrapper over `signPayload`\n(`packages/trust-core/src/signing.ts:24-47`), which already binds `signedFields`, `signedAt`,\n`publicKeyFingerprint`, and `algorithm` into the canonical form. **OP-1** extends the existing\n`ToolResultPayload` interface with the missing bound fields and adds a domain-separation\n`payloadType`, keeping `signToolResult`/`verifyToolResult` as the low-level entry points (the\nattestation is a `SignedEnvelope<ToolResultAttestationPayload>` produced by `signToolResult`).\n\n```ts\n// packages/trust-core/src/tool-signing.ts  (EXTENDED — additive; keeps existing fields optional)\nexport interface ToolResultAttestationPayload {\n  payloadType: 'tool-result-attestation';   // OP-3 domain-separation constant (in signedFields)\n  // WHAT the executor ran (bound by hash over the argv the EXECUTOR actually ran — never asserted):\n  toolName: string;\n  toolCallId: string;\n  commandHash: string;                       // sha256(canonicalizeArgv(argv the executor ran)) — OP-2\n  argsHash: string;                          // sha256(canonicalizeArgs(args the executor ran))  — OP-2\n  // WHAT the executor OBSERVED it return:\n  exitCode: number;\n  stdoutHash: string;                        // sha256 of the stdout bytes the EXECUTOR captured (OP-2)\n  stderrHash: string;                        // sha256 of the stderr bytes the EXECUTOR captured (OP-2)\n  // WHEN:\n  startedAt: string;                         // ISO\n  finishedAt: string;                        // ISO\n  durationMs: number;\n  // WHERE (replay binding, OP-14):\n  runId: string;\n  sessionId: string;\n  // WHO decided (optional link to the precondition path, §4.2 A–E of the base spec):\n  modelDecisionFingerprint?: string;\n  // WHO executed + AT WHAT GRANULARITY (honest scope, OP-B-PRODUCER):\n  executorKind: 'tool-executor';\n  producer: 'proven-shell' | 'spawn-layer';  // OP-B-PRODUCER: the seam that OBSERVED the execution\n  attestationScope: 'inner-command' | 'harness-invocation'; // what the commandHash names (§5.2/§5.3)\n}\n```\n\n`ToolResultPayload` (the shipped shape) is retained for back-compat; the attestation payload is a\nnew, fully-bound superset. `signToolResult` is generalized to sign the attestation payload with an\nexplicit `fields` list (the existing `signPayload(privateKey, fingerprint, payload, fields)` third\nargument, `signing.ts:24-30`) so `payloadType`, `producer`, and `attestationScope` are provably in\n`signedFields`.\n\n### 3.2 OP-2 — Hashes via the SHARED canonicalizers, over what the EXECUTOR ran\n\n`argsHash` and `commandHash` MUST be computed by the **single shared** helpers exported from\n`@a5c-ai/policy-adapter` (`packages/adapters/policy/src/canonicalize-args.ts`):\n`argsHash(value)` = `sha256(canonicalizeArgs(value))` (`canonicalize-args.ts:77-79`) and\n`commandHash(command)` = `sha256(canonicalizeArgv(command).join('|'))` (`canonicalize-args.ts:82-85`).\nThis is the identical function the gate and the `CommandAuthorization` issuer use (base spec AC-52/53),\nso an outcome proof's `argsHash`/`commandHash` are byte-identical to a `CommandAuthorization`'s for the\nsame call. **Critically, the argv fed to `commandHash` is the argv the EXECUTOR actually ran** — for\nthe proven-execution runner that is the exact `command`+`args` it passed to `execFileSync` (§5.2); for\nthe spawn-layer signer it is the launched harness `invocationCmd.command`+`args` (§5.3). `stdoutHash`/\n`stderrHash` are `sha256` over the **captured** output bytes (the exact bytes the executor observed,\nOP-15 TOCTOU). A non-representable arg (non-finite number) throws in `canonicalizeArgs`\n(`canonicalize-args.ts:47-49`) → the producer **denies** (no proof), never coerces. Because\n`trust-core` is a leaf and cannot import `policy-adapter`, the producers (in the SDK and adapters\nlayers, both of which already reach `policy-adapter` by dynamic import) compute the hashes with the\nshared helper and hand the finished payload to `signToolResult`; `trust-core` never sees the\ncanonicalizer (preserving the leaf boundary, §8/OP-28).\n\n### 3.3 OP-3 — `payloadType` + `producer` + `attestationScope` bound for domain separation\n\n`payloadType: 'tool-result-attestation'` MUST be in `signedFields` and MUST equal that constant, so a\n`ToolResultAttestation` cannot be replayed as, or confused with, a `command-authorization`,\n`model-decision`, `config-manifest`, etc. `producer` and `attestationScope` are also bound so a\nconsumer can require the authoritative inner-command producer and reject a harness-scope proof\npresented as if it were an inner-command proof (OP-18). This mirrors AC-51 and the shipped\n`EXPECTED_PAYLOAD_TYPE`/`REQUIRED_SIGNED_FIELDS` maps in\n`packages/adapters/policy/src/verify-envelope-trusted.ts:29-72`. See OP-6 for where the check lands.\n\n---\n\n## 4. The `tool-executor` trust-root kind + verify path (Milestone F — unchanged from Draft 1, sound)\n\n### 4.1 OP-4 — Add `tool-executor` to `TrustRootKind`\n\nThe shipped `TrustRootKind` is `'human' | 'engine' | 'agent' | 'tool' | 'config'`\n(`verify-envelope-trusted.ts:76`). **OP-4** adds `'tool-executor'`:\n\n```ts\nexport type TrustRootKind = 'human' | 'engine' | 'agent' | 'tool' | 'config' | 'tool-executor';\n```\n\n`'tool'` (the shipped generic tool-identity kind, unused by the enforcement path) is intentionally\ndistinct from `'tool-executor'` (the execution-attestation signer). A `tool` root does NOT satisfy a\n`tool-executor` requirement and vice-versa (OP-7 cross-kind rejection). `tool-executor` public keys\nlive in the manifest-covered `.policy/trust-roots.json` (a new-kind entry), exactly like the\n`human`/`engine` roots that `trusted-breakpoint-policy.ts:293-298` and the base evaluator already read;\nthe executor **private** key is provisioned off-agent (deploy config / KMS, like the proxy key — base\nspec §4.2, §10.1). No fingerprint may be shared across kinds (the duplicate-fingerprint rejection in\n`loadTrustStore`, `policy-schema.ts:318-333`, already forbids one fingerprint appearing twice). The\n`tool-executor` root carries the same `producer` discriminant the shipped `engine` root uses\n(`verify-envelope-trusted.ts:101`, `producer:'proxy'|'in-process'`) — here `producer:'proven-shell'|\n'spawn-layer'` — so a policy can require the authoritative producer (OP-18).\n\n### 4.2 OP-5 — New evidence kind `tool-result` + its `EvidenceKind` mapping\n\n**OP-5** adds `'tool-result'` to the shipped `EvidenceKind` union\n(`verify-envelope-trusted.ts:21-26`), to `EXPECTED_PAYLOAD_TYPE`\n(`'tool-result' → 'tool-result-attestation'`), and to `REQUIRED_SIGNED_FIELDS`:\n\n```ts\nREQUIRED_SIGNED_FIELDS['tool-result'] = [\n  'payloadType', 'toolName', 'toolCallId', 'commandHash', 'argsHash',\n  'exitCode', 'stdoutHash', 'stderrHash', 'startedAt', 'finishedAt', 'durationMs',\n  'runId', 'sessionId', 'executorKind', 'producer', 'attestationScope',\n];   // modelDecisionFingerprint is OPTIONAL — omitted from the required set (OP-1)\n```\n\nand extends `trustRootKindForEvidence` (`verify-envelope-trusted.ts:292-305`) with\n`case 'tool-result': return 'tool-executor';`. This wires `tool-result` evidence to the\n`tool-executor` trust-root kind through the identical selection machinery A–E use — **no new resolver**.\n\n### 4.3 OP-6 — `verifyEnvelopeTrusted` resolves + kind-checks the executor key (fail-closed, in order)\n\nThe shipped `verifyEnvelopeTrusted` / `verifyOne` (`verify-envelope-trusted.ts:237-329`) already\nimplements the AC-35 (a)–(g) sequence. Because OP-4/OP-5 register `tool-result → tool-executor`,\n`verifyEnvelopeTrusted(attestation, 'tool-result', store, allowedFingerprints?)` resolves a\n`ToolResultAttestation` with **zero new verification code**, enforcing exactly:\n\n- **(a)** resolve key material **only** from the trusted store by `publicKeyFingerprint`\n  (`verify-envelope-trusted.ts:250-251`); the envelope's own embedded key, if any, is ignored;\n- **(b)** the resolved root's `kind` MUST equal `tool-executor` (`:254-257` via\n  `trustRootKindForEvidence('tool-result')`); `allowedFingerprints` (if the step supplies them) MUST\n  contain the fingerprint (`:258-260`);\n- **(c)** `sha256(resolvedPublicKey) === envelope.publicKeyFingerprint` (`:267-269`) — the\n  fingerprint→material binding a raw verify omits;\n- **(d)** cross-kind is rejected by (b): **NO** non-`tool-executor` key (human/engine/agent/tool/config)\n  can satisfy a `tool-result` step (**OP-7**);\n- **(e)** `signedFields` completeness + `payloadType === 'tool-result-attestation'`\n  (`:274-275` → `checkCompleteness`, `:211-230`) using the OP-5 required-field set (**OP-3**);\n- **(f)** root not `revoked` and not expired at `signedAt` (`:278-279`, `keyValidAt` `:196-202`) — **OP-8**;\n- **(g)** only now, the raw `verifySignature(resolvedPublicKey, envelope)` (`:282`).\n\nAny thrown exception at any step is a DENY (`:326-328`). **OP-6** is: a `ToolResultAttestation`\nverifies iff and only iff (a)–(g) all pass against a `tool-executor` root in the manifest-verified\nstore. A convenience export `verifyToolResultTrusted(envelope, store, allowedFingerprints?)` wraps\n`verifyEnvelopeTrusted(envelope, 'tool-result', ...)` so consumers have a named entry (mirrors the\nshipped `verifyCommandAuthorization` wrapper, `:355`).\n\n### 4.4 OP-9 — The verify path is anchored to the manifest-verified config, never agent-writable input\n\n`verifyToolResultTrusted` is only ever called with a `store` whose `tool-executor` roots come from the\n**manifest-verified** `.policy/trust-roots.json`. The producers (OP-11/OP-12) resolve the trust roots\n+ executor key through the SAME manifest-verification path A–E use: for the proven-execution runner,\nthe pinned `POLICY_CONFIG_ROOT_FP` + `POLICY_CONFIG_MIN_EPOCH` → `verifyConfigManifest`\n(`packages/adapters/policy/src/config-manifest.ts:67`) → trust-roots, exactly as\n`loadTrustedBreakpointPolicy` does (`trusted-breakpoint-policy.ts:161-290`); for the spawn signer, the\nsame `loadPolicyEnforcementGate(projectRoot)` gate context the shipped GATE 3 already resolves\n(`policy-spawn-gate.ts:70-96`). A config file whose sha256 is not the manifest-bound one, a manifest\nsigned by anything but the pinned config root, or an epoch below the floor → deny-all (config-manifest\nverifier, `config-manifest.ts:88-123`). **The executor public keys + trust roots are therefore never\ntaken from agent-writable input** — closing the same class of attack Milestone C closed for\nbreakpoints (`trusted-breakpoint-policy.ts:6-42`).\n\n---\n\n## 5. The `ToolExecutorSigner` interface + the two signers (Milestone G — redesigned)\n\n### 5.1 OP-10 — The pluggable `ToolExecutorSigner` interface\n\n```ts\n// packages/adapters/policy/src/tool-executor-signer.ts  (NEW module in the EXISTING package)\nexport interface ToolExecutorSigner {\n  readonly fingerprint: string;          // this executor's tool-executor fingerprint (in trust-roots)\n  readonly producer: 'proven-shell' | 'spawn-layer';\n  /** Sign the CAPTURED result at capture time (OP-15). Returns a SignedEnvelope or throws (fail closed). */\n  sign(payload: ToolResultAttestationPayload): SignedEnvelope<ToolResultAttestationPayload>;\n}\n/** Resolve the configured executor signer from the manifest-verified config, or `undefined`\n *  when no anchor is pinned (enforcement inactive → no attestation, back-compat). Throws → deny. */\nexport function resolveToolExecutorSigner(projectRoot: string): Promise<ToolExecutorSigner | undefined>;\n```\n\n`resolveToolExecutorSigner` loads the executor **private** key from the off-agent pin\n(`POLICY_TOOL_EXECUTOR_KEY` / `POLICY_TOOL_EXECUTOR_KEY_PATH`, mirroring the config-root key envs at\n`trusted-breakpoint-policy.ts:83-89`), verifies the config manifest, confirms the corresponding\n`tool-executor` public root is present + non-revoked in the manifest-verified `.policy/trust-roots.json`,\nand returns a signer whose `sign` delegates to `signToolResult`. Both concrete signers below **are**\n`ToolExecutorSigner`s — the policy evaluator + process gate consume their output identically. The\nresolved signer's `producer` is stamped into every payload it signs, so `attestationScope` and\n`producer` cannot be forged onto a proof by a consumer (they are signed).\n\n### 5.2 OP-11 — Proven-execution runner (`kind: 'proven-shell'`, NEW, authoritative)\n\n**This is the heart of the Draft-2 redesign: a producer that OWNS EXECUTION.** The SDK runtime — not\nthe orchestrating agent — runs the command, observes the real outcome, and signs over what it observed.\n\n**New task kind + dispatch (so the SDK, not the agent, executes).** We add a `kind: 'proven-shell'`\ntask and a new module `packages/babysitter-sdk/src/runtime/proven-shell/runner.ts` (the one genuinely\nnew execution seam). Unlike `kind: 'shell'` — which compiles to a *request* the agent fulfils\nout-of-band (`intrinsics/task.ts:120,191-212`) — a `proven-shell` effect is **executed in-line by the\nSDK runtime**:\n\n- The intrinsic `runProvenShellIntrinsic` (new, alongside `runTaskIntrinsic`) resolves the effect not\n  by emitting an `EFFECT_REQUESTED` for the agent to answer, but by invoking the runner **directly in\n  the SDK runtime process** (§5.4 / OP-N2: the effect NEVER enters an agent-observable `requested`\n  state). The runner calls `node:child_process.execFileSync(resolvedProgram, args, {\n  timeout, maxBuffer, cwd: resolvedCwd, env: sanitizedEnv })` — **`shell:false`, argv form only** (no\n  `sh -c`, so the agent cannot smuggle a different command through shell metacharacters; a `command`\n  that is not a bare program is rejected, mirroring the anti-evasion `matchArgv` posture,\n  `policy-evaluator.ts:509-547`). The `resolvedProgram`, `resolvedCwd`, and `sanitizedEnv` are all\n  derived by the runner under SDK control per **OP-N1** below — **not** taken from the agent — so\n  binary/argv/cwd/env provenance sits with the SDK.\n- The runner captures the **real** `status` (exit code, or the thrown `error.status`/`error.signal`\n  from a non-zero exit), the **real** `stdout` and `stderr` bytes it received, and the wall-clock\n  `startedAt`/`finishedAt`. **These are the bytes the SDK itself observed** — there is no `task:post`\n  in this path, so there is no agent-supplied value anywhere in the chain. **`exitCode`, `stdoutHash`,\n  and `stderrHash` (which the predicates read) are computed ONLY from runner-captured bytes** (OP-15b);\n  if `execFileSync` throws because `maxBuffer` was exceeded or the `timeout` elapsed, the runner has no\n  complete captured result and **denies** — it never emits a partial-capture attestation (OP-15b keeps\n  the OP-15 TOCTOU binding airtight).\n- The runner then builds the `ToolResultAttestationPayload`: `commandHash`/`argsHash` over the\n  **exact `resolvedProgram`+`args` it passed to `execFileSync`** (shared canonicalizer, OP-2 — note the\n  bound `commandHash` names the *resolved absolute program*, OP-N1),\n  `exitCode`/`stdoutHash`/`stderrHash` over what it captured, `attestationScope:'inner-command'`,\n  `producer:'proven-shell'`, and signs via the resolved `ToolExecutorSigner` (OP-10). The signed\n  envelope is written into `StoredTaskResult.metadata.toolResultAttestation` (OP-13) as the effect\n  result is committed — but the *value being signed comes from the runner, never from `commitEffectResult`\n  `options.result`*.\n\n**OP-N1 — the runner controls binary/argv/cwd/env; the agent cannot hijack resolution.** An\nexecution-owning seam is only authoritative if *what it runs* is under its control. The agent supplies\nthe command *definition* (`ctx.provenShell({ command, args })`); the runner resolves the actual\nexecution parameters itself:\n\n- **Program → trusted absolute path (MUST).** The runner MUST resolve `command` to an **absolute path**\n  itself, and MUST NOT rely on an agent-influenceable `PATH`. Resolution is one of, in order: (i) if\n  `command` is already an absolute path, it MUST resolve inside a configured trusted-root allowlist (the\n  manifest-config-declared `provenShell.programAllowlist` / trusted bin dirs, anchored like the trust\n  roots, OP-9) or be rejected; (ii) otherwise the bare program is resolved against a **sanitized system\n  `PATH`** the SDK derives from its own environment / a manifest-config-declared allowlist — **never**\n  from `env.PATH` supplied by the agent and **never** including the run workspace. If the program cannot\n  be resolved to a trusted absolute path, the runner **denies** (no proof; the effect rejects). The\n  bound `commandHash` (OP-2) is computed over the **resolved absolute program**, so a proof names\n  exactly the binary the SDK ran.\n- **Sanitized env (MUST).** The runner MUST run with an env **derived from the SDK's own environment**\n  (or a manifest-config-declared allowlist), NOT from agent-supplied `env`. In particular `PATH` and any\n  variable that can alter binary/argv0 resolution come from the SDK, never the agent. If `ctx.provenShell`\n  exposes an `env` option at all, it MAY only **ADD** non-`PATH` variables that cannot influence binary\n  resolution (e.g. `CI=1`); it may not override `PATH`, `PATHEXT`, `NODE_OPTIONS`, or any loader/resolver\n  variable — such keys are dropped or the call is rejected. The recommended posture is to **drop the\n  `env` option entirely** and derive env wholly from the SDK. The executor **private key env var\n  (`POLICY_TOOL_EXECUTOR_KEY`) MUST be stripped from `sanitizedEnv`** so it never leaks into the child.\n- **cwd constrained (MUST).** `cwd` MUST be constrained to the run workspace: an agent-supplied `cwd`\n  that resolves (after symlink realpath) **outside** the run workspace root is **rejected**; when omitted,\n  `cwd` is resolved deterministically to the run workspace root. The runner never honors an escaping\n  `cwd`.\n- **Fail closed.** Any resolution failure (untrusted/unresolvable program, a forbidden env key, an\n  escaping cwd) is a **DENY** — the effect rejects, no attestation, mirroring the §5.2 fail-closed throw.\n\n**Where the command definition comes from vs. what is observed.** The agent (process author) declares\n**which** command to run — `ctx.provenShell({ command: 'npm', args: ['test'] })`. The agent does\n**not** supply the result: the SDK runs it. So the agent controls the *definition* (which is fine — a\npolicy binds `commandHash` to the required command via OP-17e, so a swapped definition is caught) but\n**not the OBSERVED outcome**. This is the exact provenance boundary Draft 1 violated.\n\n**Fail closed.** If the anchor is pinned but signing throws (key unresolvable, canonicalization deny,\n`command` not a bare program), OR the runner cannot resolve the program to a trusted absolute path, OR\nthe agent supplied a forbidden env key or an escaping `cwd` (OP-N1), OR `execFileSync` exceeded\n`maxBuffer` / timed out with no complete capture (OP-15b), the effect **rejects** (`RunFailedError`,\nmirroring the `signed_breakpoint_rejected` throw at `commitEffectResult.ts:279-289`) — an execution that\nwas required to be proven but could not be leaves the run unable to advance rather than committing an\nunproven or partially-captured result. When no anchor is pinned, `proven-shell` still executes the\nresolved command in-SDK (under OP-N1 resolution + sanitized env — the hardening is unconditional, not\ngated on the anchor) and returns the result, but attaches **no** attestation (back-compat; a downstream\n`requireProof`/`tool-result` step then denies for lack of proof — never silently passes).\n\n**Where the key lives.** The executor private key is resolved by `resolveToolExecutorSigner` from the\noff-agent env/deploy pin (OP-10) — **not** from the run directory, **not** from `task.json`, **not**\nfrom any workspace file the agent can write. The runner runs in the SDK **runtime** process; the\norchestrating agent's *reasoning context* is the LLM conversation, which never sees\n`process.env[POLICY_TOOL_EXECUTOR_KEY]`.\n\n**Honest residual key-custody boundary (OP-B-KEY).** The proven-execution runner is **authoritative\nfor the OBSERVATION of the exact absolute program it resolved and ran under a sanitized env**: the SDK\nresolves the binary to a trusted absolute path itself, runs it with SDK-derived env (OP-N1), genuinely\nruns it, and sees the result — so the *content* of the attestation (which binary ran, with which argv,\nin which cwd, and what it returned) is trustworthy by construction and is **not** agent-fabricated. The\nobservation claim is therefore honest: it is authoritative for the resolved absolute program, not merely\nfor \"some `npm` on the agent's `PATH`\". The remaining assumption is\npurely about **key custody**, and it is the *same* assumption the base spec makes for the transport\nproxy (§4.2 posture): the SDK runtime's executor key must be kept out of a domain where the agent can\nexecute arbitrary code and read `process.env`. In the intended deployment the SDK runtime holds the\nkey in a domain the agent's reasoning context cannot reach (separate process/user, or a KMS-backed\nsigner). If a deployment instead runs the SDK runtime in the *same* process as an agent that can\n`require` the SDK and read `process.env`, the key is reachable — but note this is a strictly weaker\nattack than Draft 1's: the attacker would have to *forge a signature over a fabricated outcome*, not\nmerely *report a fabricated value that gets signed for it*. The observation itself remains genuine\nwhenever the runner actually runs; the boundary is stated so deployments provision the key off-agent\n(KMS-backed signer recommended), exactly as they do the proxy key.\n\n### 5.3 OP-12 — Spawn-layer signer (harness-invocation scope, honest and correct)\n\n**Where.** `packages/adapters/core/src/spawn-runner.ts`, in the `child.on('exit', ...)` handler\n(`spawn-runner.ts:591-613`) / `cleanupAndFinalize` (`:568-589`), where the real exit code and the\naccumulated stdout/stderr (`stderrBuf`, `:288`, and the streamed stdout) are known. The command + args\nthat were launched are `invocationCmd.command` / `invocationCmd.args` — the **exact argv the spawn\nlayer ran** (`buildInvocationCommand`, `spawn-invocation.ts:276-388`; e.g. `claude …`, or\n`docker run … <image> …`). At exit, the spawn signer builds the `ToolResultAttestationPayload` over the\nCAPTURED exit outcome, **binding `commandHash` to the launched `invocationCmd` argv** (OP-2), with\n`attestationScope:'harness-invocation'` and `producer:'spawn-layer'`, and signs with the executor key\nresolved from the SAME manifest-verified gate context GATE 3 uses (`resolveGate3Context` →\n`loadPolicyEnforcementGate`, `policy-spawn-gate.ts:70-96`). The signed attestation is surfaced on the\nrun result so the SDK stores it in the journal (OP-13) for the effect that dispatched the spawn.\n\n**Honest scope (OP-B-SCOPE).** The spawn layer launches a process (`spawn(...)`, `spawn-runner.ts:231`)\nand observes *that process* — **the harness invocation**. It genuinely observes an execution, and its\nkey is well-isolated (the harness is a **child** that cannot read its parent's `process.env`/memory).\nBut it observes the **harness**, whose argv is e.g. `claude …`, **not** an inner `npm test` the harness\nmay run inside itself. Therefore the spawn-layer attestation is **NOT** what satisfies the `npm test`\nexample. It honestly attests *\"the harness ran with this exact invocation and exited N\"* — useful when\nthe whole harness run is the unit to be proven (e.g. \"a sandboxed one-shot tool container ran and\nexited 0\"), or for coarse audit. A consumer that needs an inner-command guarantee MUST require\n`producer:'proven-shell'` / `attestationScope:'inner-command'` (OP-18); a spawn-layer proof will not\nsatisfy it. This is stated in code (the signed `producer`/`attestationScope` fields) and in docs.\n\n**Both signers, one consumer.** Because both emit a `SignedEnvelope<ToolResultAttestationPayload>`\nverified through the identical `verifyToolResultTrusted` path (OP-6) against the identical\n`tool-executor` kind, the policy evaluator (OP-16) and the process gate (OP-19) consume them\nuniformly; they differ only in the signed `producer`/`attestationScope`, which a policy may constrain\n(OP-18).\n\n### 5.4 OP-11-DEL — REMOVED: the \"sign agent-reported `task:post` value\" producer\n\nThe Draft-1 SDK-commit signer signed `commitEffectResult` `options.result` — an agent-fabricated\n`task:post` value (§0.1). **It is deleted.** `commitEffectResult.ts` is **not** modified to attach a\n`ToolResultAttestation` over `options.result`; there is no `signToolResultAttestation(options, record)`\nstep, no `attestResult`-triggered signing of a `task:post` value. The only path that produces a\nproof at commit time is the proven-execution runner, whose value the SDK observed directly, not the\nagent's post. Signing an agent-reported value is **not an outcome proof of any grade**; the Draft-1\n\"correlation-grade OP-B1\" framing that presented it as a weak-but-usable proof is deleted with it. Any\n`ctx.task({ kind: 'shell' })` continues to work exactly as today (agent runs it, `task:post` reports\nit) — it simply **produces no outcome proof**, and a downstream gate that demands one denies.\n\n### 5.4.1 OP-N2 — `proven-shell` kind gate on `task:post` / `commitEffectResult` + replay reconciliation\n\nDeleting the Draft-1 signer removes the path that *signed* an agent value, but it does not by itself\nstop an agent from **posting** a value with a fabricated `metadata.toolResultAttestation` for a\n`proven-shell` effect — because, grounded in the shipped code, neither commit path has a **kind gate**:\n`handleTaskPost` accepts any effect whose `record.status === 'requested'`\n(`cli/main/taskCommands.ts:170`, keyed on `effectId` + `status==='requested'`, no kind check) and it\ncarries `--metadata` straight through into `metadata` (`taskCommands.ts:176-177,230,241`);\n`commitEffectResult` guards only on `record.status !== 'requested'` (`commitEffectResult.ts:35`).\nOP-N2 closes this on two fronts.\n\n- **MANDATE — explicit kind gate (MUST).** Both `handleTaskPost` and `commitEffectResult` MUST\n  **reject** any effect whose `record.kind === 'proven-shell'`. Concretely: in `handleTaskPost`\n  (after the `record` lookup at `cli/main/taskCommands.ts:165-173`, before the `commitEffectResult`\n  call at `:215`) add — *if `record.kind === 'proven-shell'` then error `[task:post] effect <id> is\n  proven-shell; its result is produced by the SDK runtime and cannot be posted by an agent` and return\n  a non-zero exit* — and in `commitEffectResult` (a new guard alongside the `record.status` check at\n  `commitEffectResult.ts:35-38`) throw `RunFailedError('Effect <id> is proven-shell and cannot be\n  committed via commitEffectResult')`. **The agent can therefore supply neither `value` nor `metadata`\n  (including a forged `metadata.toolResultAttestation`) for a `proven-shell` effect.** The in-line\n  runner writes the proven-shell result through the SDK's own serialization path (§5.2 / OP-13), not\n  through `commitEffectResult`'s agent-facing options.\n- **No agent-observable `requested` state (MUST).** A `proven-shell` effect MUST NOT enter the\n  `requested` state that `handleTaskPost`/`commitEffectResult` accept, so there is no window for the\n  agent to race a `task:post` against it. Unlike `kind:'shell'`, which emits `EFFECT_REQUESTED` and\n  throws `EffectRequestedError` for the agent to fulfil out-of-band\n  (`intrinsics/task.ts:191-212`), `runProvenShellIntrinsic` runs the command **in-line in the SDK\n  runtime** and resolves the effect within the same runtime turn. The kind gate above is defense in\n  depth: even if a malformed journal presented a `proven-shell` record as `requested`, both commit\n  paths refuse it.\n- **Effect-index / replay reconciliation (MUST — deterministic).** In-line resolution coexists with the\n  request/resolve replay + effect-index model as follows. On **first execution**, the intrinsic\n  computes the same `invocationKey` the task intrinsic uses (`hashInvocationKey`, `intrinsics/task.ts:79`),\n  runs the command **exactly once**, and journals the effect's resolution — the signed attestation plus\n  the captured result — as a resolved effect (an `EFFECT_REQUESTED` immediately followed by, or coalesced\n  into, an `EFFECT_RESOLVED` carrying the runner-produced result; it does **not** dwell in an\n  agent-answerable `requested` state). On **replay**, `getByInvocation(invocationKey)` returns the\n  resolved record and the intrinsic reuses the **journaled result via `handleResolvedRecord`**\n  (`intrinsics/task.ts:88-92,97-106`) — **the command is NOT re-run**, and the previously-signed\n  attestation is read back byte-stable from `StoredTaskResult.metadata.toolResultAttestation` (OP-13).\n  This makes proven-shell replay-deterministic and idempotent: the observation happens once, the proof\n  is durable, and no second execution can diverge.\n\n**Red-test targets (OP-N2).** (a) An agent `task:post`s a fabricated `--value` + `--metadata`\ncontaining a `toolResultAttestation` for a `proven-shell` effect ⇒ **REJECTED** by the kind gate (and,\nindependently, `commitEffectResult` refuses it). (b) A `proven-shell` effect **never appears as a\npending `requested` effect** an agent-facing lister/`task:post` can resolve — its resolution is in-line\nand its record is `resolved` by the time the runtime turn yields. (c) On replay the journaled result is\nreused and `execFileSync` is **not** called a second time.\n\n### 5.5 OP-13 — How the attestation rides the effect result / journal\n\nThe attestation is stored at `StoredTaskResult.metadata.toolResultAttestation`\n(`packages/babysitter-sdk/src/storage/types.ts:119-141`; `metadata` is a `JsonRecord`, already\nstable-cloned by `serializeTaskResult`, `serializer.ts:167`). It therefore travels with the effect\nresult and is replayable from the append-only journal — the same durable artifact the process gate reads\n(OP-19). The proven-execution runner writes it inline as the `proven-shell` effect result is committed;\nthe spawn signer's envelope is threaded back through the effect result the caller commits, so it lands\nin the same `metadata` slot. **OP-13** is: a valid attestation for a proven execution is present at\n`StoredTaskResult.metadata.toolResultAttestation`, byte-stable across replay. (Note: this is the SDK\nwriting metadata for a value *it* produced, not signing an agent's `task:post` value — the distinction\nthat OP-11-DEL enforces.)\n\n---\n\n## 6. The `tool-result` policy step + result predicates (Milestone H, consumer A — unchanged from Draft 1)\n\n### 6.1 OP-16 — New step kind `tool-result`, expressible with NO evaluator code change to add a policy\n\n**OP-16** adds `'tool-result'` to `EvidenceStepKind` (`policy-schema.ts:23`) and to\n`KIND_TO_EVIDENCE_KIND` (`policy-evaluator.ts:177-181`) and to the `Evidence.kind` union\n(`policy-evaluator.ts:42-45`). A `tool-result` requirement is satisfied by an `Evidence` of kind\n`tool-result` whose envelope verifies via `verifyEnvelopeTrusted(envelope, 'tool-result', store,\nallowedFingerprints)` (the shipped `evidenceSatisfiesStep` path, `policy-evaluator.ts:241-267`) AND whose\n**result predicates** hold (OP-17). Once the step kind + predicates exist, **adding a new\ntool-result-gated policy is pure YAML** — no code change — because it flows through the same\n`normalizeTypedStep`/`satisfyTypedStep`/`evaluateChain` machinery as every other step\n(`policy-schema.ts:117-129`, `policy-evaluator.ts:277-307`, `:424-469`).\n\n### 6.2 OP-17 — Result predicates on the verified attestation\n\nResult predicates are declared in the step's `conditions` (the shipped `StepConditions` escape-hatch is\nalready open-ended, `policy-schema.ts:26-41`) and evaluated in `conditionsHold`\n(`policy-evaluator.ts:110-146`) with a new `tool-result` branch. All predicates run **only after** the\nenvelope is verified (OP-6), so they read signed fields:\n\n- **`exitCode`** — `require.exitCode` MUST equal `payload.exitCode` (**OP-17a**).\n- **`stdoutHash` / `stderrHash`** — an exact expected hash MUST equal the signed hash (**OP-17b**).\n- **`stdoutMatches` / `stderrMatches`** — a full-string-anchored regex (compiled like `modelIdMatches`,\n  `policy-evaluator.ts:120-128`) over the captured output; requires the raw output be carried alongside\n  the proof and re-hashed to the signed `stdoutHash` before matching, else deny (the proof binds the\n  hash; pattern-match is over the hash-verified bytes) (**OP-17c**).\n- **`freshness` / expiry** — `now - finishedAt <= maxAgeMs` (from the step) AND, when the step sets\n  `notExpired`, honored via the existing `notExpired` operator convention (**OP-17d**).\n- **command/args binding** — `commandMatches` (regex over the recomputed canonical argv, tokenized by\n  the SHARED `canonicalizeArgv`) and/or `argsHashEquals` MUST match `payload.commandHash`/`argsHash`\n  (**OP-17e**) — this is what makes the wrong-command-swap attack (§7 OP-T2) impossible: the proof's\n  bound `commandHash` must match the required command, not a different command.\n- **`signedBy: tool-executor`** — enforced structurally by the kind→`tool-executor` mapping (OP-5); a\n  step MAY also set `requireExecutorProducer` / `requireAttestationScope` (OP-18).\n\nAny predicate miss → the requirement is unsatisfied → the chain does not grant → deny (fail closed, the\nshipped `evaluateChain` returns unsatisfied at the first miss, `policy-evaluator.ts:443-447`).\n\n### 6.3 OP-18 — `requireExecutorProducer` / `requireAttestationScope` (inner-command vs harness scope)\n\nMirroring the shipped `requireProxyAttestation` (`policy-schema.ts:83-85`,\n`policy-evaluator.ts:224-230`, `:259-264`), a `tool-result` step MAY set:\n\n- `requireExecutorProducer: 'proven-shell'` — accept only proofs from the authoritative\n  proven-execution runner, rejecting a harness-scope spawn-layer proof; and/or\n- `requireAttestationScope: 'inner-command'` — accept only proofs whose signed `attestationScope`\n  proves an inner command (as opposed to `'harness-invocation'`).\n\n**Default:** for any action whose `match` names a `credentialScope` (i.e. can inject a scoped\ncredential), both default to the authoritative inner-command posture (`producer:'proven-shell'`,\n`attestationScope:'inner-command'`), matching the AC-39 credential-default posture; non-credential\nactions may accept a harness-scope proof if the policy explicitly opts in. An explicit value opts out.\nThis makes the honest producer-scope boundary of §5.2/§5.3 enforceable in policy — the `npm test`\nexample **must** use a proven-shell proof, and the evaluator rejects a spawn-layer harness proof\nsubstituted for it.\n\n### 6.4 OP-19-A — Worked example (the mandated \"authorize aws deploy only if npm test provably passed\")\n\nThe `npm test` step runs through the **proven-execution runner** (the SDK executes `npm test`,\ncaptures exit 0, signs — §5.2), and the aws-deploy policy's `tool-result` step requires that\nproven-shell attestation. The command is defined by the process as a `proven-shell` task; the deploy\npolicy binds to the resulting proof:\n\n```yaml\nversion: 1\nauthorizationTtlSeconds: 120\ncommandDefaultAllow: false\ndefaultDeny: [\"aws:*\"]\nactions:\n  - id: aws-deploy-if-tests-passed\n    match:\n      tool: Bash\n      argv:\n        program: aws\n        subcommandEquals: [\"deploy\"]\n      credentialScope: \"aws:prod:deploy\"\n    # credentialScope present ⇒ requireProxyAttestation defaults true (model step)\n    #                        ⇒ requireExecutorProducer defaults 'proven-shell'  (tool-result step, OP-18)\n    #                        ⇒ requireAttestationScope   defaults 'inner-command'(tool-result step, OP-18)\n    chains:\n      - id: human-opus-and-tests-passed\n        requirements:\n          - step: { kind: human-approval }                       # a person approved   (precondition)\n          - step:\n              kind: model-decision\n              conditions: { modelIdMatches: \"^claude-opus-\" }    # opus decided        (precondition)\n          - step:\n              kind: tool-result                                   # AND tests PROVABLY passed (OUTCOME PROOF)\n              conditions:\n                commandMatches: \"^npm(\\\\s+run)?\\\\s+test\\\\b\"       # binds proof.commandHash (OP-17e)\n                require: { exitCode: 0 }                          # OP-17a\n                maxAgeMs: 1800000                                 # 30-min freshness (OP-17d)\n                requireExecutorProducer: \"proven-shell\"           # authoritative inner-command only (OP-18)\n                requireAttestationScope: \"inner-command\"          # a harness-invocation proof is REJECTED\n              # signedBy: tool-executor is structural (OP-5)\n```\n\nThe process that runs the tests:\n\n```js\n// The SDK ITSELF executes npm test and signs over what it observed — no task:post, no agent value.\nawait ctx.provenShell({ command: 'npm', args: ['test'] });   // kind:'proven-shell' → SDK runs it (§5.2)\n```\n\nThe `aws deploy` command is authorized only if the evidence set for the call contains a valid\n`tool-executor`-signed `ToolResultAttestation` whose signed `producer` is `proven-shell`, whose bound\n`commandHash` matches `^npm ... test`, and whose signed `exitCode` is 0, in addition to the\nhuman-approval + opus model-decision preconditions. Because the SDK — not the agent — produced that\nproof by running the command, a compromised agent cannot mint it. The `tool-result` evidence is bound\ninto the resulting `CommandAuthorization`'s `evidenceStepBindings` (`policy-evaluator.ts:448-452`)\nexactly like every other evidence, so the gate confirms it enforced the proven-test precondition.\n\n---\n\n## 7. The babysitter-process gate: `provenShell` + `requireProof` (Milestone H, consumer B)\n\n### 7.1 OP-20 — `ctx.provenShell` task option (replaces the Draft-1 `attestResult` flag)\n\n**OP-20** adds a `ctx.provenShell({ command, args, timeout?, cwd?, env? })` intrinsic that resolves a\n`kind: 'proven-shell'` effect **in-line in the SDK runtime** (§5.2; it never enters an agent-observable\n`requested` state, §5.4.1 / OP-N2). **Per OP-N1, the agent-facing `cwd`/`env` options are constrained,\nnot honored verbatim:** `command` is resolved by the runner to a trusted absolute path (an\nagent-supplied `PATH` cannot redirect it); `env` may only ADD non-resolver variables (the recommended\nposture drops the option and derives env wholly from the SDK — `PATH`/`PATHEXT`/`NODE_OPTIONS`/loader\nvars and the executor key are never taken from or leaked to the agent); and `cwd` is rejected if it\nescapes the run workspace. Draft 1's `attestResult?: boolean` flag on a `kind:'shell'` task\nis **removed** — it declared intent to attest an agent-reported value, which OP-11-DEL forbids.\nAttestation is not a flag on an agent-executed task; it is intrinsic to the `proven-shell`\nkind, whose defining property is that the **SDK executes it**. When the anchor is pinned, a\n`proven-shell` effect yields a `ToolResultAttestation` over the SDK-observed outcome (OP-13); when the\nanchor is not pinned, it runs and returns the result but attaches no attestation (back-compat). The\ntrust comes from the executor key + manifest anchor (OP-9) and from the SDK owning execution — never\nfrom an agent-set flag.\n\n### 7.2 OP-19 — The `ctx.requireProof` intrinsic (a step BLOCKS without a matching valid attestation)\n\n**OP-19** adds a `requireProof` intrinsic to `babysitter-sdk`, registered in\n`packages/babysitter-sdk/src/runtime/intrinsics/index.ts` and exposed as `ctx.requireProof(...)`\nalongside the shipped `ctx.task` / `ctx.breakpoint` (`runtime/intrinsics/task.ts`, `.../breakpoint.ts`).\nIt scans the append-only journal / task results of the current run for a `StoredTaskResult` carrying\n`metadata.toolResultAttestation` (OP-13) that:\n\n1. verifies via `verifyToolResultTrusted(envelope, store, ...)` against the manifest-verified\n   `tool-executor` roots (OP-6/OP-9) — the store is loaded through the same\n   `loadTrustedBreakpointPolicy`-style dynamic-import path (`trusted-breakpoint-policy.ts:218-246`) so\n   the SDK's CJS build stays clean (OP-28); **and**\n2. satisfies the caller's predicates (`command` regex → `commandHash` binding, `result.exitCode`,\n   freshness, `signedBy`/`requireExecutorProducer`/`requireAttestationScope`) — the **same** predicate\n   set as OP-17, shared code; **and**\n3. is bound to **this** run (`payload.runId === current runId`, OP-14) and is fresh (OP-14).\n\nIf a matching valid attestation exists → `requireProof` resolves and the process advances. If none\nexists → the intrinsic **throws** (`RunFailedError`, the shipped fail-closed exception used at\n`commitEffectResult.ts:279`), so **the step blocks**. An orchestrator that did not actually run the\ncommand (no `proven-shell` effect, hence no attestation), or whose command failed (`exitCode !== 0` →\npredicate miss), or ran a different command (`commandHash` mismatch), **cannot advance** — the\nrequirement is checked against a signed artifact the SDK produced by observing execution, never the\nagent's narrative and never an agent-reported `task:post` value.\n\nCopy-pasteable:\n\n```js\n// The step that runs the tests: the SDK EXECUTES npm test and signs over what it observed (§5.2).\nawait ctx.provenShell({ command: 'npm', args: ['test'] });   // kind:'proven-shell'\n\n// A later step / breakpoint gate requires the signed proof — not the agent's word.\nawait ctx.requireProof({\n  command: /^npm(\\s+run)?\\s+test\\b/,        // binds the proof's commandHash (OP-17e)\n  result:  { exitCode: 0 },                 // OP-17a\n  maxAgeMs: 1_800_000,                      // freshness (OP-17d / OP-14)\n  signedBy: 'tool-executor',                // structural (OP-5)\n  requireExecutorProducer: 'proven-shell',  // authoritative inner-command only (OP-18)\n  requireAttestationScope: 'inner-command', // a harness-invocation proof is REJECTED\n});\n// → no valid proven-shell attestation in the journal for THIS run → the step throws → blocks.\n```\n\n### 7.3 OP-21 — Single-use consumption where required (journal-anchored ledger, OP-27)\n\nA `requireProof` call MAY set `singleUse:true`; when so, the consumed attestation's\n`toolCallId`+`runId`+`finishedAt` triple is recorded in the **journal-anchored** proof-consumption\nledger (OP-27) and a second `requireProof` for the same triple denies — preventing one genuine proof\nfrom satisfying two distinct gates that each demanded a fresh execution. The ledger is **NOT** an\nagent-writable `.a5c` file (§10, OP-27). Default is multi-use (a proof of a fact can be read by several\nobservers) unless the caller declares the fact must be freshly produced per gate.\n\n### 7.4 OP-22 — Optional proof-gated breakpoint auto-approval\n\nThe shipped breakpoint auto-approval engine (`evaluateAutoApproval`, wired at\n`runtime/intrinsics/task.ts:163-177`; rules from `breakpoints/rules.ts`) is a natural consumer.\n**OP-22** adds a `requireProof` clause to an auto-approval rule: a breakpoint auto-approves **only if** a\nmatching valid `ToolResultAttestation` exists (evaluated through the same OP-19 verify + predicate path).\nWithout a matching proof, the rule does not fire and the breakpoint falls through to human input — so an\nauto-approval cannot be driven by an agent's unproven claim. This is additive to the existing rules\nengine; absent the clause, behavior is unchanged.\n\n---\n\n## 8. Threat model (Milestone H test targets)\n\nEach row is a stable-id AC with the blocking mechanism grounded in shipped code, and an honest boundary\nwhere the guarantee is bounded.\n\n- **OP-T1 — forged / never-ran result.** The orchestrator emits \"tests passed\" it never ran. **Blocked:**\n  the only path that mints a `proven-shell` proof is the SDK runtime running the command itself (§5.2),\n  so there is no `task:post` value to forge — the agent cannot report a passing outcome the SDK did not\n  observe. The `proven-shell` **kind gate** (OP-N2) makes this explicit: `task:post` and\n  `commitEffectResult` **reject** a `proven-shell` effect, and it never enters an agent-observable\n  `requested` state, so the agent cannot supply `value` or a forged `metadata.toolResultAttestation` for\n  it. A `kind:'shell'` task the agent runs out-of-band produces **no** attestation, so a\n  `requireProof`/`tool-result` step denies (OP-19/OP-16). The agent cannot mint a proof — the executor\n  key is off-agent (OP-9, stripped from the child's env by OP-N1), and `verifyEnvelopeTrusted` resolves\n  keys only from the manifest-verified store, rejecting any non-executor key (OP-7). *Boundary\n  (OP-B-KEY):* the guarantee that the *observed* outcome of the *resolved absolute program* (OP-N1) is\n  genuine is unconditional whenever the runner runs; the only residual assumption is key-custody (the\n  executor key must be off-agent), the same posture as the proxy key (§5.2).\n- **OP-T2 — wrong-command swap.** The agent presents a real proof of a *different* command as the\n  required one. **Blocked:** the proof binds `commandHash`/`argsHash` (OP-2) over the argv the executor\n  actually ran; the step/gate's `commandMatches`/`argsHashEquals` predicate (OP-17e) must match the\n  bound hash, and the policy argv matcher is the anti-evasion `matchArgv` (`policy-evaluator.ts:509-547`)\n  — a disguised wrapper (`sh -c`, `$(...)`) is denied, not matched. The proven-shell runner itself\n  refuses a non-bare-program `command` (§5.2), so a shell-smuggled swap never even executes.\n- **OP-T3 — replay / staleness.** The agent replays a stale proof from an old run/step. **Blocked\n  (OP-14):** the payload binds `runId`/`sessionId`/`toolCallId` (in `signedFields`, OP-5) and the\n  consumer requires `payload.runId === current runId` + `now - finishedAt <= maxAgeMs` (OP-17d);\n  `singleUse` (OP-21) prevents intra-run double-satisfaction where the gate demands a fresh execution.\n- **OP-T4 — tamper (stored attestation or trust roots).** The agent edits the stored envelope or the\n  trust-roots file. **Blocked:** editing the envelope invalidates the Ed25519 signature (OP-6 step g);\n  editing `.policy/trust-roots.json` fails the config-manifest hash/epoch check (`config-manifest.ts:88-123`,\n  base §10.1 AC-46/47) → deny-all. The pins (`POLICY_CONFIG_ROOT_FP`, `POLICY_CONFIG_MIN_EPOCH`) are\n  off-workspace (OP-9).\n- **OP-T5 — producer-scope substitution.** The agent presents a real **spawn-layer** (harness-invocation)\n  proof where an inner-command proof is required. **Blocked:** `producer`/`attestationScope` are signed\n  (OP-3) and the credential-default policy requires `producer:'proven-shell'` /\n  `attestationScope:'inner-command'` (OP-18); a harness-scope proof is rejected. This is the honest\n  encoding of the §5.3 scope boundary — the spawn-layer proof is genuine but attests a *different fact*.\n- **OP-T6 — TOCTOU (capture-then-sign).** A result changes between running and attesting. **Blocked\n  (OP-15):** both signers sign over the **captured** result at capture time — the proven-shell runner\n  over the exact bytes `execFileSync` returned to it, the spawn signer over the captured\n  `exitCode`/`stderrBuf`/stdout at the `exit` event (`spawn-runner.ts:591-613`). The signed\n  `stdoutHash`/`stderrHash` bind those exact bytes; a later re-read that differs cannot match.\n  **OP-15b (predicate-source integrity):** the `exitCode`/`stdoutHash`/`stderrHash` the predicates read\n  come **ONLY** from runner-captured bytes — never from any agent-supplied or separately-read source.\n  If `execFileSync` throws because `maxBuffer` was exceeded or the `timeout` elapsed, there is **no\n  complete capture**, so the runner **DENIES** — it MUST NOT emit a partial-capture attestation over\n  truncated bytes. This keeps the OP-15 TOCTOU binding airtight: a proof exists only over a fully\n  captured, runner-observed result.\n- **OP-T7 — cross-kind / cross-payload-type confusion.** A `model-decision` or `command-authorization`\n  envelope presented as a `tool-result`. **Blocked:** `payloadType` domain separation (OP-3) + kind\n  binding (OP-7) — no non-`tool-executor` root and no non-`tool-result-attestation` payload satisfies a\n  `tool-result` step.\n- **OP-N1 — executor PATH/env/cwd hijack (IN SCOPE, blocked).** The agent supplies `env.PATH` pointing\n  a bare `npm` at a workspace shim that exits 0 (or a relative program, or an escaping `cwd`), aiming to\n  make the runner honestly sign an inner-command proof over a binary the agent controls — **without\n  touching `package.json`**. **Blocked (OP-N1, §5.2):** the runner resolves the program to a **trusted\n  absolute path itself** (rejecting an unresolvable/untrusted program), runs under a **sanitized\n  SDK-derived env** (agent `PATH`/loader vars dropped; executor key stripped), and rejects a `cwd` that\n  escapes the run workspace. Binary/argv/cwd/env provenance is the SDK's, not the agent's, and the bound\n  `commandHash` names the *resolved absolute program*. This is a **strictly different** attack from OP-T8\n  (which is about the *contents* of a legitimately-resolved script), and it is **closed**, not deferred.\n  *Red tests:* (a) an agent-supplied `env.PATH` shim is **not** run — the runner resolves the trusted\n  absolute binary instead (or denies); (b) a relative-program `command` is **rejected**; (c) an escaping\n  `cwd` is **rejected**; (d) `env.PATH`/`NODE_OPTIONS` override attempts are dropped or rejected, and\n  `POLICY_TOOL_EXECUTOR_KEY` never reaches the child.\n- **OP-T8 — workspace test-script CONTENTS tampering (OUT OF SCOPE, NARROWED, stated).** The agent\n  rewrites the workspace's own `package.json` `test` script to a no-op that exits 0, then has the SDK\n  prove `npm test` exit 0 — where `npm` is the *legitimately-resolved absolute binary* (OP-N1 held) and\n  the *cwd/env were the SDK's*. The proof is *honest* — the resolved `npm` genuinely exited 0 — but the\n  *meaning of the script CONTENTS* is subverted upstream of the execution seam. This narrow residual —\n  **only the contents of a legitimately-resolved script**, NOT which binary/argv/cwd/env resolves (those\n  are OP-N1, in scope) — is an orthogonal workspace-integrity / supply-chain concern (§1.2), **not\n  addressed** by outcome proofs, and called out plainly so no one over-reads the guarantee. Mitigations\n  (attesting a pinned/hashed script, running in a read-only or vetted image via the spawn-layer seam) are\n  future work.\n\n---\n\n## 9. Milestone map & AC index\n\n| AC | What | Milestone |\n|----|------|-----------|\n| OP-1 | Extend `ToolResultPayload` → `ToolResultAttestationPayload` (+ `producer`/`attestationScope`) | **F** |\n| OP-2 | `argsHash`/`commandHash`/`stdoutHash`/`stderrHash` via shared canonicalizers, over executor-run argv | **F** |\n| OP-3 | `payloadType`/`producer`/`attestationScope` bound in `signedFields` (domain separation) | **F** |\n| OP-4 | `tool-executor` trust-root kind added to `TrustRootKind` | **F** |\n| OP-5 | `tool-result` `EvidenceKind` + required-fields + kind mapping | **F** |\n| OP-6 | `verifyEnvelopeTrusted`/`verifyToolResultTrusted` resolves + kind-checks executor | **F** |\n| OP-7 | Cross-kind rejection: no non-executor key satisfies `tool-result` | **F** |\n| OP-8 | Expiry / revocation of the executor root honored | **F** |\n| OP-9 | Verify + key anchored to manifest-verified config, never agent input | **F** |\n| OP-10 | `ToolExecutorSigner` interface (+ `producer`) + `resolveToolExecutorSigner` | **F** |\n| OP-11 | **Proven-execution runner** (`kind:'proven-shell'`): SDK executes + observes + signs (fail-closed) | **G** |\n| OP-N1 | **Runner controls binary/argv/cwd/env** — trusted absolute program + sanitized SDK env + workspace-constrained cwd; agent PATH/env/relative-program/escaping-cwd rejected | **G** |\n| OP-N2 | **`proven-shell` kind gate** on `task:post` + `commitEffectResult`; no agent-observable `requested` state; in-line resolution + replay reuse (command not re-run) | **G** |\n| OP-11-DEL | **REMOVED** the \"sign agent-reported `task:post` value\" producer — not an outcome proof | **G** |\n| OP-12 | Spawn-layer signer: signs the launched HARNESS argv exit (harness-invocation scope) | **G** |\n| OP-13 | Attestation rides `StoredTaskResult.metadata.toolResultAttestation` (SDK-produced value only) | **G** |\n| OP-14 | Replay binding: `runId`/`sessionId`/`toolCallId` + freshness | **G**/**H** |\n| OP-15 | TOCTOU: sign over captured result at capture time | **G** |\n| OP-15b | Predicates read `exitCode`/`stdoutHash`/`stderrHash` ONLY from runner-captured bytes; maxBuffer/timeout ⇒ DENY, no partial-capture attestation | **H** |\n| OP-16 | `tool-result` policy step type (no evaluator change to add a policy) | **H** |\n| OP-17 | Result predicates (exitCode/hash/pattern/freshness/command-args binding) | **H** |\n| OP-18 | `requireExecutorProducer` + `requireAttestationScope` (inner-command vs harness scope) | **H** |\n| OP-19 | `ctx.requireProof` intrinsic — step BLOCKS without a valid attestation | **H** |\n| OP-19-A | Worked \"aws deploy only if npm test PROVABLY passed\" policy YAML (via `proven-shell`) | **H** |\n| OP-20 | `ctx.provenShell` intrinsic (replaces the Draft-1 `attestResult` flag) | **H** |\n| OP-21 | Single-use proof consumption where required (journal-anchored ledger) | **H** |\n| OP-22 | Optional proof-gated breakpoint auto-approval | **H** |\n| OP-27 | Proof-consumption ledger in the append-only journal / manifest-anchored store (NOT `.a5c`) | **H** |\n| OP-28 | Architecture-boundary + metadata/lockfile constraints honored | **F/G/H** |\n| OP-30 | Non-goals | — |\n\nEvery `OP-N` maps to exactly one of F (attestation-core), G (producers), H (consumers), except OP-14\n(payload field is F/G, freshness check is H) and OP-28 (a cross-cutting build constraint). OP-11-DEL is\na removal recorded under G for traceability. The **Draft-3 additions** map cleanly: **OP-N1** (runner\nbinary/argv/cwd/env provenance) and **OP-N2** (`proven-shell` kind gate + replay reconciliation) are\n**Milestone G producer** requirements; **OP-15b** (predicate-source integrity) is **Milestone H**, since\nit constrains what the consumer predicates are permitted to read (the maxBuffer/timeout DENY is enforced\nby the G runner, but the *predicate-source* rule it guarantees is a consumer-facing property). No\npreviously-closed point is regressed; the sound F/H verify/consume mechanics are unchanged.\n\n---\n\n## 10. Reuse ledger, boundaries, constraints (OP-27 / OP-28)\n\n**OP-27 — reuse the spine; no new package; ledger is NOT agent-writable.**\n\n| Concern | Reused / extended artifact (file) | New? |\n|---------|-----------------------------------|------|\n| Signed envelope + canonical form | `trust-core/src/signing.ts:24-65` | reuse |\n| Tool-result payload + sign/verify | `trust-core/src/tool-signing.ts:4-34` | **extend** (add bound fields + `payloadType`/`producer`/`attestationScope`) |\n| Shared arg/argv/output hashing | `policy/src/canonicalize-args.ts:77-85` | reuse |\n| Trusted-store verify + kind check | `policy/src/verify-envelope-trusted.ts:237-329` | **extend** (add `tool-executor`/`tool-result`) |\n| Config-manifest anchor | `policy/src/config-manifest.ts:67-133` | reuse |\n| Policy schema + evaluator | `policy/src/policy-schema.ts`, `policy-evaluator.ts` | **extend** (`tool-result` step + predicates) |\n| Executor signer | `policy/src/tool-executor-signer.ts` | **NEW module** (in existing package) |\n| **Proven-execution runner** | `babysitter-sdk/src/runtime/proven-shell/runner.ts` + `intrinsics/provenShell.ts` | **NEW module** (in existing package) — the SDK-owned execution seam; resolves trusted absolute program + sanitized env + workspace cwd (OP-N1) |\n| **`proven-shell` kind gate** | `cli/main/taskCommands.ts:165-173` (task:post) + `runtime/commitEffectResult.ts:35-38` | **extend** (reject `record.kind==='proven-shell'` — agent cannot post value/metadata, OP-N2) |\n| Spawn-layer signer | `adapters/core/src/spawn-runner.ts:568-613`; gate ctx `policy-spawn-gate.ts:70-96` | **extend** (bind `commandHash` to launched argv, `attestationScope:'harness-invocation'`) |\n| Attestation storage | `babysitter-sdk/src/tasks/serializer.ts:150-185`; `storage/types.ts:119-141` | reuse (`metadata`) |\n| `requireProof` intrinsic | `babysitter-sdk/src/runtime/intrinsics/` | **NEW intrinsic** (extends the intrinsic set) |\n| Proof-consumption ledger | **append-only journal event** (new `PROOF_CONSUMED` event) or a manifest-anchored run-scoped index — **NOT** an agent-writable `.a5c` file | reuse (journal) |\n| Proof-gated auto-approval | `babysitter-sdk/src/breakpoints/evaluator.ts` (wired `task.ts:163-177`) | **extend** (optional clause) |\n| ~~SDK-commit signer over `task:post` value~~ | ~~`commitEffectResult.ts`~~ | **REMOVED (OP-11-DEL)** — `commitEffectResult` is NOT modified to sign `options.result` |\n\nThe **genuinely new modules** are `policy/src/tool-executor-signer.ts`, the proven-execution runner\n(`babysitter-sdk/src/runtime/proven-shell/runner.ts` + its `provenShell` intrinsic — the one new\nexecution seam, described precisely in §5.2), and the `requireProof` intrinsic — all **inside existing\npackages**. **No new package** is created (OP-30). If a future deployment needs a standalone\nout-of-process executor daemon or a KMS-backed signer service, that would be a new package and must be\nclassified + `private:true` + metadata-passing (`scripts/check-package-metadata.cjs`) — explicitly out\nof scope here.\n\n**Single-use ledger placement (OP-27, MUST).** The proof-consumption record for `singleUse` (OP-21)\nlives in the **append-only journal** (a new `PROOF_CONSUMED` event carrying the\n`toolCallId`+`runId`+`finishedAt` triple) or a manifest-anchored run-scoped store — the same durable,\nintegrity-covered artifact class the attestation itself rides. It is **never** an agent-writable\n`.a5c/**` file, so an agent cannot erase a consumption record to double-spend a single-use proof.\n\n**OP-28 — architecture-boundary + metadata/lockfile constraints.**\n- Crypto stays in `trust-core` (support-systems leaf, `packages/trust-core/package.json`\n  `private:true`, `type:commonjs`); `trust-core` gains **no** dependency on `policy-adapter` — the\n  producers compute the shared hashes in the SDK/adapters layers (which already reach `policy-adapter`)\n  and hand a finished payload to `signToolResult` (OP-2). The pre-push\n  `scripts/check-architecture-boundaries.cjs` gate is respected: **no `dispatch-core →\n  orchestration-core` edge** is introduced. `babysitter-sdk` consumes `policy-adapter` (ESM) only via\n  **dynamic `import()`**, the proven pattern at `trusted-breakpoint-policy.ts:218-226` and\n  `policy-spawn-gate.ts:76` — no new static ESM edge in the CJS SDK build.\n- The proven-execution runner's `node:child_process` use is confined to the new\n  `babysitter-sdk/src/runtime/proven-shell/runner.ts` module (SDK runtime layer, which is where the\n  runtime already lives); it introduces no cross-package edge and no new dependency.\n- Windows lockfile: no bare `npm install`; use `--package-lock-only`, verify no win32 pins (project\n  memory: Windows npm install pollutes lockfile). `execFileSync` is `argv`-form, `shell:false`, so it is\n  Windows-safe without shell-quoting (and refuses non-bare-program commands, §5.2).\n- Any new package would need metadata-passing; we add none (OP-30).\n- Tests-first, frozen; adversarial security review at each crypto/enforcement milestone; introduce no\n  NEW `babysitter-sdk` test failures (7 known pre-existing session-state/`task_cancel` failures are\n  unrelated and not counted). Commit specific product files only; never `.a5c/**` or lockfiles in agent\n  commits.\n\n---\n\n## 11. Non-goals (OP-30, consolidated)\n\n- No new package; no third evaluator; no online executor-key revocation beyond manifest-epoch + `revoked`.\n- No semantic interpretation of stdout/stderr content beyond declared predicates.\n- **No attestation of an agent-reported `task:post` value** — that is not an outcome proof of any grade\n  (OP-11-DEL). The only proofs are over executions a seam performed and observed: the SDK-owned\n  proven-execution runner (inner command) and the spawn layer (harness invocation).\n- No attestation of executions outside those two seams — anything else is unproven and denies.\n- **No workspace-script CONTENTS integrity guarantee (NARROWED)** — a proven `npm test` exit 0 proves the\n  *resolved absolute binary ran under the SDK's env/cwd and returned 0* (which binary/argv/cwd/env resolve\n  is IN scope, OP-N1), but **not** that the *contents* of the workspace's test script are trustworthy\n  (OP-T8, §1.2). Only the script-contents residual is orthogonal / out of scope; a PATH/binary/cwd/env\n  hijack is closed by OP-N1.\n- No claim that a spawn-layer harness-invocation proof satisfies an inner-command requirement; the two\n  scopes are signed and a policy/gate rejects the wrong one (OP-18, OP-T5).\n",
    "documents": []
  },
  "outgoingEdges": [],
  "incomingEdges": [
    {
      "from": "page:docs",
      "to": "page:docs-design-proof-based-policy-enforcement-outcome-proofs",
      "kind": "contains_page"
    }
  ]
}

Shortcuts

Back to overview
Open graph tab