@sema-agent/core 7.5.2 → 7.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -0,0 +1,242 @@
1
+ /**
2
+ * The tool gate's RECORD FLOOR — everything about how one gate pass is recorded and how it ends:
3
+ *
4
+ * - the ONE mint of a {@link GateOutcome} ({@link mintGateOutcome}) — the record of what a tool-gate pass
5
+ * decided, what ask it settled and who asked. The gate accumulates two facts on its way to an exit (WHO
6
+ * refused, written once at the refusing site; WHAT was settled, written at the resolution site) and hands
7
+ * them here; the mint assembles the record, runs the invariant screen, and refuses a record the engine
8
+ * composed wrong. Every projection of the outcome (the deny observer's payload, the call's `tool_end`
9
+ * frame, the engine's per-call sideband) is the object minted here, never a re-derivation;
10
+ * - the settlement LEDGER of a pass ({@link createSettlementLedger}) — the one rule every attestation read
11
+ * goes through;
12
+ * - the PASS RECORD ({@link GatePass}) — the per-call cells the gate's station groups read and write, and
13
+ * the hook-machinery seats they borrow, built once per call by `runToolGate` and handed to every station;
14
+ * - the EXITS — the two phase-1 short-circuits ({@link engineFailClosedExit}, {@link hookDenyExit}) and the
15
+ * single deny / allow exit of the adjudicate chain ({@link exitGate}), the only places a pass turns into a
16
+ * `ToolGateResult`; plus the two observer-facing helpers every exit uses (the payload isolation clone and
17
+ * the deployment-lane crash report).
18
+ *
19
+ * A floor module (layer 0 of the prepare/run path): the two station modules (`gate-fold.ts`, `gate-lanes.ts`)
20
+ * and the gate's dispatch in `hooks.ts` all reach DOWN for it, and it reaches down only for the vocabulary,
21
+ * the reminder envelope and the text helpers. Its `import type` edges up to `hooks.ts` name the gate's public
22
+ * contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
23
+ */
24
+ import { type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
25
+ import type { AskOrigin, AskOriginFacts } from "../ask-origin.js";
26
+ import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
27
+ import type { SafeNotifier } from "../safe-notify.js";
28
+ import type { HookSeatOutcome, PermissionDeniedPayload, PreToolUseResult, ToolGateInput, ToolGateResult } from "../hooks.js";
29
+ /** The ask a gate pass settled, as the gate captured it at its resolution site: the settlement the
30
+ * resolver composed and the origin word the ask carried. Both or neither (invariant I1). */
31
+ export interface SettledAsk {
32
+ settlement: Settlement;
33
+ origin: AskOrigin;
34
+ }
35
+ /** The facts a gate pass hands the mint. `deniedBy` is present exactly when the pass refused; `settled`
36
+ * exactly when the pass consumed an ask's settlement (a person's yes that was later vetoed included). */
37
+ export interface GateExitFacts {
38
+ deniedBy?: DeniedBy;
39
+ settled?: SettledAsk;
40
+ }
41
+ /**
42
+ * Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
43
+ * settlement without its origin, a person's yes beside a non-veto layer's deny) and throws: the gate's
44
+ * own composition is not a caller input, so there is no fail-closed "deny" to fall back to that would be
45
+ * more honest than the fault.
46
+ */
47
+ export declare function mintGateOutcome(facts: GateExitFacts): GateOutcome;
48
+ /** A direct refusal by one layer, with no ask settled — the five short-circuit exits (a hook that threw or
49
+ * never answered, the hook's own deny, the compliance lock, the plan-mode block). */
50
+ export declare function directDeny(deniedBy: Exclude<DeniedBy, "ask_resolution">): GateOutcome;
51
+ /**
52
+ * The gate's settlement LEDGER for one pass — the one place "which ask did this pass consume" is written,
53
+ * with ONE rule for every attestation read (the main adjudication, the approval-edit re-check, the gate's
54
+ * own ask resolutions), so the three sites cannot disagree about what an attested decision means:
55
+ * - an engine attestation on a decision object (the approval factory's wait ended inside a policy
56
+ * `check()`; an inherited lane resolved an ancestor's ask) is a settled ask of THIS call — read off the
57
+ * identity-keyed seat, never off a caller-composable member;
58
+ * - a deny carrying a REFUSAL attestation is the ask's own resolution (`ask_resolution`); a deny carrying a
59
+ * person's YES is a later layer's VETO (the yes stays on the settlement, the layer is the disposition —
60
+ * invariant I3);
61
+ * - the LATEST resolution wins (a re-check refusal replaces the first approval; a re-approval renews it);
62
+ * - a SURVIVING ask supersedes any settlement read off an earlier decision (a tighten minted a new
63
+ * question; whoever resolves it settles it afresh).
64
+ * For a resolver's object the attestation outranks the exposed member; the member is the fallback for a
65
+ * resolver seat that composes without attesting (a hand-built resolver; the ask lane's own intercept
66
+ * deny). TRUST BOUNDARY, stated: `ToolGateInput.resolveAsk` is wired by prepare from the engine's own
67
+ * resolver — a wrapper that spreads a resolved object (dropping the identity-keyed attestation) and
68
+ * REPLACES the member is inside the engine's trust line, not a deployment's; making the member
69
+ * attestation-only is the follow-up that migrates the ask lane's composition and the test resolvers
70
+ * onto an engine-owned mint.
71
+ */
72
+ export interface SettlementLedger {
73
+ /** The ask this pass consumed, or undefined when no ask was settled (a direct deny, a plain allow). */
74
+ readonly settled: SettledAsk | undefined;
75
+ /** An engine-composed settlement the gate itself minted (the unshown content ask's refusal). */
76
+ set(settled: SettledAsk): void;
77
+ /** Record an ask's resolution: the resolver's settlement beside the ask's own origin word. */
78
+ fromResolved(resolved: ResolvedAsk, asked: PermissionResult, toolName: string): void;
79
+ /** Read an adjudication's attestation. For a deny, returns the refusing layer word (`ask_resolution`
80
+ * for an attested refusal, `policy` otherwise — a veto when a yes rides the settlement); undefined
81
+ * for an allow or an ask. */
82
+ fromDecision(decision: PermissionResult): DeniedBy | undefined;
83
+ /** A surviving ask supersedes whatever an earlier decision settled. */
84
+ supersede(): void;
85
+ }
86
+ export declare function createSettlementLedger(call: {
87
+ toolCallId: string;
88
+ toolName: string;
89
+ }): SettlementLedger;
90
+ /**
91
+ * 1.256 复审 MED-1 — observe-only payload isolation for {@link import("../hooks.js").Hooks.permissionDenied}:
92
+ * clone the tool args before they ride the observer payload, so a hook mutating `payload.input` can never
93
+ * pollute the LIVE args object (later events / audit records share it). Same posture as the postToolUseFailure
94
+ * details clone (the gate-stations phase): `structuredClone` first; a non-structured-cloneable graph
95
+ * (functions/handles) falls back to a SHALLOW plain object/array copy (top-level mutation isolated);
96
+ * a non-object primitive passes through as-is (immutable anyway).
97
+ */
98
+ export declare function cloneObserverInput(input: unknown): unknown;
99
+ /** RB-442: hand the crash itself to the deployment's trace lane (`onError(phase:"hook")`), never letting a
100
+ * throwing sink alter the fail-closed outcome — the same swallow posture as every other observer here.
101
+ * Shared by the gate's dispatch (the phase-1 crash arm, the seat-bound refusal), the reversibility probe's
102
+ * two failure arms and the approved-edit re-screen. */
103
+ export declare function traceHookCrash(input: ToolGateInput, err: unknown, notifier: SafeNotifier): void;
104
+ /**
105
+ * The PreToolUse screening face as the gate's stations see it — the hook machinery of `runToolGate` (the seat
106
+ * runner, the per-call seat bound, the fresh per-screening context, the result screen, the two fail-closed
107
+ * reason texts) behind three methods, so the approved-edit re-screen in the lanes runs the SAME screening the
108
+ * phase-1 pass ran without the lanes module reaching into the hook machinery. Present on the pass exactly when
109
+ * a `preToolUse` face is wired.
110
+ */
111
+ export interface PreToolUseScreening {
112
+ /** Run the deployment's PreToolUse face over `args` under the seat bound. The answer is the SEAT's, unscreened —
113
+ * the caller passes it through {@link PreToolUseScreening.screened} in the seat's own continuation. The seat
114
+ * promise is returned as is (no wrapping `async`): what the gate does after the face answers — the policy in
115
+ * phase 1, the re-check in the edit chain — runs on the tick the seat settles, as it did when this call was the
116
+ * seat runner's own; a wrapper that screened the answer inside a second `async` cost one more tick there. An
117
+ * expired seat is reported, not thrown; a crashing face rejects. */
118
+ screen(args: unknown): Promise<HookSeatOutcome<PreToolUseResult | undefined>>;
119
+ /** The result screen over an answered seat's value: an out-of-contract decision becomes the ordinary hook deny. */
120
+ screened(answer: PreToolUseResult | undefined): PreToolUseResult | undefined;
121
+ /** The model-facing text for a face that THREW while screening `subject`. */
122
+ crashReason(subject: string, err: unknown): string;
123
+ /** The model-facing text for a face that never ANSWERED while screening `subject` (the timeout arm also
124
+ * reports on the deployment lane; a task-abort expiry does not). */
125
+ unansweredReason(subject: string, cause: "timeout" | "aborted"): Promise<string>;
126
+ }
127
+ /**
128
+ * ONE gate pass — the record the station groups (`runGateFold`, `runGateLanes`) and the exits read and write,
129
+ * built once per call by `runToolGate` before the screening pass. What used to be the closure-captured `let`
130
+ * cells of a 1 500-line function are its MUTABLE cells (each written by the station named on it, read by the
131
+ * ones after); what the stations borrow from the hook machinery are its SEATS (fixed for the pass). Ownership
132
+ * classes follow the phase-Input convention: `borrowed-readonly` = fixed for the pass, the stations only read
133
+ * it; `borrowed-mutable` = a cell a station writes.
134
+ */
135
+ export interface GatePass {
136
+ /** borrowed-readonly — the gate's input: every deployment seat the stations read by name (`egress`,
137
+ * `orgRules`, `persistedRules`, `autoMode`, …), and the identity / reminder mark the exits stamp. */
138
+ readonly input: ToolGateInput;
139
+ /** borrowed-readonly — the gate's event, read ONCE at entry (the dispatch's entry destructure): the exit compares the
140
+ * final args against THIS event's input, never against a re-read of the input record. */
141
+ readonly event: ToolGateInput["event"];
142
+ /** borrowed-readonly — the deployment's policy face, captured ONCE at entry before the screening pass. The fold
143
+ * adjudicates through it and the edit chain re-adjudicates through the SAME binding: a stateful accessor on the
144
+ * input cannot hand the fold one policy and the approved-edit re-check another (or none). */
145
+ readonly adjudicate: ToolGateInput["adjudicate"];
146
+ /** borrowed-readonly — the ask resolver, captured ONCE at entry (same rule: the first-round resolution and the edit
147
+ * round's re-ask resolve through one binding). */
148
+ readonly resolveAsk: ToolGateInput["resolveAsk"];
149
+ /** borrowed-readonly — the durable park closure, captured ONCE at entry (the three park sites hand it the same
150
+ * thirteen seats; presence is judged off this one read). */
151
+ readonly suspendAsk: ToolGateInput["suspendAsk"];
152
+ /** borrowed-readonly — `input.event.toolName`, read once. */
153
+ readonly toolName: string;
154
+ /** borrowed-readonly — `input.event.toolCallId`, read once. */
155
+ readonly toolCallId: string;
156
+ /** borrowed-readonly — design/384: ONE read of the per-call signal for the whole gate call (the one-read
157
+ * discipline the settlement screen already states): every conditional seat invocation (the three wait
158
+ * seats plus the three suspendAsk park sites) judges presence and passes the value from this same cell, so
159
+ * a stateful accessor cannot answer the arity check with a signal and the call with something else. */
160
+ readonly callSignal: AbortSignal | undefined;
161
+ /** borrowed-readonly — RB-463: the one host-callback isolation scope of this gate call (the deny observer,
162
+ * the hook-error trace sink, the org and rule lanes' disclosure sinks). */
163
+ readonly notifier: SafeNotifier;
164
+ /** borrowed-readonly — the settlement LEDGER of this pass (the binding is fixed; the ledger's own methods
165
+ * are how the resolution sites write it — one rule per read, see {@link createSettlementLedger}). */
166
+ readonly ledger: SettlementLedger;
167
+ /** borrowed-readonly — the ONE emission seat of the deny observer for this pass, bounded and contained like
168
+ * every other seat (the hook machinery owns the seat runner; the exits call this). */
169
+ readonly notifyPermissionDenied: (payload: Omit<PermissionDeniedPayload, "signal">) => Promise<void>;
170
+ /** borrowed-readonly — the PreToolUse screening face ({@link PreToolUseScreening}), or undefined when no
171
+ * `preToolUse` face is wired (the edit chain then re-screens nothing, as before). */
172
+ readonly screening: PreToolUseScreening | undefined;
173
+ /** borrowed-readonly — the station group that runs AFTER the fold (the lanes: org layer, rule lane, classifier,
174
+ * parks, resolution, edit chain, and the exit they end in), handed the pass by the fold's LAST station on the
175
+ * fold's own completing tick. A seat rather than an import because the two station modules sit on one layer
176
+ * and may not name each other; a seat rather than a second `await` in the dispatch because that await is a
177
+ * suspension point of its own: the org face and the rule store read HOST state the host may refresh
178
+ * concurrently, and the tick on which they are consulted is the contract — when the station bodies were one
179
+ * function, the org face was invoked in the same continuation that ran the last tighten, and a refresh queued
180
+ * behind the policy's answer could not land in between. Returns the gate result (a park's suspension or the
181
+ * exit's record). Writer: the dispatch (`runToolGate`, which wires `runGateLanes`). Reader: the fold's tail. */
182
+ readonly afterFold: (pass: GatePass) => Promise<ToolGateResult>;
183
+ /** borrowed-mutable — the FINAL args: the model's input, rewritten by the phase-1 hook, by the policy
184
+ * rewrite on the ask path, by the content-ask lane's presented snapshot and by the exits' rewrite fold.
185
+ * Writers: the dispatch (phase 1), the lanes, the exits. */
186
+ currentInput: unknown;
187
+ /** borrowed-mutable — the adjudication request. Minted over the model's input so the record is whole from
188
+ * the first station, and RE-MINTED by the adjudication station over the FINAL post-screen args (the
189
+ * pre-existing `const req` site); `args` is re-pointed at the policy rewrite on the ask path and at the
190
+ * content-ask lane's presented snapshot. Writers: the fold (mint), the lanes (`args`). */
191
+ req: ToolCallRequest;
192
+ /** borrowed-mutable — the PreToolUse `additionalContext` collected for this call (pushed into; the binding
193
+ * is fixed). Writers: the dispatch (phase 1), the lanes (sandbox admission note, the edit re-screen). */
194
+ readonly preToolContext: string[];
195
+ /** borrowed-mutable — the phase-1 hook's ASK, remembered for the fold. Only ever an ask (assigned under the
196
+ * `r.action === "ask"` arm); typed as the narrowed member so the fold's reconstruction reads the ask-only
197
+ * fields without a runtime re-test that could, if it ever failed, silently skip the promotion (i.e. loosen).
198
+ * Writer: the dispatch (phase 1). Readers: the fold, the sandbox-admission conjunct. */
199
+ hookAsk: Extract<PermissionResult, {
200
+ action: "ask";
201
+ }> | undefined;
202
+ /** borrowed-mutable — set by a park attempt that FAILED (see `ParkAttemptFailed`). Read at the single deny
203
+ * exit — whatever the fallback chain then decides to say, it says it knowing the park was tried first.
204
+ * Writers: the three park sites. */
205
+ parkFailed: string | undefined;
206
+ /** borrowed-mutable — WHO refused, written ONCE by the refusing site (a direct policy / org / classifier
207
+ * deny, a hook re-screen refusal, the write-protection re-judge, the edit chain's cap, an ask resolution
208
+ * that ended as a refusal). The exit mints the outcome from it beside the ledger's `settled`; a refusal
209
+ * that reaches the exit without it is an engine defect and the mint throws. */
210
+ deniedBy: DeniedBy | undefined;
211
+ /** borrowed-mutable — the standing verdict. The no-policy default until the adjudication station writes the
212
+ * policy's answer; then rewritten by every station that changes the verdict (fold, tightens, org, rule
213
+ * lane, stamp, classifier, sandbox, content lane, resolution, edit chain). */
214
+ decision: PermissionResult;
215
+ /** borrowed-mutable — which of the gate's OWN post-fold tightens minted the standing ask, if one did — an
216
+ * input of the ask-origin derivation (`AskOriginFacts.tightened`). Written at most once per pass: every
217
+ * tighten fires only over a surviving `allow`, and the first one to fire leaves an `ask` the others do not
218
+ * touch. Recorded by the gate rather than read off `decisionReason` because that member is a policy's to
219
+ * compose (a policy stamping `"safety"` on its own ask must read as `policy`). Writer: the fold. */
220
+ tightenedBy: AskOriginFacts["tightened"];
221
+ /** borrowed-mutable — the rewrite remembered BEFORE a hook-ask promotion / tighten, re-applied on the
222
+ * approved path (a redaction is not cancelled because a person confirmed). Writer: the fold. Readers: the
223
+ * fold's probe and write-protection judges, the lanes, the allow exit. */
224
+ policyRewrite: unknown;
225
+ }
226
+ /**
227
+ * The engine's fail-closed block of phase 1 — the PreToolUse face THREW or never ANSWERED, so the engine
228
+ * decides, on its own rule, that the call does not run. design/134 R5 BOUNDARY (deliberate asymmetry with
229
+ * {@link hookDenyExit} — do NOT unify the two): a hook `deny` is the HOOK's decision and the observer
230
+ * exclusion covers it; a crash / an unanswered seat produces no decision at all, so the deny OBSERVER fires
231
+ * here — an attribution channel whose job is "why was this call denied" must see an engine deny, or the
232
+ * deployment's only evidence of a broken hook is a tool that mysteriously stops working.
233
+ */
234
+ export declare function engineFailClosedExit(pass: GatePass, reason: string): Promise<ToolGateResult>;
235
+ /** The hook's OWN deny in phase 1. design/134 R5: a PreToolUse hook deny does NOT fire `permissionDenied`
236
+ * (the exclusion recorded on `Hooks.permissionDenied`) — this return stays observer-free. */
237
+ export declare function hookDenyExit(pass: GatePass, r: PreToolUseResult): ToolGateResult;
238
+ /**
239
+ * The exit of the adjudicate chain — the pass has a standing `deny` or `allow` (every ask was resolved or
240
+ * parked before this), and this is where it becomes the gate result and the ONE record of the pass.
241
+ */
242
+ export declare function exitGate(pass: GatePass): Promise<ToolGateResult>;
@@ -0,0 +1,124 @@
1
+ import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
2
+ import { decisionText, engineSettlementOf } from "../tool-policy.js";
3
+ import { inlineUntrusted } from "../untrusted-text.js";
4
+ import { formatHookFeedback } from "../reminder-mint.js";
5
+ export function mintGateOutcome(facts) {
6
+ const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
7
+ const outcome = Object.freeze({
8
+ disposition: Object.freeze(facts.deniedBy !== undefined ? { kind: "denied", deniedBy: facts.deniedBy } : { kind: "allowed" }),
9
+ ...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
10
+ });
11
+ const defects = screenGateOutcome(outcome);
12
+ if (defects.length > 0)
13
+ throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
14
+ return outcome;
15
+ }
16
+ export function directDeny(deniedBy) {
17
+ return mintGateOutcome({ deniedBy });
18
+ }
19
+ export function createSettlementLedger(call) {
20
+ let settled;
21
+ return {
22
+ get settled() {
23
+ return settled;
24
+ },
25
+ set(next) {
26
+ settled = next;
27
+ },
28
+ fromResolved(resolved, asked, toolName) {
29
+ if (asked.action !== "ask" || asked.origin === undefined) {
30
+ if (resolved.settlement === undefined)
31
+ return;
32
+ throw new Error(`an ask for "${toolName}" was resolved without an origin word — the origin stamp precedes every resolution`);
33
+ }
34
+ const attested = engineSettlementOf(resolved, call);
35
+ if (attested !== undefined) {
36
+ settled = { settlement: attested.settlement, origin: asked.origin };
37
+ return;
38
+ }
39
+ if (resolved.settlement === undefined)
40
+ return;
41
+ settled = { settlement: resolved.settlement, origin: asked.origin };
42
+ },
43
+ fromDecision(decision) {
44
+ if (decision.action === "ask")
45
+ return undefined;
46
+ const attested = engineSettlementOf(decision, call);
47
+ if (attested !== undefined)
48
+ settled = { settlement: attested.settlement, origin: attested.origin };
49
+ if (decision.action !== "deny")
50
+ return undefined;
51
+ return attested !== undefined && SETTLEMENT_IS_REFUSAL[attested.settlement.kind] ? "ask_resolution" : "policy";
52
+ },
53
+ supersede() {
54
+ settled = undefined;
55
+ },
56
+ };
57
+ }
58
+ export function cloneObserverInput(input) {
59
+ try {
60
+ return structuredClone(input);
61
+ }
62
+ catch {
63
+ if (Array.isArray(input))
64
+ return [...input];
65
+ if (input !== null && typeof input === "object")
66
+ return { ...input };
67
+ return input;
68
+ }
69
+ }
70
+ export function traceHookCrash(input, err, notifier) {
71
+ notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
72
+ }
73
+ const PARK_FAILURE_CAUSE_MAX = 600;
74
+ function withParkFailureCause(reason, parkFailed) {
75
+ if (parkFailed === undefined)
76
+ return reason;
77
+ return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
78
+ `(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
79
+ `left to say, not the reason the call stopped.`);
80
+ }
81
+ export async function engineFailClosedExit(pass, reason) {
82
+ const { input, toolName, toolCallId, notifyPermissionDenied } = pass;
83
+ const gate = directDeny("hook");
84
+ await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
85
+ return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, preToolContext: pass.preToolContext };
86
+ }
87
+ export function hookDenyExit(pass, r) {
88
+ const { input, toolName } = pass;
89
+ return {
90
+ block: true,
91
+ reason: formatHookFeedback(decisionText(r) ?? `tool "${toolName}" blocked by a PreToolUse hook`, input.reminderMark),
92
+ gate: directDeny("hook"),
93
+ preToolContext: pass.preToolContext,
94
+ };
95
+ }
96
+ export async function exitGate(pass) {
97
+ const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
98
+ if (pass.decision.action === "deny") {
99
+ const denyReason = withParkFailureCause(decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`, pass.parkFailed);
100
+ if (pass.decision.updatedInput !== undefined) {
101
+ pass.currentInput = pass.decision.updatedInput;
102
+ }
103
+ if (pass.deniedBy === undefined)
104
+ throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
105
+ const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
106
+ await notifyPermissionDenied({ toolName, input: cloneObserverInput(pass.currentInput), toolCallId, reason: denyReason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
107
+ return {
108
+ block: true,
109
+ reason: formatHookFeedback(denyReason, input.reminderMark),
110
+ gate,
111
+ preToolContext: pass.preToolContext,
112
+ };
113
+ }
114
+ if (pass.decision.action === "allow") {
115
+ const rw = pass.decision.updatedInput !== undefined ? pass.decision.updatedInput : pass.policyRewrite;
116
+ if (rw !== undefined)
117
+ pass.currentInput = rw;
118
+ }
119
+ return {
120
+ updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
121
+ gate: mintGateOutcome(ledger.settled !== undefined ? { settled: ledger.settled } : {}),
122
+ preToolContext: pass.preToolContext,
123
+ };
124
+ }
@@ -55,23 +55,23 @@ export declare function sanitizedTtlMs(ttlMs: number | undefined): number | unde
55
55
  * SINGLE derivation on purpose. Two consumers need this answer: the abort-classification seam, which
56
56
  * stamps it into the `details` of the results the loop mints for the contaminated siblings, and the
57
57
  * `tool_end` projection, which puts it on the wire. Reading it from one function keeps the two from
58
- * disagreeing about WHICH HOLDER WINS or about what a holder with no call means. They are not otherwise
58
+ * disagreeing about what a pause with no call means. They are not otherwise
59
59
  * interchangeable: the frame face applies strictly narrower conditions on top of this answer (see
60
60
  * `tool_end.gatedCallId`), so a frame may omit an id this function returns — never the reverse.
61
+ * "Which holder wins" is no longer a question: there is one holder, and a pause that holds no call
62
+ * publishes absence.
61
63
  *
62
64
  * The frame side must not read the id back out of a tool RESULT even though the marker is there: a
63
65
  * result's `details` is written by the tool (and replaceable by post-tool hooks), so lifting a
64
66
  * cross-call attribution from it would let any failing tool name an arbitrary call and put a phantom
65
- * approval wait on someone else's frame. Same rule, same reason, as `settledBy`.
67
+ * approval wait on someone else's frame. Same rule, same reason, as `tool_end.gate`.
66
68
  *
67
- * The two holders are never both set (the commit-side discriminant writes exactly one); they are read
68
- * here in assemble-result's slot order so the winner is the same one the terminal status is built from.
69
69
  */
70
- export declare function gatedCallIdOf(p: Pick<Prepared, "suspendRef" | "reviewRef">): string | undefined;
70
+ export declare function gatedCallIdOf(p: Pick<Prepared, "pausedRef">): string | undefined;
71
71
  /**
72
72
  * The abort-classification marker for the loop's short-circuit "Operation aborted" family: when a gate
73
- * committed a durable park this run (`suspendRef` for human/irreversible/content asks, `reviewRef` for
74
- * the dry-run/plan-review family), the contaminated sibling frames carry something a consumer can key
73
+ * committed a durable park this run (`pausedRef.current`, whatever the pause's kind), the contaminated
74
+ * sibling frames carry something a consumer can key
75
75
  * on instead of parsing the (contract-stable) result text (ruled 2026-08-04). The commit is published
76
76
  * BEFORE the abort fires, so this read is race-free at mint time.
77
77
  *
@@ -83,26 +83,20 @@ export declare function gatedCallIdOf(p: Pick<Prepared, "suspendRef" | "reviewRe
83
83
  * Module-level rather than a closure in `prepareTask` (design/238 D-7: extract, don't accrete) — the
84
84
  * marker's shape and its rules belong beside {@link gatedCallIdOf}, the derivation it reads.
85
85
  */
86
- export declare function parkContaminationMarker(refs: Pick<Prepared, "suspendRef" | "reviewRef">): Record<string, unknown> | undefined;
86
+ export declare function parkContaminationMarker(refs: Pick<Prepared, "pausedRef">): Record<string, unknown> | undefined;
87
87
  /**
88
- * design/76 §2.5 — THE COMMIT-SIDE DISCRIMINANT (v4 MAJOR-A, the load-bearing dry-run fix). Called
89
- * AFTER `commitSuspendSaga` returns true (the checkpoint is durably committed) to publish which holder
90
- * the run loop reads for the terminal status. Branch on the COMMITTED `gate.kind`: a REVIEW-PAUSE gate
91
- * writes `reviewRef` (→ assemble-result slot 8.6 `status:"needs_review"` / `review.pending`); every
92
- * approval/resource gate (human / irreversible_ask / resource_limit / task_done) writes `suspendRef` as
93
- * before ( slot 8.5 `status:"suspended"`). NEVER both a review pause that also set `suspendRef`
94
- * would assemble as `"suspended"` and make slot 8.6 dead code. The review-pause family is BOTH the
95
- * dry-run `{kind:"needs_review"}` gate (design/76 §2.5) AND the design/80 D-B `{kind:"plan_review"}`
96
- * gate — both are human-review pauses that share the `needs_review` terminal (a `plan_review` resumes
97
- * with a `plan_review` outcome, a `needs_review` with `dry_run_review`; the distinct gate.kind keeps the
98
- * resume discriminant unambiguous). (The review checkpoints themselves are minted by a PROFILE, out of
99
- * core scope; core only owns this routing seam + the gate/status/resume discriminants.)
88
+ * THE COMMIT-SIDE PUBLICATION. Called AFTER `commitSuspendSaga` returns true (the checkpoint is durably
89
+ * committed) to publish the run's one committed pause for the run loop's terminal assembly. There is no
90
+ * routing: the cause carries the committed gate whole, and WHICH terminal status it reads
91
+ * (`suspended` / `needs_review`) is the gate kind's registry row the two holders the review family and
92
+ * the approval family used to be split across, and the branch that chose between them, are gone.
93
+ * (Review checkpoints themselves are minted by a PROFILE, out of core scope; core owns this seam and the
94
+ * gate/status/resume discriminants.)
100
95
  *
101
- * Module-level, taking the holders as a parameter (design/238 D-7: extract, don't accrete) — the
102
- * closure form captured nothing else, so hoisting it costs nothing and puts the discriminant beside the
96
+ * Module-level, taking the holder as a parameter (design/238 D-7: extract, don't accrete) — beside the
103
97
  * two readers of what it publishes ({@link gatedCallIdOf}, {@link parkContaminationMarker}).
104
98
  */
105
- export declare function publishCommittedSuspend(refs: Pick<Prepared, "suspendRef" | "reviewRef">, token: CheckpointToken, gate: CheckpointGate, scope: string, remoteHandle: WorkspaceHandle | undefined, checkpointId?: string,
99
+ export declare function publishCommittedSuspend(refs: Pick<Prepared, "pausedRef">, token: CheckpointToken, gate: CheckpointGate, scope: string, remoteHandle: WorkspaceHandle | undefined, checkpointId?: string,
106
100
  /** The COMMITTED row's pendingAction — passed whole, not as a pre-extracted id, so no call site can
107
101
  * hand this seam an id the committed row does not actually park on. */
108
102
  pendingAction?: PendingAction): void;
@@ -7,26 +7,25 @@ export function sanitizedTtlMs(ttlMs) {
7
7
  return Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : DEFAULT_RESOURCE_TTL_MS;
8
8
  }
9
9
  export function gatedCallIdOf(p) {
10
- if (p.suspendRef.token !== undefined)
11
- return p.suspendRef.gatedCallId;
12
- if (p.reviewRef.token !== undefined)
13
- return p.reviewRef.gatedCallId;
14
- return undefined;
10
+ return p.pausedRef.current?.gatedCallId;
15
11
  }
16
12
  export function parkContaminationMarker(refs) {
17
- if (refs.suspendRef.token === undefined && refs.reviewRef.token === undefined)
13
+ if (refs.pausedRef.current === undefined)
18
14
  return undefined;
19
15
  const gatedCallId = gatedCallIdOf(refs);
20
16
  return { code: "gate.parked", ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
21
17
  }
22
18
  export function publishCommittedSuspend(refs, token, gate, scope, remoteHandle, checkpointId, pendingAction) {
23
- const ref = gate.kind === "needs_review" || gate.kind === "plan_review" ? refs.reviewRef : refs.suspendRef;
24
- ref.token = token;
25
- if (checkpointId !== undefined)
26
- ref.checkpointId = checkpointId;
27
- ref.gate = gate;
28
- ref.gatedCallId = pendingAction?.kind === "tool_approval" ? pendingAction.toolCallId : undefined;
29
- if (remoteHandle !== undefined)
30
- ref.restoreMode = remoteHandle.restoreMode === "park_only" ? "park_only" : "snapshot";
31
- ref.scope = scope;
19
+ const pause = {
20
+ cause: {
21
+ kind: "paused",
22
+ gate,
23
+ token,
24
+ ...(checkpointId !== undefined ? { checkpointId } : {}),
25
+ ...(remoteHandle !== undefined ? { restoreMode: remoteHandle.restoreMode === "park_only" ? "park_only" : "snapshot" } : {}),
26
+ },
27
+ scope,
28
+ ...(pendingAction?.kind === "tool_approval" ? { gatedCallId: pendingAction.toolCallId } : {}),
29
+ };
30
+ refs.pausedRef.current = pause;
32
31
  }
@@ -60,9 +60,6 @@ export interface PrepareAskLaneInput {
60
60
  * synchronous resolve that a person actually judged (count / totalWaitMs / gates push); runtask adds the durable
61
61
  * resume latency. */
62
62
  humanReviewRef: Prepared["humanReviewRef"];
63
- /** borrowed-mutable — the bare-human-rejection sideband. Writer here: a `settledBy:"human"` deny with no note
64
- * (`add`); the gate station judges and sweeps it. */
65
- humanBareRejections: Set<string>;
66
63
  /** borrowed-readonly — the injectable wall clock every human-time observable reads (`Prepared.now`). */
67
64
  now: () => number;
68
65
  /** borrowed-readonly — the policy-chain phase's rule-offer factory (the suggestion members the ask mint threads). */
@@ -55,7 +55,7 @@ function resolveApprovalPreview(tools, toolName, args) {
55
55
  }
56
56
  }
57
57
  export function prepareAskLane(input) {
58
- const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, humanBareRejections, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash } = input;
58
+ const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash } = input;
59
59
  if (!gateMachineryActive)
60
60
  return { askLane: undefined };
61
61
  const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
@@ -77,6 +77,7 @@ export function prepareAskLane(input) {
77
77
  `(no live approver reachable, or a durable-park mandate applies), and no durable approval park is ` +
78
78
  `available — denied fail-closed (the inherited constraint stands).`,
79
79
  decisionReason: "mode",
80
+ settlement: { kind: "approver_unavailable", who: { party: "none" }, when: now() },
80
81
  approverUnavailable: true,
81
82
  };
82
83
  }
@@ -104,7 +105,7 @@ export function prepareAskLane(input) {
104
105
  ...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
105
106
  }, onAsk, composedCallSignal(callSignal), lateAskSettlementObserver({ toolName: req.toolName, toolCallId: req.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
106
107
  const waitMs = Math.max(0, now() - t0);
107
- if (resolved.approverUnavailable !== true && resolved.resolution !== "task_aborted") {
108
+ if (resolved.approverUnavailable !== true && resolved.settlement?.kind !== "task_aborted") {
108
109
  humanReviewRef.count += 1;
109
110
  humanReviewRef.totalWaitMs += waitMs;
110
111
  const toolArg = primaryActivityArg(req.args);
@@ -116,9 +117,6 @@ export function prepareAskLane(input) {
116
117
  ...(toolArg !== undefined ? { toolArg } : {}),
117
118
  });
118
119
  }
119
- if (resolved.action === "deny" && resolved.settledBy === "human" && resolved.humanRefusalNote !== true) {
120
- humanBareRejections.add(req.toolCallId);
121
- }
122
120
  return resolved;
123
121
  };
124
122
  const notifyPermissionDenied = createDenyObserverNotifier(hooks, hookTimeoutMs, abortController.signal, notifyOwnHookCrash);
@@ -78,12 +78,9 @@ export interface PrepareBoundaryParksInput {
78
78
  f012CheckpointState: () => boolean;
79
79
  /** borrowed-readonly — see `faceCheckpointState`. */
80
80
  orgAdmissionCheckpointState: () => boolean;
81
- /** borrowed-mutable — the durable-suspend holder (`Prepared.suspendRef`). Writer here: the commit-side publication of a
82
- * committed resource row. */
83
- suspendRef: Prepared["suspendRef"];
84
- /** borrowed-mutable — the review holder (`Prepared.reviewRef`). Writer here: the commit-side publication of a committed
85
- * plan_review row. */
86
- reviewRef: Prepared["reviewRef"];
81
+ /** borrowed-mutable — the run's committed-pause holder (`Prepared.pausedRef`). Writer here: the commit-side publication
82
+ * of a committed resource / platform / plan-review checkpoint. */
83
+ pausedRef: Prepared["pausedRef"];
87
84
  /** borrowed-mutable — the run-scoped remote-lifecycle failure log. Writer here: a refused `suspendVM` (`push`). */
88
85
  remoteEnvFailures: Prepared["remoteEnvFailures"];
89
86
  /** borrowed-readonly — the wiring-manifest phase's task-axis predicate (opt-in ∧ durable infra): exposes `suspendForResource`. */
@@ -5,7 +5,7 @@ import { checkpointScopeOf } from "./checkpoint-scope.js";
5
5
  import { DEFAULT_RESOURCE_TTL_MS, publishCommittedSuspend, sanitizedTtlMs, USAGE_WINDOW_REAP_MARGIN_MS } from "./park-commit.js";
6
6
  import { remoteEnvFailureNote } from "./remote-env-retry.js";
7
7
  export function prepareBoundaryParks(input) {
8
- const { saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, suspendRef, reviewRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter } = input;
8
+ const { saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter } = input;
9
9
  if (saga === undefined)
10
10
  return { suspendForResource: undefined, suspendForPlatformLimit: undefined, suspendForReview: undefined };
11
11
  const { inFlightSpendMicroUsd, serializeCheckpointState, commitSuspendSaga, suspendLoopCapHit, suspendableEnv, parkOnlyRemoteEnv, parkOnlyHandle } = saga;
@@ -67,7 +67,7 @@ export function prepareBoundaryParks(input) {
67
67
  };
68
68
  if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
69
69
  return false;
70
- publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
70
+ publishCommittedSuspend({ pausedRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
71
71
  try {
72
72
  await sessions.pin?.(sessionId);
73
73
  }
@@ -153,7 +153,7 @@ export function prepareBoundaryParks(input) {
153
153
  };
154
154
  if ((await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).tag !== "committed")
155
155
  return false;
156
- publishCommittedSuspend({ suspendRef, reviewRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
156
+ publishCommittedSuspend({ pausedRef }, token, gate, scope, remoteHandle, cp.checkpointId, cp.pendingAction);
157
157
  try {
158
158
  await sessions.pin?.(sessionId);
159
159
  }
@@ -22,7 +22,6 @@ import type { AgentHarness, AgentTool, ThinkingLevel } from "../../internal/harn
22
22
  import type { Model } from "../../internal/llm.js";
23
23
  import { forkGovernanceDenial } from "../../agents/subagent.js";
24
24
  import type { PromptEpochArtifact } from "../../prompt-assembly/artifact.js";
25
- import { createRunWorkflowTool } from "../../orchestration/run-workflow-tool.js";
26
25
  import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
27
26
  import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
28
27
  import { type AutoModeDecider, type AutoModeDenialTracker } from "../auto-mode.js";
@@ -33,11 +32,8 @@ import type { OnAsk } from "../tool-policy.js";
33
32
  import type { RunnerDeps, RuntimeCaps, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
34
33
  import type { AutoModeArmReason } from "../wiring-manifest.js";
35
34
  import type { ReadFace } from "../../tools/fs/index.js";
36
- import type { InheritedGate, Prepared, RunInternals, ToolFaceSnapshot } from "./contracts.js";
35
+ import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
37
36
  import { type BlockedRef } from "./synthetic-tools.js";
38
- /** The runner the Workflow mount accepts — spelled as the factory's own option type so this phase names no runner
39
- * module of its own (the narrow `RunnerSelfSeat` contract is a design decision still open; see the layering registry). */
40
- type WorkflowMountRunner = Parameters<typeof createRunWorkflowTool>[0]["runner"];
41
37
  export interface PrepareCapsAndWorkflowInput {
42
38
  /** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
43
39
  * `enablePlanMode`, `interactiveTools`, `checkpointStore`; the caps/compliance stations read `principal`, `selfOrchestration`,
@@ -113,7 +109,7 @@ export interface PrepareCapsAndWorkflowInput {
113
109
  peerSendMessageBuiltIn: boolean;
114
110
  /** borrowed-readonly — the trusted Runner self-reference the Workflow mount executes children through; undefined when
115
111
  * prepareTask runs standalone (then Workflow is simply not mounted). */
116
- runnerSelf: WorkflowMountRunner | undefined;
112
+ runnerSelf: RunnerSelfSeat | undefined;
117
113
  /** borrowed-readonly — LATE-BOUND: the memory-engine session; the Workflow mount's capture-floor getter reads it at ITS call time. */
118
114
  memoryEngineSession: () => Prepared["memoryEngineSession"];
119
115
  /** borrowed-readonly — LATE-BOUND: the adopted center artifact; the Workflow mount's two center getters read it at THEIR call time. */
@@ -167,4 +163,3 @@ export interface PrepareCapsAndWorkflowResult {
167
163
  }
168
164
  /** The M4 phase body — prepareTask's caps-and-workflow stretch, verbatim (see the module header). */
169
165
  export declare function prepareCapsAndWorkflow(input: PrepareCapsAndWorkflowInput): Promise<PrepareCapsAndWorkflowResult>;
170
- export {};
@@ -244,7 +244,7 @@ export async function prepareCapsAndWorkflow(input) {
244
244
  },
245
245
  ...(spec.handsReadOnly === true ? { parentHandsReadOnly: true } : {}), ...(spec.interactiveTools === false ? { parentInteractiveTools: false } : {}),
246
246
  onNotice: deps.onNotice,
247
- parentCheckpointStoreDisabled: spec.checkpointStore === null,
247
+ parentCheckpointStoreDisabled: spec.checkpointStore === "disabled",
248
248
  parentCenterArtifactDigest: () => input.centerAdoption()?.artifact.artifactDigest,
249
249
  parentCenterSourceRevision: () => input.centerAdoption()?.sourceRevision,
250
250
  ...(forwardEvent ? { forwardEvent } : {}),