@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
@@ -130,6 +130,18 @@ export interface ToolCallRequest {
130
130
  * a Runner with no `memoryEngineDir` would stamp. Read-only, frozen.
131
131
  */
132
132
  exemptRoots?: readonly string[];
133
+ /**
134
+ * The request's statement that nobody can be asked about THIS call — the word of a run's own declaration
135
+ * (`TaskSpec.approvalReach`, the closed set `APPROVAL_REACH`), stated per request. Present (`"none"`) on every request the
136
+ * engine's synthetic probes hand a policy layer (the "would this call be refused outright?" judgments: the visibility
137
+ * probes of a file the model did not ask to read, the background memory consolidation's write probes); absent on a call
138
+ * the gate adjudicates, and on a bare request. A policy that would put the call to a person — an approval seat it awaits,
139
+ * a hook it invokes, a card or a message it sends — answers at once instead: the engine's own factories read this member
140
+ * before reaching a seat and answer {@link noApproverSeatDeny}; a deployment closure does the same. The signal handed
141
+ * beside such a request is LIVE (the judge's wait for that layer): an aborted one means only that the task ended.
142
+ * Read-only.
143
+ */
144
+ approvalReach?: import("./wiring-manifest.js").ApprovalReach;
133
145
  /**
134
146
  * design/80 D-E-core: read-only per-run budget snapshot so a STATELESS policy can self-limit against
135
147
  * state that survives suspend/resume (a fresh in-memory counter would reset every leg — the supervisor
@@ -272,11 +284,25 @@ export declare function attest<T extends object>(d: T, call: {
272
284
  }, members: AttestationMembers): T;
273
285
  /** `d` carrying `record` (a fresh object), or `d` itself when there is no record to put on it. */
274
286
  export declare function withAttestation<T extends object>(d: T, record: EngineAttestation | undefined): T;
287
+ /** The `no_approver`-attested deny an engine policy answers on a request that states nobody can be asked about it
288
+ * ({@link ToolCallRequest.approvalReach}) instead of reaching a seat — read by a probe as "would ask an approver". */
289
+ export declare function noApproverSeatDeny(req: {
290
+ toolCallId: string;
291
+ toolName: string;
292
+ }, message: string, origin: AskOrigin): PermissionResult;
275
293
  /** The `task_aborted`-attested deny an engine policy answers on an aborted signal instead of reaching a seat. */
276
294
  export declare function abortedSeatDeny(req: {
277
295
  toolCallId: string;
278
296
  toolName: string;
279
297
  }, message: string, origin: AskOrigin): PermissionResult;
298
+ /** THE question every engine factory asks before it would reach a person (an approver it awaits, a hook it invokes): may the
299
+ * seat be reached? Not when the task ended (an aborted signal ⇒ {@link abortedSeatDeny}, read first) nor when the request
300
+ * states that nobody can be asked about the call ({@link ToolCallRequest.approvalReach} ⇒ {@link noApproverSeatDeny});
301
+ * `undefined` ⇒ it may. `say` is the factory's own sentence for each. */
302
+ export declare function unaskedSeatAnswer(req: Pick<ToolCallRequest, "toolCallId" | "toolName" | "approvalReach">, signal: AbortSignal | undefined, say: {
303
+ aborted: string;
304
+ unreachable: string;
305
+ }, origin: AskOrigin): PermissionResult | undefined;
280
306
  /**
281
307
  * design/252 G-7 — how long an approver-attribution identifier may be.
282
308
  *
@@ -575,12 +601,14 @@ export declare function decisionText(d: PermissionResult): string | undefined;
575
601
  *
576
602
  * `check` may be async, which is also how **human-in-the-loop approval** works: a deployment can
577
603
  * hold the promise open until an operator approves/denies. `signal` fires when the task aborts
578
- * (timeout / max turns / cancel) or the asking turn is interrupted (design/384) — honor it to release a
579
- * pending approval (F4; the Runner also races `check` against it). An already-aborted one: see `check`.
604
+ * (timeout / max turns / cancel) or the asking turn is interrupted (design/384), and on a synthetic probe when the judge
605
+ * stops waiting for this layer — honor it to release a pending approval (F4; the Runner also races `check` against it). An
606
+ * already-aborted one: see `check`. A request that states nobody can be asked about it: {@link ToolCallRequest.approvalReach}.
580
607
  */
581
608
  export interface ToolPolicy {
582
609
  /** @contract policy.check.aborted_signal_reaches_nobody — handed an already-aborted signal, `check` reaches no person and
583
- * no external approval (it answers at once, or with a `task_aborted` deny); visibility probes rely on it. */
610
+ * no external approval (it answers at once, or with a `task_aborted` deny). The engine's probes do not rely on it: they hand a
611
+ * live signal and state on the request that nobody can be asked (`approvalReach: "none"`, answered with `noApproverSeatDeny`). */
584
612
  check(req: ToolCallRequest, signal?: AbortSignal): PermissionResult | Promise<PermissionResult>;
585
613
  /**
586
614
  * The policy's CONSTRAINT PROJECTION — its deterministic DENY-verdict half expressed as pure data
@@ -1295,10 +1323,12 @@ export interface AskRequest {
1295
1323
  * mint site added later is covered by construction); optional on the type because a deployment may
1296
1324
  * invoke its approver function directly. */
1297
1325
  readonly boundInputHash?: string;
1298
- /** A call to the engine's `Skill` tool: the hash of {@link boundGrants} — the digest a parked approval of the same card is judged by, so a live card reconciles against its parked row by `(toolCallId, boundGrantDigest)` and an answer is remembered by "this tool + this binding". Present exactly on that call (whoever asked, however the question was worded), absent on every other card; `message` renders the same lists narrowed to what releases in the leg's mode. Reconciliation metadata, never adjudication input. */
1326
+ /** A call to the engine's `Skill` tool: the hash of {@link boundGrants} — the digest a parked approval of the same card is judged by, so a live card reconciles against its parked row by `(toolCallId, boundGrantDigest)` and an answer is remembered by "this tool + this binding". Present exactly on that call (whoever asked, however the question was worded); every other card carries {@link boundGrantAbsence} instead; `message` renders the same lists narrowed to what releases in the leg's mode. Reconciliation metadata, never adjudication input. */
1299
1327
  readonly boundGrantDigest?: string;
1300
1328
  /** The value {@link boundGrantDigest} hashes: `{ skill, allowedTools, disallowedTools }` in canonical spelling (de-duplicated, sorted), `skill: null` when no loaded skill is named; a fresh copy per ask; same presence. Display metadata (deployment text — render with contextual escaping). */
1301
1329
  readonly boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
1330
+ /** Why this card carries no {@link boundGrantDigest} — every card the engine mints carries exactly one of the two: `not_bindable` (the call is not an activation of the engine's `Skill` tool, so it binds no grant and an answer may be remembered by the tool) or `unreadable` (a Skill card whose binding cannot be read — said by a stored row's reader; a live card always has its digest). The same factory mints both members; the park row and the inbox projection carry the same member. Reconciliation metadata, never adjudication input. */
1331
+ readonly boundGrantAbsence?: import("./skill-grant-binding.js").BoundGrantAbsence;
1302
1332
  /**
1303
1333
  * design/179 §4, re-formed by design/375 §3.1 (BREAKING — this seat REPLACES the retired
1304
1334
  * `ruleSuggestions` key, which is never emitted again; the new name is the safe failure posture for
@@ -1358,8 +1388,9 @@ export interface AskRequest {
1358
1388
  * seat's to say: it is answered per ask by {@link readRootCandidate}, which is present exactly when
1359
1389
  * widening the session's read directories would clear this one and absent for every other member of
1360
1390
  * the family — so a surface reads that seat and never infers an answer from this word;
1361
- * · `"shadowed"` — a rule the person already wrote is speaking and does not clear this ask (a
1362
- * matching allow rule that the lane refuses to honor here, or their own explicit `ask` rule);
1391
+ * · `"allow_outranked"` — an allow-layer member already matched this call and this question outranks it
1392
+ * ({@link allowRuleShadowed} says which): another allow rule would be outranked the same way;
1393
+ * · `"ask_rule_matched"` — an explicit `ask` rule matched: it keeps asking while it stands;
1363
1394
  * · `"lane_cannot_speak"` — the lane is armed and this ask IS clearable, but the offer lane
1364
1395
  * produced no rule for this command. Usually because the grammar has no text for it (a
1365
1396
  * substitution, a subshell, a backgrounded process, or a simple command carrying a redirection —
@@ -1381,7 +1412,7 @@ export interface AskRequest {
1381
1412
  * and a surface that ignores it renders exactly the card it rendered before. The durable park row
1382
1413
  * carries the same seat under the same contract, filled by the same one factory.
1383
1414
  */
1384
- readonly ruleOffersAbsence?: "mandated" | "lane_cannot_speak" | "shadowed";
1415
+ readonly ruleOffersAbsence?: import("./ask-origin.js").RuleOffersAbsence;
1385
1416
  /** The human/model-readable prompt explaining what needs approval. */
1386
1417
  message: string;
1387
1418
  /** Runner-filled source identity (design S1d §1): the end-user {@link AskRequest} issuing task's
@@ -1444,7 +1475,7 @@ export interface AskRequest {
1444
1475
  * remembered from an earlier call, at any scope, a session included — can retire this question; it is
1445
1476
  * answered only for THIS call, by an answerer (see the ask-arm member of the same name for who may). ABSENT is not the inverse claim: a person's own explicit `ask` rule and a
1446
1477
  * matching allow rule the lane already refused both leave it absent and still block rule clearance
1447
- * ({@link ruleOffersAbsence} words those `shadowed`). The wire twin of the
1478
+ * ({@link ruleOffersAbsence} words them `ask_rule_matched` / `allow_outranked`). The wire twin of the
1448
1479
  * {@link PermissionResult} ask-arm member of the same name, minted once by the engine at the gate's
1449
1480
  * origin stamp and copied here by the four mint stations. The DOORS are enumerated once, at
1450
1481
  * {@link import("./ask-origin.js").ruleClearanceOf}: the tool seat's own egress / irreversibility
@@ -1452,7 +1483,7 @@ export interface AskRequest {
1452
1483
  * that could not answer, a write the protection judge matched or could not resolve, governance
1453
1484
  * ({@link requiresRealApproval}), a hook's screening authority, and an ask an ANCESTOR's approver
1454
1485
  * decides. Not a door: a person's own explicit `ask` rule, which {@link ruleOffersAbsence} words
1455
- * `shadowed` — a rule they wrote is speaking, and a posture answering it crosses no mandate.
1486
+ * `ask_rule_matched` — a rule they wrote is speaking, and a posture answering it crosses no mandate.
1456
1487
  *
1457
1488
  * WHY a surface must read THIS member and not its neighbour. {@link ruleOffersAbsence} answers what
1458
1489
  * the RULE-OFFER LANE has to give, and that lane is structurally silent for any tool it does not
@@ -15,7 +15,7 @@ import { heldBy } from "./tool-name-readings.js";
15
15
  import { boundInputHashOf } from "./canonical-json.js";
16
16
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
17
17
  import { describeRejectedValue } from "./tool-search-setting.js";
18
- import { thrownText } from "./thrown-value.js";
18
+ import { egressText, egressThrownText } from "./untrusted-egress.js";
19
19
  import { assertNoRetiredToolPolicyKeys, RETIRED_DECISION_MEMBERS } from "./retired-keys.js";
20
20
  import { isEngineRefusal, nestedRecordsOf, receiveContract, receiveNestedRecords, refusedOutOfContract, screenContractValues } from "./decision-receipt.js";
21
21
  export { isEngineRefusal } from "./decision-receipt.js";
@@ -102,8 +102,25 @@ export function attest(d, call, members) {
102
102
  export function withAttestation(d, record) {
103
103
  return record === undefined ? d : { ...d, attestation: record };
104
104
  }
105
+ const UNASKED_SEAT_SETTLEMENTS = new Set(["task_aborted", "no_approver"]);
106
+ function seatNeverAsked(settlement) {
107
+ return settlement !== undefined && UNASKED_SEAT_SETTLEMENTS.has(settlement.kind);
108
+ }
109
+ function unaskedSeatDeny(req, kind, message, origin) {
110
+ return attest({ action: "deny", message }, req, { settlement: settledByNobody(kind), origin });
111
+ }
112
+ export function noApproverSeatDeny(req, message, origin) {
113
+ return unaskedSeatDeny(req, "no_approver", message, origin);
114
+ }
105
115
  export function abortedSeatDeny(req, message, origin) {
106
- return attest({ action: "deny", message }, req, { settlement: settledByNobody("task_aborted"), origin });
116
+ return unaskedSeatDeny(req, "task_aborted", message, origin);
117
+ }
118
+ export function unaskedSeatAnswer(req, signal, say, origin) {
119
+ if (signal?.aborted === true)
120
+ return abortedSeatDeny(req, say.aborted, origin);
121
+ if (req.approvalReach === "none")
122
+ return noApproverSeatDeny(req, say.unreachable, origin);
123
+ return undefined;
107
124
  }
108
125
  export const APPROVER_ATTRIBUTION_MAX_CHARS = 256;
109
126
  const APPROVER_REFUSED_CHARS_RE = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/u;
@@ -442,8 +459,9 @@ export function createApprovalPolicy(opts) {
442
459
  }
443
460
  if (nameListHolds(need, req) || namespacedCoveringHit(needCovering, req)) {
444
461
  const attestOver = (inputDigest) => (d, settlement) => ({ ...d, attestation: mintAttestation(req, { settlement, origin: "policy", inputDigest }) });
445
- if (signal?.aborted)
446
- return abortedSeatDeny(req, `approval aborted for "${req.toolName}" (run or turn interrupted)`, "policy");
462
+ const unasked = unaskedSeatAnswer(req, signal, { aborted: `approval aborted for "${req.toolName}" (run or turn interrupted)`, unreachable: `approval for "${req.toolName}" was not requested: nobody can be asked about this call` }, "policy");
463
+ if (unasked !== undefined)
464
+ return unasked;
447
465
  const shown = tryCloneArgs(req.args);
448
466
  if (!shown.ok) {
449
467
  return attestOver(boundInputHashOf(req.args))({ action: "deny", message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${shown.reason}) — denied fail-closed` }, settledByNobody("presentation_failed"));
@@ -565,7 +583,7 @@ export function combinePolicies(...policies) {
565
583
  for (const p of policies) {
566
584
  const d = refuseOutOfContractDecision(await p.check(current, signal));
567
585
  if (d.action === "deny") {
568
- if (attestationOf(d, current)?.settlement?.kind !== "task_aborted")
586
+ if (!seatNeverAsked(attestationOf(d, current)?.settlement))
569
587
  return settleDeny(d);
570
588
  seatDeny ??= settleDeny(d);
571
589
  continue;
@@ -854,10 +872,10 @@ export function tryCloneArgs(v) {
854
872
  }
855
873
  }
856
874
  export function describeThrown(err) {
857
- const contained = containThrownText(thrownText(err));
875
+ const contained = containThrownText(egressThrownText("same-principal", err));
858
876
  if (contained !== "")
859
877
  return contained;
860
- return containThrownText(describeRejectedValue(err));
878
+ return containThrownText(egressText("same-principal", describeRejectedValue(err)));
861
879
  }
862
880
  const THROWN_TEXT_MAX = 300;
863
881
  function containThrownText(text) {
@@ -18,7 +18,7 @@ export type { PermissionMode } from "./ask-origin.js";
18
18
  export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ReadRootGrantCandidate, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
19
19
  export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
20
20
  export type { AgentDefinition, SkillSpec } from "./agent-definition.js";
21
- export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
21
+ export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit, McpRevocationLedger, McpMountIdentity } from "./mcp-server-spec.js";
22
22
  export type { TaskLimits } from "./task-limits.js";
23
23
  export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult, HumanReviewGateRow } from "./task-result.js";
24
24
  export type { TerminalCause, PausedCause } from "./terminal-cause.js";
@@ -26,8 +26,8 @@ export type { TaskEventIdentity, BrainStatusPhase, BrainRetryErrClass, BrainStat
26
26
  export type { TaskStream } from "./task-stream.js";
27
27
  export type { WorkflowGovernanceBaseline, RuntimeCaps, AutoModeDenySource } from "./runtime-caps.js";
28
28
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
29
- export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
30
- export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle, emitsChildRowFrames, taskStartedFrame } from "./delegation-frames.js";
29
+ export type { BackgroundChildEvent, BackgroundChildRow, DelegationLifecycleEvent } from "./delegation-frames.js";
30
+ export { __resetMalformedDelegationSeatAnnouncement, backgroundChildTerminal, deliverDelegationLifecycle, emitsChildRowFrames, taskStartedFrame } from "./delegation-frames.js";
31
31
  export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
32
32
  export type { EngineNotice, OwedUserInputAccount, UserInputUndrainedCause } from "./engine-notice.js";
33
33
  export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./engine-notice.js";
@@ -1,4 +1,4 @@
1
1
  export { MODEL_ROLES } from "./model-seat.js";
2
2
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
3
- export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle, emitsChildRowFrames, taskStartedFrame } from "./delegation-frames.js";
3
+ export { __resetMalformedDelegationSeatAnnouncement, backgroundChildTerminal, deliverDelegationLifecycle, emitsChildRowFrames, taskStartedFrame } from "./delegation-frames.js";
4
4
  export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES, USER_INPUT_UNDRAINED_FAMILIES } from "./engine-notice.js";
@@ -1,18 +1,67 @@
1
- import { type RedactionReport } from "./arg-summary.js";
2
- export type { RedactionFinding, RedactionReport, RedactionConfidence, RedactionSource, SummarizableFinding, } from "./arg-summary.js";
3
- export { summarizeRedactions } from "./arg-summary.js";
4
- export declare function redactSecrets(s: string, report?: RedactionReport): string;
5
- export declare function redactHostLeaks(s: string, report?: RedactionReport): string;
6
- /** Secret-redact + size-bound (NO injection fence) — the SAME-PRINCIPAL tier: workflow outputs/results/errors
7
- * riding back to the launching model/script, run rows behind scope-gated observe. Paths/URLs survive
8
- * (the assertion pattern depends on them); secrets never do. */
9
- export declare function boundedRedactedSummary(value: unknown, max: number): string;
1
+ import { type RedactionReport } from "./redaction-passes.js";
2
+ export type { RedactionFinding, RedactionReport, RedactionConfidence, RedactionSource, SummarizableFinding, } from "./redaction-passes.js";
3
+ export { summarizeRedactions } from "./redaction-passes.js";
10
4
  /**
11
- * The FULL untrusted-egress transform (design/97 CORE-2): full host-leak redaction + size-bound + FENCE. Use
5
+ * Every exit that redacts — into a model's context, a host's event or error lane, a log line, a person's screen — does it
6
+ * with ONE pass, its tier's, and nothing else redacts: a text through {@link egressText}, a thrown value's text through
7
+ * {@link egressThrownText}, a value with members (a thrown error, a marks bag) through {@link egressValue}, which runs the
8
+ * same pass over every string member. What comes out is an {@link Egress}: a string the type checker knows came out of
9
+ * that tier. What a tool RETURNS is its content, read by the model that asked for it, and is not redacted (CC redacts
10
+ * nothing there either); what is redacted is what the engine itself words or carries: errors, summaries, run rows.
11
+ *
12
+ * Two tiers, because the faces have two audiences (CC 2.1.212 alignment):
13
+ * - **`same-principal`** — the model, the script and the host that launched the work (tool results, workflow agent
14
+ * outputs / results / errors, run rows behind the scope-gated observe API, host error lanes): the credential
15
+ * vocabulary only. Paths and URLs in a worker's answer are the caller's OWN machine's paths — redacting them killed the
16
+ * cross-agent assertion pattern ("return the file you wrote" → `[redacted-path]`) while protecting no one;
17
+ * cross-tenant isolation on these faces is ACCESS CONTROL (scope gates), not string surgery. CC redacts nothing here;
18
+ * sema keeps the credential pass as a declared superset.
19
+ * - **`human`** — text shown to a person or exported beyond the launching principal: the credential vocabulary plus
20
+ * every URL and absolute path — host internals, and exfil-lure links in untrusted text.
21
+ * {@link untrustedEgressForHuman} additionally FENCES its text (design/97 CORE-2), so untrusted content cannot pose as
22
+ * instructions to its reader.
23
+ */
24
+ export type EgressTier = "same-principal" | "human";
25
+ declare const EGRESS_TIER: unique symbol;
26
+ /** A string that came out of tier `T`'s pass. Only this module mints one. */
27
+ export type Egress<T extends EgressTier> = string & {
28
+ readonly [EGRESS_TIER]: T;
29
+ };
30
+ /** Tier `tier`'s one pass over a text. */
31
+ export declare function egressText<T extends EgressTier>(tier: T, text: string, report?: RedactionReport): Egress<T>;
32
+ /** The text a thrown value reads as (`thrownText`), through tier `tier`'s pass: the one form a thrown value's words take
33
+ * on their way out of the engine. */
34
+ export declare function egressThrownText<T extends EgressTier>(tier: T, err: unknown): Egress<T>;
35
+ /** The `same-principal` tier's pass, under the name hosts call it by (the engine calls {@link egressText}). */
36
+ export declare function redactSecrets(s: string, report?: RedactionReport): Egress<"same-principal">;
37
+ /** The `human` tier's pass, under the name hosts call it by (the engine calls {@link egressText}). */
38
+ export declare function redactHostLeaks(s: string, report?: RedactionReport): Egress<"human">;
39
+ /**
40
+ * Tier `tier`'s pass over every string member of `value`, returned as a DATA SNAPSHOT — never the value itself, so nothing
41
+ * that leaves can run the value's code again (a getter, a serializer, a proxy trap): a host's serializer, a log's inspector
42
+ * and the loop's own readers (`thrownText`, `thrownMarksOf`) reach what was read, once, and passed. The rules, one each:
43
+ * - a string goes through {@link egressText}, and so does every member NAME; a function is code, not data, and is not
44
+ * carried at any depth (the value handed in included: it comes back `undefined`); any other primitive is itself;
45
+ * - an error (any realm, any class) is re-minted as a plain `Error` — never on the thrown value's prototype, whose
46
+ * serializers and accessors would run again — carrying EVERY own member (`message`, `stack`, `cause`, `errors`,
47
+ * `details`, `code` …) and its `name`, `message`, `code` and `stack` read once through its chain, all as own data;
48
+ * - any other object is what JSON reads: its `toJSON(name)` when it has one (a `URL` is its `href`, a `Date` its ISO
49
+ * text; one that answers itself is read for its members), otherwise its own enumerable members;
50
+ * - a member whose name is a credential name (the vocabulary's record-key view: `password`, `Authorization`,
51
+ * `x-api-key`) and whose reading is a string is the marker whole;
52
+ * - cycles and shared members stay shared on the snapshot.
53
+ */
54
+ export declare function egressValue<T extends EgressTier, V>(tier: T, value: V): V;
55
+ /** The `same-principal` tier's pass + size-bound (NO injection fence): workflow outputs/results/errors riding back to
56
+ * the launching model/script, run rows behind scope-gated observe. Paths/URLs survive (the assertion pattern depends on
57
+ * them); secrets never do. */
58
+ export declare function boundedRedactedSummary(value: unknown, max: number): Egress<"same-principal">;
59
+ /**
60
+ * The FULL untrusted-egress transform (design/97 CORE-2): the `human` tier's pass + size-bound + FENCE. Use
12
61
  * for any worker prompt/output (#5 transcript) or steer content (#6) shown to a HUMAN or exported beyond the
13
62
  * launching principal, so untrusted text can neither leak host internals nor pose as instructions.
14
63
  */
15
64
  export declare function untrustedEgressForHuman(value: unknown, opts: {
16
65
  label: string;
17
66
  max: number;
18
- }): string;
67
+ }): Egress<"human">;
@@ -1,8 +1,9 @@
1
1
  import { delimitUntrusted, neutralizeForFence } from "./untrusted-text.js";
2
- import { egressPasses, runRedactionPasses } from "./arg-summary.js";
3
- import { CREDENTIAL_VOCABULARY, egressPassGroups, URI_SCHEME_SOURCE } from "./credential-vocabulary.js";
2
+ import { egressPasses, runRedactionPasses } from "./redaction-passes.js";
3
+ import { CREDENTIAL_VOCABULARY, egressPassGroups, isSensitiveRecordKey, URI_SCHEME_SOURCE } from "./credential-vocabulary.js";
4
+ import { isErrorLike, readMember, thrownText } from "./thrown-value.js";
4
5
  import { sliceHeadSafe } from "./surrogate-safe-slice.js";
5
- export { summarizeRedactions } from "./arg-summary.js";
6
+ export { summarizeRedactions } from "./redaction-passes.js";
6
7
  const URL_OF_ANY_PROTOCOL = new RegExp(String.raw `${URI_SCHEME_SOURCE}:\/\/[^\s"'\`]+`, "gi");
7
8
  const BARE_USERNAME_MAX_LEN = 20;
8
9
  function redactUserinfo(scheme, userinfoWithAt) {
@@ -18,16 +19,126 @@ function redactUserinfo(scheme, userinfoWithAt) {
18
19
  const SECRET_TIER_PASSES = egressPasses(egressPassGroups(CREDENTIAL_VOCABULARY), {
19
20
  "url-credentials": (_m, scheme, userinfo) => redactUserinfo(scheme, userinfo),
20
21
  });
21
- export function redactSecrets(s, report) {
22
- return runRedactionPasses(s, SECRET_TIER_PASSES, report);
23
- }
24
22
  const HOST_LEAK_PASSES = [
25
23
  ...SECRET_TIER_PASSES,
26
24
  { kind: "url", confidence: "medium", marker: "[redacted-url]", source: "untrusted-egress", re: URL_OF_ANY_PROTOCOL, replace: "[redacted-url]" },
27
25
  { kind: "fs-path", confidence: "low", marker: "[redacted-path]", source: "untrusted-egress", re: /(?:\/[A-Za-z0-9_.-]+){2,}/g, replace: "[redacted-path]" },
28
26
  ];
27
+ const TIER_PASSES = { "same-principal": SECRET_TIER_PASSES, human: HOST_LEAK_PASSES };
28
+ export function egressText(tier, text, report) {
29
+ return runRedactionPasses(text, TIER_PASSES[tier], report);
30
+ }
31
+ export function egressThrownText(tier, err) {
32
+ return egressText(tier, thrownText(err));
33
+ }
34
+ export function redactSecrets(s, report) {
35
+ return egressText("same-principal", s, report);
36
+ }
29
37
  export function redactHostLeaks(s, report) {
30
- return runRedactionPasses(s, HOST_LEAK_PASSES, report);
38
+ return egressText("human", s, report);
39
+ }
40
+ const VALUE_DEPTH_MAX = 32;
41
+ const VALUE_MEMBERS_MAX = 2_000;
42
+ const WHOLE = "[redacted]";
43
+ export function egressValue(tier, value) {
44
+ const snapshots = new Map();
45
+ let budget = VALUE_MEMBERS_MAX;
46
+ const ownKeys = (o, all) => {
47
+ try {
48
+ return all ? Reflect.ownKeys(o) : Object.keys(o);
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ };
54
+ const prototypeOf = (o) => {
55
+ try {
56
+ return Object.getPrototypeOf(o);
57
+ }
58
+ catch {
59
+ return Object.prototype;
60
+ }
61
+ };
62
+ const readOwn = (o, key) => {
63
+ try {
64
+ const d = Reflect.getOwnPropertyDescriptor(o, key);
65
+ if (d === undefined)
66
+ return { value: undefined, enumerable: true };
67
+ return { value: "value" in d ? d.value : d.get?.call(o), enumerable: d.enumerable ?? true };
68
+ }
69
+ catch {
70
+ return { value: undefined, enumerable: true };
71
+ }
72
+ };
73
+ const member = (read, depth, name) => {
74
+ const out = pass(read, depth + 1, name);
75
+ return typeof out === "string" && isSensitiveRecordKey(name) ? WHOLE : out;
76
+ };
77
+ const fill = (o, depth, keys, snapshot) => {
78
+ snapshots.set(o, snapshot);
79
+ for (const key of keys ?? []) {
80
+ if (budget-- <= 0)
81
+ break;
82
+ const read = readOwn(o, key);
83
+ const name = typeof key === "string" ? egressText(tier, key) : key;
84
+ Object.defineProperty(snapshot, name, { value: member(read.value, depth, typeof key === "string" ? key : ""), enumerable: read.enumerable, writable: true, configurable: true });
85
+ }
86
+ return snapshot;
87
+ };
88
+ const errorSnapshot = (o, depth) => {
89
+ const snapshot = new Error();
90
+ for (const key of Reflect.ownKeys(snapshot))
91
+ Reflect.deleteProperty(snapshot, key);
92
+ fill(o, depth, ownKeys(o, true), snapshot);
93
+ for (const key of ["name", "message", "code", "stack"]) {
94
+ if (Object.prototype.hasOwnProperty.call(snapshot, key))
95
+ continue;
96
+ const read = readMember(o, key);
97
+ if (read !== undefined && typeof read !== "function")
98
+ Object.defineProperty(snapshot, key, { value: member(read, depth, key), enumerable: false, writable: true, configurable: true });
99
+ }
100
+ return snapshot;
101
+ };
102
+ function pass(v, depth, key) {
103
+ if (typeof v === "string")
104
+ return egressText(tier, v);
105
+ if (typeof v === "function")
106
+ return undefined;
107
+ if (typeof v !== "object" || v === null)
108
+ return v;
109
+ const known = snapshots.get(v);
110
+ if (known !== undefined)
111
+ return known;
112
+ if (depth >= VALUE_DEPTH_MAX)
113
+ return WHOLE;
114
+ if (isErrorLike(v))
115
+ return errorSnapshot(v, depth);
116
+ const toJSON = readMember(v, "toJSON");
117
+ if (typeof toJSON === "function") {
118
+ let reading;
119
+ try {
120
+ reading = toJSON.call(v, key);
121
+ }
122
+ catch {
123
+ return WHOLE;
124
+ }
125
+ if (reading !== v) {
126
+ const out = pass(reading, depth + 1, key);
127
+ snapshots.set(v, out);
128
+ return out;
129
+ }
130
+ }
131
+ const array = (() => {
132
+ try {
133
+ return Array.isArray(v);
134
+ }
135
+ catch {
136
+ return false;
137
+ }
138
+ })();
139
+ return fill(v, depth, ownKeys(v, false), array ? [] : prototypeOf(v) === null ? Object.create(null) : {});
140
+ }
141
+ return pass(value, 0, "");
31
142
  }
32
143
  const REDACTION_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g;
33
144
  function countRedactionMarkers(s) {
@@ -85,10 +196,10 @@ function boundedString(value, max, redact) {
85
196
  return out;
86
197
  }
87
198
  export function boundedRedactedSummary(value, max) {
88
- return boundedString(value, max, redactSecrets);
199
+ return boundedString(value, max, (s) => egressText("same-principal", s));
89
200
  }
90
201
  export function untrustedEgressForHuman(value, opts) {
91
- const source = boundedString(value, Number.POSITIVE_INFINITY, redactHostLeaks);
202
+ const source = boundedString(value, Number.POSITIVE_INFINITY, (s) => egressText("human", s));
92
203
  const probe = (budget) => {
93
204
  const bounded = boundedString(source, budget, (s) => s);
94
205
  return { bounded, overflow: [...neutralizeForFence(bounded)].length - opts.max };
@@ -378,6 +378,22 @@ export const ENGINE_ENVELOPES = Object.freeze([
378
378
  fenced: false,
379
379
  disclosed: false,
380
380
  },
381
+ ...[
382
+ "additional_skills_reminder", "application_details", "artifacts", "ask_user_question_tool", "citation_requirements",
383
+ "claude_behavior", "computer_use", "env", "evenhandedness", "examples", "file_creation_advice", "file_handling_rules",
384
+ "good_file_sharing_examples", "high_level_computer_use_explanation", "knowledge_cutoff", "legal_and_financial_advice",
385
+ "lists_and_bullets", "notes_on_user_uploaded_files", "package_management", "producing_outputs", "product_information",
386
+ "refusal_handling", "responding_to_mistakes_and_criticism", "sharing_files", "strong", "suggesting_claude_actions",
387
+ "todo_list_tool", "tone_and_formatting", "tool_result_safety", "unnecessary_computer_use_avoidance", "user_wellbeing",
388
+ "verification_step", "web_content_restrictions", "working_with_user_files",
389
+ ].map((tag) => ({
390
+ tag,
391
+ kind: "not-an-envelope",
392
+ mint: "prompts/cowork.ts — the Cowork base template's own section markup",
393
+ guard: "no run-supplied bytes reach it: the template is a constant rendered section by section, and its only fills are the two directory placeholders, engine facts",
394
+ fenced: false,
395
+ disclosed: false,
396
+ })),
381
397
  ]);
382
398
  const ENVELOPES_BY_TAG = new Map(ENGINE_ENVELOPES.map((e) => [e.tag, e]));
383
399
  export function engineEnvelope(tag) {
@@ -646,16 +646,30 @@ export declare function deriveAskEffective(form: AskSeamForm, parkEffective: boo
646
646
  */
647
647
  export declare function askSeatEffective(manifest: Pick<WiringManifest, "ask" | "parkLane">): AskEffective;
648
648
  /**
649
- * Does an ask this leg hands on carrying `requiresRealApproval` reach a PERSON — never an automatic yes? The leg's
650
- * APPROVAL SEAT, read off the ONE ask derivation above, never re-derived: `human_reachable` (a live approver) and
651
- * `park_only` (the durable park; where the park cannot take the ask, a blanket seat behind it refuses the bit) are a
652
- * person; `auto_allow`, `auto_deny` and `unresolved` are nobody (the last the fail-closed reading). The auto-mode
653
- * classifier stations read it to decide where a classification that could not rule goes: to that person (the ask
654
- * carrying the bit), or refused.
649
+ * WHERE an ask carrying `requiresRealApproval` goes from a station — the closed set of the ONE approval-seat decision:
650
+ * `live_approver` (a live approver callback answers it now), `durable_park` (it parks for a person to answer later),
651
+ * `none` (nobody can: a blanket policy seat, no seat, or a run that declared nobody can be asked — each only when no park
652
+ * lane is armed, since an armed park is the deployment's own statement that an answer can wait for a person — or a word that cannot be
653
+ * resolved — the fail-closed reading).
655
654
  */
656
- export declare function askReachesPerson(ask: {
655
+ export declare const APPROVAL_ROUTES: readonly ["live_approver", "durable_park", "none"];
656
+ export type ApprovalRoute = (typeof APPROVAL_ROUTES)[number];
657
+ /**
658
+ * The APPROVAL SEAT as a route, read off the ONE ask derivation above, never re-derived: `human_reachable` ⇒
659
+ * `live_approver`; `park_only` ⇒ `durable_park` (where the park cannot take the ask, a blanket seat behind it refuses the
660
+ * bit); `auto_allow`, `auto_deny` and `unresolved` ⇒ `none`. Every station that forks on "does a handed-on ask reach a
661
+ * person" reads this word — the leg's gate from the leg's seat word ({@link askSeatEffective}), an ancestor's frozen seat
662
+ * through {@link frozenApproverRoute} — and the fork, its observer and the sentence a person reads all read the same word.
663
+ */
664
+ export declare function approvalRouteOf(ask: {
657
665
  readonly effective?: AskEffective;
658
- }): boolean;
666
+ }): ApprovalRoute;
667
+ /**
668
+ * The route from an ancestor's FROZEN approver (the inherited fold's station): the same table read with that seat's own
669
+ * form and no park — the inherited station resolves at the frozen seat, whose only road to a person is a live callback
670
+ * (the leg's park is reached only through a live approver's `unavailable`), so a blanket or absent frozen seat is `none`.
671
+ */
672
+ export declare function frozenApproverRoute(onAsk: OnAsk | undefined): ApprovalRoute;
659
673
  export declare function deriveWiringManifest(facts: WiringFacts): WiringManifest;
660
674
  /** A run's approval seat as its one resolution froze it: the value every reader that needs the approver itself reads
661
675
  * (`undefined` when nothing on this run can be asked), the form the manifest reports and every reader of the word derives
@@ -81,16 +81,18 @@ export function deriveAskEffective(form, parkEffective, mode) {
81
81
  export function askSeatEffective(manifest) {
82
82
  return deriveAskEffective(manifest.ask.form, manifest.parkLane.effective);
83
83
  }
84
- export function askReachesPerson(ask) {
84
+ export const APPROVAL_ROUTES = Object.freeze(["live_approver", "durable_park", "none"]);
85
+ export function approvalRouteOf(ask) {
85
86
  const effective = ask.effective ?? "unresolved";
86
87
  switch (effective) {
87
88
  case "human_reachable":
89
+ return "live_approver";
88
90
  case "park_only":
89
- return true;
91
+ return "durable_park";
90
92
  case "auto_allow":
91
93
  case "auto_deny":
92
94
  case "unresolved":
93
- return false;
95
+ return "none";
94
96
  default: {
95
97
  const _exhaustive = effective;
96
98
  void _exhaustive;
@@ -98,6 +100,12 @@ export function askReachesPerson(ask) {
98
100
  }
99
101
  }
100
102
  }
103
+ export function frozenApproverRoute(onAsk) {
104
+ return approvalRouteOf({ effective: deriveAskEffective(askSeamFormOf(onAsk) ?? "absent", false) });
105
+ }
106
+ function askSeamFormOf(v) {
107
+ return typeof v === "function" ? "callback" : v === "allow" ? "allow" : v === "deny" ? "deny" : v === undefined ? "absent" : undefined;
108
+ }
101
109
  function deriveParkLane(facts) {
102
110
  if (!facts.parkCapable) {
103
111
  return { capable: false, effective: false, reasons: ["no_checkpoint_store"] };
@@ -235,7 +243,7 @@ export function resolveAskSeat(spec, deps) {
235
243
  if (seat === undefined)
236
244
  return Object.freeze(reach === undefined ? { value: undefined, form: "absent" } : { value: undefined, form: "declared_none", reach });
237
245
  const v = seat.value;
238
- const form = typeof v === "function" ? "callback" : v === "allow" ? "allow" : v === "deny" ? "deny" : undefined;
246
+ const form = askSeamFormOf(v);
239
247
  if (form === undefined) {
240
248
  const e = new Error(`the resolved onAsk seat (${seat.provenance}) holds ${JSON.stringify(v)} — not an OnAsk ` +
241
249
  `("allow" | "deny" | approver function); refusing to classify it.`);
@@ -1,5 +1,5 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
- import { scrubSecretEnv } from "../../core/secret-env.js";
2
+ import { inheritedHostEnv } from "../../core/secret-env.js";
3
3
  import { existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  const DEFAULT_GRACE_MS = 3000;
@@ -145,7 +145,7 @@ function enumerateDescendantsSync(rootPid) {
145
145
  let out;
146
146
  try {
147
147
  const result = spawnSync("ps", ["-A", "-o", "pid=", "-o", "ppid=", "-o", "etime="], {
148
- env: scrubSecretEnv(process.env),
148
+ env: inheritedHostEnv("scrub"),
149
149
  timeout: PS_ENUMERATION_TIMEOUT_MS,
150
150
  encoding: "utf8",
151
151
  windowsHide: true,
@@ -211,7 +211,7 @@ function parseEtimeSeconds(etime) {
211
211
  function runTaskkill(args) {
212
212
  try {
213
213
  const child = spawn("taskkill", args, {
214
- env: scrubSecretEnv(process.env),
214
+ env: inheritedHostEnv("scrub"),
215
215
  stdio: "ignore",
216
216
  detached: true,
217
217
  windowsHide: true,
@@ -271,7 +271,7 @@ function tryMsysGroupKill(bashPath, pid) {
271
271
  `[ -n "$g" ] && [ "$g" != "0" ] && kill -9 -"$g" 2>/dev/null; ` +
272
272
  `done; exit 0`;
273
273
  try {
274
- const child = spawn(bashPath, ["-c", script], { env: scrubSecretEnv(process.env), stdio: "ignore", detached: true, windowsHide: true });
274
+ const child = spawn(bashPath, ["-c", script], { env: inheritedHostEnv("scrub"), stdio: "ignore", detached: true, windowsHide: true });
275
275
  child.on("error", () => { });
276
276
  return true;
277
277
  }