@sema-agent/core 7.50.2 → 7.51.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 (200) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/dist/agents/child-failure.d.ts +5 -16
  3. package/dist/agents/child-failure.js +4 -24
  4. package/dist/agents/subagent-steps.js +2 -2
  5. package/dist/agents/subagent.d.ts +4 -13
  6. package/dist/agents/subagent.js +94 -204
  7. package/dist/agents/tool-filter.d.ts +2 -2
  8. package/dist/brain/anthropic.d.ts +1 -1
  9. package/dist/brain/anthropic.js +7 -5
  10. package/dist/brain/open-responses.d.ts +1 -1
  11. package/dist/brain/open-responses.js +3 -2
  12. package/dist/brain/openai.d.ts +1 -1
  13. package/dist/brain/openai.js +3 -2
  14. package/dist/brain/request-route.d.ts +2 -2
  15. package/dist/brain/route-adjudicator.d.ts +16 -7
  16. package/dist/brain/route-adjudicator.js +23 -7
  17. package/dist/brain/route-conformance.js +23 -5
  18. package/dist/brain/stream-engine.js +9 -3
  19. package/dist/core/a2a.js +6 -8
  20. package/dist/core/allow-entry.d.ts +45 -26
  21. package/dist/core/allow-entry.js +48 -24
  22. package/dist/core/arg-summary.d.ts +8 -85
  23. package/dist/core/arg-summary.js +4 -386
  24. package/dist/core/ask-origin.d.ts +35 -33
  25. package/dist/core/ask-origin.js +9 -11
  26. package/dist/core/checkpoint-requirements.d.ts +6 -0
  27. package/dist/core/checkpoint-requirements.js +1 -0
  28. package/dist/core/checkpoint-store.d.ts +15 -34
  29. package/dist/core/checkpoint-store.js +5 -4
  30. package/dist/core/classifier-unavailable-arm.d.ts +12 -8
  31. package/dist/core/classifier-unavailable-arm.js +7 -2
  32. package/dist/core/credential-vocabulary.d.ts +3 -2
  33. package/dist/core/credential-vocabulary.js +27 -18
  34. package/dist/core/delegation-frames.d.ts +40 -15
  35. package/dist/core/delegation-frames.js +34 -0
  36. package/dist/core/engine-capabilities.d.ts +15 -0
  37. package/dist/core/engine-capabilities.js +1 -0
  38. package/dist/core/engine-notice.d.ts +41 -14
  39. package/dist/core/gate-fold.js +9 -7
  40. package/dist/core/gate-lanes.js +6 -4
  41. package/dist/core/gate-outcome.d.ts +81 -15
  42. package/dist/core/gate-outcome.js +168 -67
  43. package/dist/core/governance-codes.d.ts +7 -1
  44. package/dist/core/governance-codes.js +9 -0
  45. package/dist/core/hooks.d.ts +4 -4
  46. package/dist/core/hooks.js +4 -3
  47. package/dist/core/mcp-connection-pool.d.ts +2 -0
  48. package/dist/core/mcp-connection-pool.js +2 -1
  49. package/dist/core/mcp-failure.js +2 -1
  50. package/dist/core/mcp-server-spec.d.ts +27 -5
  51. package/dist/core/mcp.d.ts +13 -38
  52. package/dist/core/mcp.js +28 -23
  53. package/dist/core/media-byte-cap.d.ts +25 -0
  54. package/dist/core/media-byte-cap.js +18 -2
  55. package/dist/core/memory-engine/consolidation-driver.js +1 -1
  56. package/dist/core/mode-policy.d.ts +25 -1
  57. package/dist/core/mode-policy.js +28 -13
  58. package/dist/core/own-property-receipt.d.ts +18 -0
  59. package/dist/core/own-property-receipt.js +39 -0
  60. package/dist/core/permission-rule-model.d.ts +32 -11
  61. package/dist/core/permission-rule-model.js +12 -7
  62. package/dist/core/persisted-rule-arms.d.ts +3 -3
  63. package/dist/core/prompt-section-seat.d.ts +54 -0
  64. package/dist/core/prompt-section-seat.js +49 -0
  65. package/dist/core/protocol-http-auth.d.ts +44 -0
  66. package/dist/core/protocol-http-auth.js +45 -0
  67. package/dist/core/read-face-seats.d.ts +30 -0
  68. package/dist/core/read-face-seats.js +38 -0
  69. package/dist/core/redaction-passes.d.ts +82 -0
  70. package/dist/core/redaction-passes.js +378 -0
  71. package/dist/core/remote-env.d.ts +4 -3
  72. package/dist/core/resume-startup-cause.d.ts +56 -0
  73. package/dist/core/resume-startup-cause.js +43 -0
  74. package/dist/core/rule-denial-sentence.d.ts +2 -2
  75. package/dist/core/runner/announce-once-ledger.js +2 -2
  76. package/dist/core/runner/classifier-ladder.d.ts +17 -13
  77. package/dist/core/runner/classifier-ladder.js +12 -10
  78. package/dist/core/runner/construction-doors.js +9 -20
  79. package/dist/core/runner/contracts.d.ts +4 -4
  80. package/dist/core/runner/gate-exit.js +2 -2
  81. package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
  82. package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
  83. package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
  84. package/dist/core/runner/person-path-rows.d.ts +4 -3
  85. package/dist/core/runner/policy-throw.js +4 -4
  86. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  87. package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
  88. package/dist/core/runner/prepare-gate-stations.js +4 -4
  89. package/dist/core/runner/prepare-hands-readface.js +1 -19
  90. package/dist/core/runner/prepare-inherited-gate.js +10 -9
  91. package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
  92. package/dist/core/runner/prepare-policy-chain.js +10 -13
  93. package/dist/core/runner/prepare-project-context.d.ts +4 -4
  94. package/dist/core/runner/prepare-project-context.js +2 -1
  95. package/dist/core/runner/prepare-prompt-assembly.js +1 -0
  96. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
  97. package/dist/core/runner/prepare-prompt-inputs.js +4 -4
  98. package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
  99. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  100. package/dist/core/runner/prepare-task.js +6 -4
  101. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
  102. package/dist/core/runner/prepare-turn-wiring.js +8 -7
  103. package/dist/core/runner/run-harness-handlers.js +2 -2
  104. package/dist/core/runner/run-leg.js +10 -9
  105. package/dist/core/runner/run-settle-and-teardown.js +5 -5
  106. package/dist/core/runner/run-turn-boundary.js +3 -2
  107. package/dist/core/runner/runtask.d.ts +1 -2
  108. package/dist/core/runner/runtask.js +8 -12
  109. package/dist/core/runner/session-rule-policy.d.ts +15 -11
  110. package/dist/core/runner/session-rule-policy.js +43 -33
  111. package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
  112. package/dist/core/runner/stream-settle-backstop.js +2 -1
  113. package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
  114. package/dist/core/runner/sub-agent-deny-lists.js +23 -12
  115. package/dist/core/runner/terminal-account.js +3 -3
  116. package/dist/core/runner/tool-output-projection.d.ts +5 -0
  117. package/dist/core/runner/tool-output-projection.js +6 -11
  118. package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
  119. package/dist/core/runner/tool-result-media-seat.js +13 -0
  120. package/dist/core/runner/turn-attachments.d.ts +13 -12
  121. package/dist/core/runner/turn-attachments.js +11 -7
  122. package/dist/core/runner-deps.d.ts +4 -3
  123. package/dist/core/safety-merge-corpus.js +21 -21
  124. package/dist/core/secret-env.d.ts +41 -15
  125. package/dist/core/secret-env.js +85 -19
  126. package/dist/core/session-policy-store.d.ts +89 -72
  127. package/dist/core/session-policy-store.js +100 -93
  128. package/dist/core/shell-read-silence.d.ts +38 -4
  129. package/dist/core/shell-read-silence.js +102 -0
  130. package/dist/core/side-query.d.ts +7 -8
  131. package/dist/core/skill-grant-binding.d.ts +32 -11
  132. package/dist/core/skill-grant-binding.js +11 -3
  133. package/dist/core/skill-grant.d.ts +4 -3
  134. package/dist/core/skill-grant.js +5 -4
  135. package/dist/core/skills-directory.d.ts +12 -1
  136. package/dist/core/skills-directory.js +17 -8
  137. package/dist/core/spec-contract.d.ts +5 -3
  138. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  139. package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
  140. package/dist/core/swappable-deps.d.ts +15 -1
  141. package/dist/core/swappable-deps.js +25 -1
  142. package/dist/core/synthetic-read-verdict.d.ts +16 -14
  143. package/dist/core/synthetic-read-verdict.js +28 -8
  144. package/dist/core/task-notification.d.ts +13 -3
  145. package/dist/core/task-notification.js +12 -1
  146. package/dist/core/task-spec.d.ts +14 -15
  147. package/dist/core/tool-name-readings.d.ts +0 -4
  148. package/dist/core/tool-name-readings.js +0 -1
  149. package/dist/core/tool-policy.d.ts +40 -9
  150. package/dist/core/tool-policy.js +25 -7
  151. package/dist/core/types.d.ts +3 -3
  152. package/dist/core/types.js +1 -1
  153. package/dist/core/untrusted-egress.d.ts +60 -11
  154. package/dist/core/untrusted-egress.js +120 -9
  155. package/dist/core/untrusted-text.js +16 -0
  156. package/dist/core/wiring-manifest.d.ts +22 -8
  157. package/dist/core/wiring-manifest.js +12 -4
  158. package/dist/engine/execution-env/kill-tree.js +4 -4
  159. package/dist/engine/execution-env/node-execution-env.d.ts +11 -4
  160. package/dist/engine/execution-env/node-execution-env.js +9 -4
  161. package/dist/engine/harness/agent-harness.d.ts +2 -1
  162. package/dist/engine/harness/agent-harness.js +4 -1
  163. package/dist/engine/harness/types.d.ts +3 -0
  164. package/dist/engine/llm/types.d.ts +7 -9
  165. package/dist/engine/loop/agent-loop.d.ts +8 -3
  166. package/dist/engine/loop/agent-loop.js +26 -9
  167. package/dist/engine/loop/types.d.ts +10 -0
  168. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -1
  169. package/dist/engine/lsp/node-lsp-manager.js +2 -2
  170. package/dist/index.d.ts +22 -5
  171. package/dist/index.js +18 -5
  172. package/dist/orchestration/run-workflow-tool.js +8 -8
  173. package/dist/orchestration/workflow.js +2 -2
  174. package/dist/prompt-assembly/assemble.d.ts +3 -1
  175. package/dist/prompt-assembly/assemble.js +6 -0
  176. package/dist/prompts/cowork.d.ts +42 -0
  177. package/dist/prompts/cowork.js +529 -0
  178. package/dist/prompts/default.d.ts +14 -1
  179. package/dist/prompts/default.js +3 -0
  180. package/dist/stores/file/fs-atomic.js +2 -2
  181. package/dist/stores/file/session-policy-store.d.ts +6 -3
  182. package/dist/stores/file/session-policy-store.js +5 -16
  183. package/dist/tools/artifact/artifact-tool.js +2 -0
  184. package/dist/tools/fs/image-admission.d.ts +57 -4
  185. package/dist/tools/fs/image-admission.js +85 -0
  186. package/dist/tools/fs/path-containment.d.ts +2 -0
  187. package/dist/tools/fs/path-containment.js +5 -0
  188. package/dist/tools/fs/shell-read-check.d.ts +31 -6
  189. package/dist/tools/fs/shell-read-check.js +2 -4
  190. package/dist/tools/fs/shell-read-environment.d.ts +10 -25
  191. package/dist/tools/fs/shell-read-environment.js +6 -18
  192. package/dist/tools/fs/shell-read-establish.d.ts +3 -2
  193. package/dist/tools/fs/shell-read-establish.js +5 -5
  194. package/dist/tools/fs/shell-read-judge.d.ts +38 -8
  195. package/dist/tools/fs/shell-read-judge.js +12 -10
  196. package/dist/tools/web.js +2 -2
  197. package/package.json +1 -1
  198. package/test/export-surface.snapshot.json +112 -10
  199. package/wire-changes/core@7.51.0.json +2421 -0
  200. package/wire-changes/core@7.51.1.json +259 -0
@@ -172,31 +172,33 @@ export interface BackgroundChildEvent {
172
172
  * Lets a parent's orchestration logic decide "continue it" vs "start fresh" without trial-and-error.
173
173
  */
174
174
  resumable?: boolean;
175
- /** terminal: the settled status. */
175
+ /** terminal: the settled status — like every completion fact on the terminal frame (`seq`,
176
+ * `stoppedBy`, `summary`, `result`, the residuals, `resumable`, `usage`, `completionId`), read from
177
+ * the settle's one completion frame by {@link backgroundChildTerminal}, never re-stated beside it. */
176
178
  status?: "completed" | "killed" | "failed";
177
179
  /** terminal + spawn/tick (#258 widened the carriers): the stop-cycle number — on a TERMINAL frame
178
- * a MIRROR of the sibling task_notification's `TaskNotificationPayload.seq` (same settle, same X3
179
- * snapshot); on SPAWN and TICK frames the registry row's generation at emit (fresh spawn = 1, a
180
+ * the completion frame's `TaskNotificationPayload.seq` (same settle, same X3 snapshot), or the
181
+ * row's generation when that frame states none; on SPAWN and TICK frames the registry row's generation at emit (fresh spawn = 1, a
180
182
  * revived cycle's bumped counter), so a fleet consumer can tell a LATE first frame from a revived
181
183
  * cycle's frame without waiting for the terminal. One axis with `task_progress`'s `seq` — the tick
182
184
  * mirrors the frame's own stamp. Same honest downgrade as ever: present whenever a cycle number is
183
185
  * knowable (the registry handle / retain ledger's `cycleSeq`, the durable row's `seq` on a tier-3
184
186
  * revival / parked resume), absent when no carrier exists (forging a period would lie). */
185
187
  seq?: number;
186
- /** terminal: the cross-channel completion correlation id —
187
- * MIRROR of the sibling task_notification's `TaskNotificationPayload.completionId` (same settle,
188
- * same minted value; the fleet lane was explicitly one of the four channels the original ask named
189
- * — "同一完成今天从四条通道扇出" — so it gets the same mirror treatment `seq`/`stoppedBy` already
190
- * have here, not a fifth divergent copy). Absent until the underlying task reaches a terminal
191
- * settle, or on a pre-P1-3 core build. */
188
+ /** terminal: the cross-channel completion correlation id — the completion frame's
189
+ * `TaskNotificationPayload.completionId` (one minted value for every face of one completion). Absent
190
+ * until the underlying task reaches a terminal settle, or on a pre-P1-3 core build. */
192
191
  completionId?: string;
193
- /** terminal, killed only (design/134 §3.3): who stopped it. MIRROR of task-registry `StopSource`
194
- * (the single source; inlined to avoid an import cycle — task-registry imports types.ts; third
195
- * mirror: task-notification.ts `TaskNotificationPayload.stoppedBy` — keep all three in sync).
196
- * OPEN ENUM: consumers must tolerate unknown values (a future "timeout" is not breaking). */
197
- stoppedBy?: "user" | "parent" | "system" | (string & {});
198
- /** terminal: bounded human summary (same text the task_notification carries). */
192
+ /** terminal, killed only (design/134 §3.3): who stopped it — the completion frame's own word, so its
193
+ * type is that frame's. OPEN ENUM: consumers must tolerate unknown values (a future "timeout" is not breaking). */
194
+ stoppedBy?: import("./task-notification.js").TaskNotificationPayload["stoppedBy"];
195
+ /** terminal: the completion frame's bounded human summary (the text the parent's model reads). */
199
196
  summary?: string;
197
+ /** terminal: the child's report as the completion frame carries it — the same string, cut once at the
198
+ * frame's bound with its disclosure, so a host that wakes an idle conversation reads the report here
199
+ * rather than from a second, differently-cut copy. Absent when the frame carries none (a killed or
200
+ * failed leg with no report, a withheld hand-back). UNTRUSTED (the child's own text). */
201
+ result?: string;
200
202
  /** tick: live rollup (`task_progress.usage`) · terminal: the settled
201
203
  * `{tokens, turns, costMicroUsd?, toolUses?, durationMs}`. `costMicroUsd` is own+nested and present
202
204
  * only when that total is KNOWN (RB-368); `toolUses` is the run's own `stats.toolCalls` and is
@@ -221,6 +223,29 @@ export interface BackgroundChildEvent {
221
223
  costMicroUsd?: number;
222
224
  };
223
225
  }
226
+ /** What a background child's terminal frame states beside its completion: the row's own facts. */
227
+ export interface BackgroundChildRow {
228
+ taskId: string;
229
+ sessionScoped: boolean;
230
+ /** The child run's session id, when known (the row-join anchor). */
231
+ sessionId?: string;
232
+ /** The read handle to the child's transcript — absent when it was released (a stillborn fork branch). */
233
+ transcriptId?: string;
234
+ parentToolCallId?: string;
235
+ /** The row's registry generation: the cycle the terminal names when the completion frame states none. */
236
+ generation?: number;
237
+ }
238
+ /**
239
+ * The ONE constructor of a background child's TERMINAL {@link BackgroundChildEvent}: a projection of the
240
+ * completion frame the same settle routes to the parent's model, so the fleet face reads every completion
241
+ * fact from that one record and cannot state a different one (a second spelling of the summary, a result
242
+ * cut twice, a cycle number read at another instant). The row supplies only what a completion does not
243
+ * state. The frame's spend is re-spelled in this face's vocabulary (`tool_uses` → `toolUses`,
244
+ * `duration_ms` → `durationMs`); a member the frame lacks stays absent.
245
+ */
246
+ export declare function backgroundChildTerminal(completion: import("./task-notification.js").TaskNotificationPayload & {
247
+ status: "completed" | "killed" | "failed";
248
+ }, row: BackgroundChildRow): BackgroundChildEvent;
224
249
  /**
225
250
  * #281 件B — one lifecycle phase of a DELEGATED child leg, delivered to the process-level
226
251
  * {@link RunnerDeps.onDelegationLifecycle} observer: the public, deps-level home of the delegation
@@ -1,4 +1,38 @@
1
1
  import { observeThenableRejection } from "./safe-notify.js";
2
+ export function backgroundChildTerminal(completion, row) {
3
+ const seq = completion.seq ?? row.generation;
4
+ const usage = fleetUsageOf(completion.usage);
5
+ return {
6
+ kind: "terminal",
7
+ taskId: row.taskId,
8
+ sessionScoped: row.sessionScoped,
9
+ ...(row.sessionId !== undefined ? { sessionId: row.sessionId } : {}),
10
+ ...(row.transcriptId !== undefined ? { transcriptId: row.transcriptId } : {}),
11
+ ...(row.parentToolCallId !== undefined ? { parentToolCallId: row.parentToolCallId } : {}),
12
+ status: completion.status,
13
+ ...(seq !== undefined ? { seq } : {}),
14
+ ...(completion.stoppedBy !== undefined ? { stoppedBy: completion.stoppedBy } : {}),
15
+ summary: completion.summary,
16
+ ...(completion.result !== undefined ? { result: completion.result } : {}),
17
+ ...(completion.recentSteps !== undefined ? { recentSteps: completion.recentSteps } : {}),
18
+ ...(completion.editedFiles !== undefined ? { editedFiles: completion.editedFiles } : {}),
19
+ ...(completion.resumable !== undefined ? { resumable: completion.resumable } : {}),
20
+ ...(usage !== undefined ? { usage } : {}),
21
+ ...(completion.completionId !== undefined ? { completionId: completion.completionId } : {}),
22
+ };
23
+ }
24
+ function fleetUsageOf(usage) {
25
+ if (typeof usage !== "object" || usage === null)
26
+ return undefined;
27
+ const u = usage;
28
+ return {
29
+ ...(typeof u.tokens === "number" ? { tokens: u.tokens } : {}),
30
+ ...(typeof u.turns === "number" ? { turns: u.turns } : {}),
31
+ ...(typeof u.costMicroUsd === "number" ? { costMicroUsd: u.costMicroUsd } : {}),
32
+ ...(typeof u.tool_uses === "number" ? { toolUses: u.tool_uses } : {}),
33
+ ...(typeof u.duration_ms === "number" ? { durationMs: u.duration_ms } : {}),
34
+ };
35
+ }
2
36
  export function emitsChildRowFrames(identity) {
3
37
  return identity.isDelegatedChild;
4
38
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The engine build's capability words — the one home for "this engine does X". A shell gates on a word, never on a
3
+ * version number; the server projects the words it also wires onto its `/v1/capabilities`.
4
+ *
5
+ * A word is added in the release whose code makes it true and is never re-spelt; a word whose behaviour is retired
6
+ * leaves the set in a breaking release. Each word:
7
+ * - `inherit_env.own_credentials_withheld` — a child spawned on this host never inherits a name the deployment
8
+ * declared as its own credential (`declareWithheldEnv`): `inheritedHostEnv` subtracts the list from every policy's
9
+ * result (`"all"` and `"scrub"` alike), and an allow list naming one is refused (`inheritEnvPolicyProblem`; every
10
+ * spawn throws it). The word speaks of INHERITANCE only: the engine process's own environment stays readable to
11
+ * same-user processes on the host (`ps -E`), so a credential that never enters it remains the root fix.
12
+ */
13
+ export declare const ENGINE_CAPABILITIES: readonly ["inherit_env.own_credentials_withheld"];
14
+ /** One word of {@link ENGINE_CAPABILITIES}. */
15
+ export type EngineCapability = (typeof ENGINE_CAPABILITIES)[number];
@@ -0,0 +1 @@
1
+ export const ENGINE_CAPABILITIES = Object.freeze(["inherit_env.own_credentials_withheld"]);
@@ -91,6 +91,13 @@ export interface EngineNotice {
91
91
  * per-process-deduplicated: each failed write is a distinct fact, and `ref` names the ROW rather
92
92
  * than the attempt, so the uuidv7 `occurrenceId` minted at the failure is what tells two genuine
93
93
  * losses of one ref apart. Dedup key: (code, occurrenceId).
94
+ * - `"tool_result.media_refused"` — an executed call's result carried media blocks (an image or a document, from
95
+ * an MCP server, a host tool, an engine tool or a `tool_result` hook's replacement) whose bytes the tool-result
96
+ * seat's admission refused: not a base64 payload, a media type outside the closed set, over the cap, or bytes
97
+ * without the claimed type's signature. Each such block already stands in the result as a text placeholder naming
98
+ * the tool and the refusal (never the bytes), so the model, `tool_end.output` and the transcript read that; this is
99
+ * the operator's account. ONCE PER CALL. `detail: { toolName, toolCallId, refused: [{ type, reason, bytes, limit?,
100
+ * mimeType? }], sessionId, runId }` — closed-set words, counts and a media type only when it can be one.
94
101
  * - `"workflow.governance_key_stripped"` (#235) — the fields of an LLM-authored workflow `agent(spec)`
95
102
  * that did NOT cross the governed default-deny whitelist: an unrecognized/control-plane field, or a
96
103
  * `readFace` value that is not the containment-tightening one. The spawn PROCEEDS on the deployment
@@ -184,6 +191,13 @@ export interface EngineNotice {
184
191
  * - `"config.write_face_swapped"` — the same line for the WRITE seat (`RunnerDeps.writeFace`),
185
192
  * same payload, same laws. A SEPARATE code rather than a face field on the line above: the audience
186
193
  * table and every consumer table are keyed by code.
194
+ * - `"config.read_deny_swapped"` — `Runner.swapDeps` replaced the deployment's read-deny declaration
195
+ * (any of `readDenyPatterns` / `readDenyBuiltinTiers` / `readDenyBuiltinExclude`) without a restart;
196
+ * ONE line per swap. `detail: { seats, patterns, tiers, exclude, occurrenceId }` — the seats the swap
197
+ * named, then the declaration in force after it (additions and removals as counts, the tier selection
198
+ * by name; `null` = no declaration). In-flight legs keep the deny face they prepared under; every later
199
+ * prepare compiles against the new declaration. A candidate outside the seats' gates throws atomically
200
+ * and mints nothing. Dedup key: (code, occurrenceId).
187
201
  * - `"config.context_settings_swapped"` — `Runner.swapDeps` replaced the deployment's per-model
188
202
  * context-window overlay (`RunnerDeps.contextSettings`, the `contextSettings` seat of the swappable
189
203
  * set) without a restart. Unlike every other swappable seat this one is read LIVE: a running leg
@@ -237,10 +251,11 @@ export interface EngineNotice {
237
251
  * - `"classifier.unavailable"` (`src/core/runner/classifier-ladder.ts`, `classifierUnavailableNotice`; minted by
238
252
  * both classifier stations — the leg's gate and an ancestor's frozen classifier in the policy fold) — a
239
253
  * classification could NOT RULE (the classifier leg failed, or its per-level budget
240
- * `RunnerDeps.autoMode.timeoutMs` ran out) and says where the call went: `disposition: "asked"` = on down the
241
- * ask chain to the leg's approval seat (a live approver or a durable park decides; the handed-on ask carries
242
- * `requiresRealApproval`, so no automatic lane answers it; a declared departure from CC, which refuses either
243
- * way), `"denied"` = refused with the classifier's own sentence (no approval seat).
254
+ * `RunnerDeps.autoMode.timeoutMs` ran out) and says where the call went, read off the station's one approval
255
+ * route (`approvalRouteOf`): `disposition: "asked"` = on to the live approver, or on to the durable park — the
256
+ * sentence names which route, never whether the park then commits (the handed-on ask carries `requiresRealApproval`, so no automatic lane answers
257
+ * it; a declared departure from CC, which refuses either way), `"denied"` = refused with the classifier's own
258
+ * sentence (route `none`: no approver and no park facility).
244
259
  * `detail: { toolName, toolCallId, cause, disposition, sessionId?, runId? }` — `cause` is the
245
260
  * `AutoModeUnavailableCause` word. Once per classification that could not answer (a call judged by an ancestor's
246
261
  * classifier and the leg's own can carry two); not minted while the call is being aborted (a halt, or the run's end). Audience
@@ -364,17 +379,22 @@ export interface EngineNotice {
364
379
  * - `"config.execution_env_capability_invalid"` — a remote env DECLARES a boolean capability key
365
380
  * (`isolation`, `suspendable`, `canonicalPathAuthoritative`) with a non-boolean value; ABSENCE is a
366
381
  * supported reading of each and stays silent. Once per key per run; `detail: { key, valueType }`.
367
- * - `"config.secret_env_scrubbed"` (#1081) — the D1 fail-closed `scrub` policy withheld one or more
368
- * secret-shaped keys from a spawned child's environment (a tool's shell, or a language server).
382
+ * - `"config.secret_env_scrubbed"` (#1081) — the D1 fail-closed `scrub` policy, or the deployment's
383
+ * declared own credentials (`declareWithheldEnv`), withheld one or more keys from a spawned child's
384
+ * environment (a tool's shell, an oracle gate, or a language server).
369
385
  * Once per PROCESS, shared by every scrub site ({@link import("./secret-env.js").announceSecretEnvScrub});
370
- * `detail: { names }` — every withheld key's name, never a value.
386
+ * `detail: { names, ownCredentials? }` — every withheld key's name, never a value; `ownCredentials`, present
387
+ * only when one was withheld, the subset withheld as declared own credentials (under any policy, `"all"`
388
+ * included).
371
389
  * - `"config.permission_mode_unavailable"` — the permission mode a task requested is not the mode the leg runs
372
390
  * under: the deployment denies it (`by: "operator_deny"` — `RuntimeCaps.bypassPermissions` / `autoMode` is
373
- * `false`), a requested `auto` has no classifier face (`by: "face_absent"`), or a child's declaration was folded
374
- * under its parent's ceiling (`by: "inherited_ceiling"`, minted on the PARENT's stream with the child's
375
- * `sessionId` beside the parent's — a child's own manifest is not shown to the person). The leg runs in
376
- * `effective` (always `default` for the first two arms), announced once per leg and once per steer that folds;
377
- * `detail: { requested, effective, by, sessionId, runId }`. Audience `user`.
391
+ * `false`), a requested `auto` has no classifier face (`by: "face_absent"` — neither the leg's deps nor its chain carry
392
+ * one), or a child's declaration was folded under its parent's ceiling (`by: "inherited_ceiling"`). A CHILD's fold, of
393
+ * any of the three, is minted on the PARENT's stream too (`childSessionId` beside the parent's `sessionId` — a child's
394
+ * own manifest is not shown to the person). The leg runs in `effective` (always `default` for the first two arms),
395
+ * announced once per leg and once per steer that folds; `detail: { requested, effective, by, sessionId, runId,
396
+ * childSessionId? }` — `requested` is the word the fold folded (a child that requested nothing folds the word it
397
+ * inherited; the sentence says "(inherited)"). Audience `user`.
378
398
  * - `"config.permission_mode_invalid"`, `"config.spec_retired_key"` and `"config.shell_gate_value_retired"`
379
399
  * are ERROR codes, not notices (named here only to keep the mode family's spellings
380
400
  * in one place): a `permissionMode` word outside the closed set (on the spec or on a checkpoint row), a spec
@@ -395,7 +415,7 @@ export interface EngineNotice {
395
415
  * - `"permission.rule_store_unavailable"` — the person's persisted permission rules could not be read (a read
396
416
  * failure, a corrupt or unreadable store, a read that did not answer in time), so that read's rows were SKIPPED:
397
417
  * none of their allow, ask or deny rows took part in the decisions it served. At `"call"` the call asks a person
398
- * meanwhile (a deny row among the rows nobody could read may forbid it; the class is `governance_unavailable`,
418
+ * meanwhile (a deny row among the rows nobody could read may forbid it; the class is `rule_unreadable`,
399
419
  * which `bypassPermissions` keeps and a headless run refuses); at `"prepare"` the run's path tables lack the rows
400
420
  * for the whole run. `detail.at` names the read that found it first: `"prepare"` (the path rows the run's file and
401
421
  * shell checks take at run start — the next run reads them again) or `"call"` (one call's own rule check — every
@@ -855,8 +875,15 @@ export interface EngineNotice {
855
875
  * throw rendered twice keeps one id, two refusals sharing a code/scope/message get two. Omitted
856
876
  * (never fabricated) when the caller composes the line with no error in hand.
857
877
  *
858
- * The four SERVER-minted rows ({@link NOTICE_MINTER} `"server"`) — catalogued so their audience and detail shape
878
+ * The five SERVER-minted rows ({@link NOTICE_MINTER} `"server"`) — catalogued so their audience and detail shape
859
879
  * are read here like every other row; this engine never mints them. A server forwards them through the same sink.
880
+ * - `"permission.entry_mode_clamped"` — the permission mode the caller requested was clamped by the deployment's
881
+ * entry floor BEFORE the run reached this engine (the server's own clamp, through this package's
882
+ * `clampPermissionMode`), so the run starts under another mode. The disclosure is minted where the clamp happens:
883
+ * a fold this engine makes itself (a parent's ceiling, the deployment's deny bits, a missing classifier face) is
884
+ * `config.permission_mode_unavailable`, never this row. Audience `"user"`; `detail: { requested, effective,
885
+ * ceiling, sessionId }` — `requested` the caller's word, `effective` the word the run starts under, `ceiling`
886
+ * the floor that clamped it; no run id — the clamp happens at intake, before any run exists. Once per run.
860
887
  * - `"instructions.source_changed"` — the project instruction file this run injects is not the one the same
861
888
  * session's previous run injected (a file added, removed or renamed). Audience `"user"`; `detail: { previous,
862
889
  * current, sessionId }` — `previous` / `current` are chain-member relative paths, `null` = that run had none.
@@ -9,7 +9,7 @@ import { shellReadFaceWords } from "../tools/fs/shell-read-face.js";
9
9
  import { failedJudgmentDeny } from "./runner/policy-throw.js";
10
10
  import { thrownText } from "./thrown-value.js";
11
11
  import { refuseOutOfContractDecision } from "./tool-policy.js";
12
- import { applyUnansweredSilence, silencedBy } from "./shell-read-silence.js";
12
+ import { applyUnansweredSilence, readStationAnswer, silencedBy } from "./shell-read-silence.js";
13
13
  import { asksUnstated, permissionSeatAt, unstatedAskMessage } from "./mode-policy.js";
14
14
  export async function runGateFold(pass) {
15
15
  const { input, toolName, toolCallId, callSignal, ledger, notifier, adjudicate } = pass;
@@ -194,7 +194,9 @@ function shellReadAskMessage(toolName, verdict) {
194
194
  return `tool "${toolName}" ${verb} where the session's read boundary could not establish what is ${participle} — explicit approval required`;
195
195
  return `tool "${toolName}" ${verb} outside the directories allowed for this session — explicit approval required`;
196
196
  }
197
- function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
197
+ function applyShellReadVerdict(pass, answer, toolName, shellGated) {
198
+ const reading = readStationAnswer(answer, { toolName, toolCallId: pass.toolCallId, report: (e) => traceHookCrash(pass.input, e, pass.notifier) });
199
+ const verdict = reading.verdict;
198
200
  const account = verdict.sentence !== undefined && verdict.sentence.trim().length > 0 ? inlineUntrusted(verdict.sentence, PROBE_REASON_MAX) : undefined;
199
201
  if (verdict.writesPlaced === true)
200
202
  pass.shellWritesPlaced = true;
@@ -204,20 +206,20 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
204
206
  pass.targetUnresolved = true;
205
207
  if (verdict.unplacedOptions !== undefined)
206
208
  pass.shellUnplacedOptions = verdict.unplacedOptions;
207
- if (verdict.finding === "unestablishable" || verdict.finding === "declined" || verdict.finding === "unanswered" || verdict.unestablished === true)
209
+ if (reading.kind === "silence" || verdict.finding === "unestablishable" || verdict.finding === "declined" || verdict.unestablished === true)
208
210
  pass.shellReadUnestablished = true;
209
- if (verdict.disposition === "refuse") {
211
+ if (reading.kind === "refuse") {
210
212
  pass.decision = { action: "deny", message: verdict.sentence ?? `tool "${toolName}" reads what the session's read boundary refuses` };
211
213
  pass.deniedBy = "read_boundary";
212
214
  if (verdict.details !== undefined)
213
215
  pass.denyDetails = verdict.details;
214
216
  return;
215
217
  }
216
- if (verdict.finding === "unanswered") {
218
+ if (reading.kind === "silence") {
217
219
  applyUnansweredSilence(pass, verdict, { message: shellReadAskMessage(toolName, verdict), account, tightenedBy: shellGated ? "shell_gate" : "safety" });
218
220
  return;
219
221
  }
220
- switch (verdict.disposition) {
222
+ switch (reading.kind) {
221
223
  case "vouch":
222
224
  return;
223
225
  case "mandated": {
@@ -268,7 +270,7 @@ function applyShellReadVerdict(pass, verdict, toolName, shellGated) {
268
270
  return;
269
271
  }
270
272
  default: {
271
- const exhaustive = verdict.disposition;
273
+ const exhaustive = reading.kind;
272
274
  return exhaustive;
273
275
  }
274
276
  }
@@ -21,6 +21,7 @@ import { exitGate, passAskFace } from "./runner/gate-exit.js";
21
21
  import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js";
22
22
  import { traceHookCrash } from "./gate-crash-trace.js";
23
23
  import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
24
+ import { readStationAnswer } from "./shell-read-silence.js";
24
25
  import { policyThrowLine, reportPolicyThrow } from "./runner/policy-throw.js";
25
26
  const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
26
27
  function parkWith(suspendAsk, parkArgs, carry) {
@@ -679,14 +680,15 @@ export async function runGateLanes(pass) {
679
680
  catch {
680
681
  verdict = station.unanswered(pass.req, "the read station could not answer");
681
682
  }
682
- if (verdict.disposition !== "vouch") {
683
+ const reading = readStationAnswer(verdict, { toolName, toolCallId, report: (e) => traceHookCrash(input, e, notifier) });
684
+ if (reading.kind !== "vouch") {
683
685
  editDenied = {
684
686
  action: "deny",
685
- message: verdict.sentence ?? `the approved edit for "${toolName}" reads where the read boundary does not vouch — denied fail-closed; re-submit the command as a fresh call and answer its card`,
687
+ message: reading.verdict.sentence ?? `the approved edit for "${toolName}" reads where the read boundary does not vouch — denied fail-closed; re-submit the command as a fresh call and answer its card`,
686
688
  };
687
689
  pass.deniedBy = "read_boundary";
688
- if (verdict.details !== undefined)
689
- pass.denyDetails = verdict.details;
690
+ if (reading.verdict.details !== undefined)
691
+ pass.denyDetails = reading.verdict.details;
690
692
  }
691
693
  }
692
694
  }
@@ -8,7 +8,7 @@
8
8
  * verdict ended.
9
9
  */
10
10
  import { type AskClass, type AskOrigin, type AssertAllKeysHandled, type ModeReleaseReason, type PermissionMode, type PersistedRuleMandate } from "./ask-origin.js";
11
- import { type AutoModeClassifierRound } from "./auto-mode.js";
11
+ import { type AutoModeClassifierRound, type AutoModeFallbackCause } from "./auto-mode.js";
12
12
  /**
13
13
  * How the wait an ask was in ENDED — the closed set. One word per way a wait can end; the `who` on the
14
14
  * record says which party ended it and is fixed BY the word (see {@link Settlement}).
@@ -403,8 +403,8 @@ export interface ModeRefusal {
403
403
  * I7 `disposition.released` present ⇒ `kind === "allowed"`, no settlement, `mode` is `bypassPermissions`,
404
404
  * `askClass` is an ask class (a member of the closed set — SHAPE; whether the class was releasable is the mint's
405
405
  * law at mint time, read by the gate's mode-release arm, never re-judged here against today's table: a persisted
406
- * record outlives the policy of its day), every member of `classes` is an ask class, `origin` / `reason`, when
407
- * present, are members of their closed sets, and `reason` names a member of `classes` (the mint takes it from
406
+ * record outlives the policy of its day), every member of `classes` is an ask class, `origin` / `mandate` / `reason`,
407
+ * when present, are members of their closed sets, and `reason` names a member of `classes` (the mint takes it from
408
408
  * that set) — a mode releases nothing a person answered;
409
409
  * I8 `deniedBy === "mode"` ⇔ `mode` present (`bypassPermissions` | `dontAsk`) with `askClass` and `classes`.
410
410
  * Nothing here is read out of an ABSENCE: an ordinary allow with no ask is `{ disposition: { kind: "allowed" } }`,
@@ -416,20 +416,86 @@ export interface GateOutcome {
416
416
  settlement?: Settlement;
417
417
  origin?: AskOrigin;
418
418
  }
419
+ /** A release as `major.minor.patch`: the version whose engine stopped minting a retired word. */
420
+ type ReleaseVersion = `${number}.${number}.${number}`;
419
421
  /**
420
- * The one invariant screen for a {@link GateOutcome} (I1–I8 above, plus the closed-set words each member carries —
421
- * `settlement.who.party` among them, {@link SETTLEMENT_PARTIES}). Runs
422
- * where an outcome enters a wire face — the engine's settlement sideband before the `tool_end` frame, and
423
- * the decide lane before a durable row is resolved. Returns the defects as sentences (an empty list is a
424
- * clean record); the caller decides the fail-loud form (report + withhold, or refuse), never silently
425
- * repairs the record.
422
+ * The words that LEFT a closed set a gate record carries, one list per set, each word with the version whose engine stopped
423
+ * minting it. A persisted record outlives the vocabulary of its day as it outlives the policy of its day, so the screen's
424
+ * archive reading ({@link screenGateOutcome}) reads each set as its live members plus these, and its producer reading reads the
425
+ * live members alone. Every set the screen reads has a list (empty when nothing left it); no word is both live and retired
426
+ * ({@link RetiredMembersAreNotLive}). A word-indexed table ({@link DENIED_BY_MAY_VETO}, {@link DENIED_BY_MAY_CARRY_CAUSE},
427
+ * {@link SETTLEMENT_IS_REFUSAL}) keeps rows for live words only: a retired word is read as granted nothing by it.
426
428
  */
429
+ export declare const RETIRED_MEMBERS: Readonly<{
430
+ askClass: readonly (Readonly<{
431
+ word: "write_protection_unresolved";
432
+ retiredIn: ReleaseVersion;
433
+ }> | Readonly<{
434
+ word: "governance_unavailable";
435
+ retiredIn: ReleaseVersion;
436
+ }>)[];
437
+ modeReleaseReason: readonly (Readonly<{
438
+ word: "write_protection_unresolved";
439
+ retiredIn: ReleaseVersion;
440
+ }> | Readonly<{
441
+ word: "rule_unreadable";
442
+ retiredIn: ReleaseVersion;
443
+ }>)[];
444
+ askOrigin: readonly Readonly<{
445
+ word: "unresolvable";
446
+ retiredIn: ReleaseVersion;
447
+ }>[];
448
+ persistedRuleMandate: readonly Readonly<{
449
+ word: "write_protection_unresolved";
450
+ retiredIn: ReleaseVersion;
451
+ }>[];
452
+ deniedBy: readonly Readonly<{
453
+ word: "plan_mode";
454
+ retiredIn: ReleaseVersion;
455
+ }>[];
456
+ settlementKind: readonly [];
457
+ settlementParty: readonly [];
458
+ classifierDenyCause: readonly [];
459
+ permissionMode: readonly [];
460
+ autoModeFallbackCause: readonly [];
461
+ }>;
462
+ type MemberSet = keyof typeof RETIRED_MEMBERS;
463
+ type RetiredWord<S extends MemberSet> = (typeof RETIRED_MEMBERS)[S][number]["word"];
464
+ /** Compile-time fence: `never` while no retired word is also a live member of its set. */
465
+ export type RetiredMembersAreNotLive = AssertAllKeysHandled<Extract<RetiredWord<"askClass">, AskClass> | Extract<RetiredWord<"modeReleaseReason">, ModeReleaseReason> | Extract<RetiredWord<"askOrigin">, AskOrigin> | Extract<RetiredWord<"persistedRuleMandate">, PersistedRuleMandate> | Extract<RetiredWord<"deniedBy">, DeniedBy> | Extract<RetiredWord<"settlementKind">, SettlementKind> | Extract<RetiredWord<"settlementParty">, SettlementParty> | Extract<RetiredWord<"classifierDenyCause">, ClassifierDenyCause> | Extract<RetiredWord<"permissionMode">, PermissionMode> | Extract<RetiredWord<"autoModeFallbackCause">, AutoModeFallbackCause>>;
466
+ /** Compile-time fence: `never` while every reason word, live or retired, is an ask class of its day (I7 reads it in `classes`). */
467
+ export type RetiredReasonsAreClassWords = AssertAllKeysHandled<Exclude<RetiredWord<"modeReleaseReason">, AskClass | RetiredWord<"askClass">>>;
468
+ /** Whether `word` left the closed set `set` ({@link RETIRED_MEMBERS}). A set name outside the registry throws. */
469
+ export declare function isRetiredMember<S extends MemberSet>(set: S, word: unknown): word is RetiredWord<S>;
427
470
  /**
428
- * The PRODUCER's half of I7, read beside {@link screenGateOutcome} wherever this engine composes or screens a record it is
429
- * minting today: a mode release names a class this engine's table lets a mode release (`releasableAskClassesFor` on some
430
- * face). Policy, not shape — it moves between versions (7.50.0 made `rule_unreadable` immune), so a reader of PERSISTED
431
- * records (a replay, an archive) does not read it: a release lawful when minted is not a defect under a later table.
471
+ * The release law of THIS engine (I7's policy half): a mode release names a set of classes that one face of the engine's table
472
+ * releases whole (`releasableAskClassesFor`) — the arbitration releases a set only when no member is immune on the call's face.
473
+ * Policy, not shape — it moves between versions — so only the producer reading of {@link screenGateOutcome} reads it.
432
474
  * Module-level only; not part of the package root.
433
475
  */
434
- export declare function releasePolicyDefects(g: GateOutcome): string[];
435
- export declare function screenGateOutcome(g: GateOutcome): string[];
476
+ export declare function releasePolicyDefects(g: unknown): string[];
477
+ /** The members only the denied arm of a {@link GateDisposition} carries. */
478
+ declare const DENY_ONLY_MEMBERS: readonly ["deniedBy", "cause", "mode", "askClass", "classes"];
479
+ type DenyOnlyMember = Exclude<keyof Extract<GateDisposition, {
480
+ kind: "denied";
481
+ }>, keyof Extract<GateDisposition, {
482
+ kind: "allowed";
483
+ }>>;
484
+ /** Compile-time fence: `never` while the list is exactly the denied arm's own members. */
485
+ export type DenyOnlyMembersAreListed = AssertAllKeysHandled<Exclude<DenyOnlyMember, (typeof DENY_ONLY_MEMBERS)[number]> | Exclude<(typeof DENY_ONLY_MEMBERS)[number], DenyOnlyMember>>;
486
+ /**
487
+ * The one invariant screen for a {@link GateOutcome} (I1–I8 above, plus the closed-set word each member carries). Two readings:
488
+ * - `"archive"` (the default) — a record read back from where it was kept (a replay, a ledger, a durable row): its shape, with
489
+ * every closed set read as its live members plus {@link RETIRED_MEMBERS}. A record lawful when minted is not a defect later.
490
+ * - `"producer"` — a record THIS engine is composing or publishing now: the live sets alone, and the engine's release law
491
+ * ({@link releasePolicyDefects}). The gate's exit and its station screen read this.
492
+ * The record is read as whatever it holds (`unknown`): a member that is not the shape the type names — `null`, an array, a
493
+ * primitive where a record belongs, an object where a word belongs — is a defect naming its position, never an exception, and
494
+ * every class, reason, origin and mandate word is read wherever the record carries it.
495
+ * Returns the defects as sentences (an empty list is a clean record); the caller decides the fail-loud form (report + withhold,
496
+ * or refuse), never silently repairs the record. An options value that is not an object, or a reading outside the two, throws.
497
+ */
498
+ export declare function screenGateOutcome(g: unknown, opts?: {
499
+ readonly reading?: "archive" | "producer";
500
+ }): string[];
501
+ export {};