@sema-agent/core 7.16.0 → 7.17.0

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 (193) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/agents/builtin-agents.d.ts +3 -3
  3. package/dist/agents/cross-session-envelope.d.ts +0 -1
  4. package/dist/agents/cross-session-envelope.js +0 -1
  5. package/dist/agents/peer-notices.d.ts +0 -9
  6. package/dist/agents/peer-notices.js +0 -63
  7. package/dist/agents/subagent.js +7 -15
  8. package/dist/brain/request-params.d.ts +2 -2
  9. package/dist/config/defaults.d.ts +0 -7
  10. package/dist/config/defaults.js +0 -2
  11. package/dist/core/artifact-host.d.ts +218 -0
  12. package/dist/core/artifact-host.js +92 -0
  13. package/dist/core/ask-origin.d.ts +29 -3
  14. package/dist/core/ask-origin.js +17 -9
  15. package/dist/core/ask-question.d.ts +25 -0
  16. package/dist/core/ask-question.js +4 -0
  17. package/dist/core/ask-unresolvable-notice.d.ts +30 -21
  18. package/dist/core/ask-unresolvable-notice.js +16 -8
  19. package/dist/core/auto-compaction.d.ts +10 -10
  20. package/dist/core/auto-mode-arming.d.ts +2 -2
  21. package/dist/core/background-agent-store.d.ts +5 -5
  22. package/dist/core/background-shell.d.ts +1 -1
  23. package/dist/core/checkpoint-execution-record.d.ts +0 -3
  24. package/dist/core/checkpoint-execution-record.js +0 -4
  25. package/dist/core/checkpoint-store.d.ts +71 -99
  26. package/dist/core/checkpoint-store.js +9 -16
  27. package/dist/core/context-edit.d.ts +36 -104
  28. package/dist/core/context-edit.js +37 -60
  29. package/dist/core/effective-path-target.d.ts +68 -8
  30. package/dist/core/effective-path-target.js +42 -30
  31. package/dist/core/engine-notice.d.ts +46 -32
  32. package/dist/core/env-budget.d.ts +31 -0
  33. package/dist/core/env-budget.js +39 -0
  34. package/dist/core/exec-output-tail.d.ts +5 -5
  35. package/dist/core/file-history-store.js +3 -6
  36. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  37. package/dist/core/fs-write-gate-policy.js +3 -3
  38. package/dist/core/gate-lanes.js +9 -15
  39. package/dist/core/governance-codes.d.ts +1 -1
  40. package/dist/core/governance-codes.js +2 -0
  41. package/dist/core/hands-band.d.ts +1 -1
  42. package/dist/core/hooks.d.ts +18 -17
  43. package/dist/core/lsp-session.d.ts +1 -1
  44. package/dist/core/lsp.d.ts +1 -1
  45. package/dist/core/lsp.js +5 -4
  46. package/dist/core/mcp.d.ts +5 -5
  47. package/dist/core/mcp.js +4 -48
  48. package/dist/core/memory-recall.d.ts +0 -8
  49. package/dist/core/memory.d.ts +1 -1
  50. package/dist/core/oracle-isolation.js +2 -2
  51. package/dist/core/parent-spec-seat.d.ts +66 -0
  52. package/dist/core/parent-spec-seat.js +47 -0
  53. package/dist/core/permission-rule-model.d.ts +11 -6
  54. package/dist/core/permission-rule-model.js +53 -37
  55. package/dist/core/permission-rules.js +8 -3
  56. package/dist/core/protocol-naming.d.ts +2 -2
  57. package/dist/core/remote-env.d.ts +1 -1
  58. package/dist/core/retired-keys.d.ts +19 -0
  59. package/dist/core/retired-keys.js +15 -0
  60. package/dist/core/runner/active-skill-scope.js +3 -3
  61. package/dist/core/runner/assemble-result.d.ts +15 -13
  62. package/dist/core/runner/assemble-result.js +5 -4
  63. package/dist/core/runner/compaction-call-options.d.ts +25 -89
  64. package/dist/core/runner/contracts.d.ts +10 -9
  65. package/dist/core/runner/gate-exit.js +15 -1
  66. package/dist/core/runner/halt-attribution.d.ts +35 -0
  67. package/dist/core/runner/halt-attribution.js +16 -0
  68. package/dist/core/runner/memory-consolidation.d.ts +0 -2
  69. package/dist/core/runner/memory-consolidation.js +0 -1
  70. package/dist/core/runner/prepare-artifact.d.ts +53 -0
  71. package/dist/core/runner/prepare-artifact.js +77 -0
  72. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  73. package/dist/core/runner/prepare-caps-and-workflow.js +2 -3
  74. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  75. package/dist/core/runner/prepare-config-doors.js +5 -2
  76. package/dist/core/runner/prepare-context-lane.d.ts +2 -2
  77. package/dist/core/runner/prepare-context-lane.js +2 -13
  78. package/dist/core/runner/prepare-file-history.d.ts +1 -1
  79. package/dist/core/runner/prepare-file-history.js +3 -52
  80. package/dist/core/runner/prepare-hands-readface.d.ts +4 -7
  81. package/dist/core/runner/prepare-hands-readface.js +3 -2
  82. package/dist/core/runner/prepare-inherited-gate.d.ts +5 -2
  83. package/dist/core/runner/prepare-inherited-gate.js +7 -8
  84. package/dist/core/runner/prepare-memory.d.ts +4 -6
  85. package/dist/core/runner/prepare-memory.js +4 -2
  86. package/dist/core/runner/prepare-policy-chain.d.ts +3 -3
  87. package/dist/core/runner/prepare-policy-chain.js +15 -30
  88. package/dist/core/runner/prepare-prompt-assembly.js +1 -1
  89. package/dist/core/runner/prepare-safety-scan.d.ts +1 -1
  90. package/dist/core/runner/prepare-suspend-saga.js +0 -2
  91. package/dist/core/runner/prepare-task.js +10 -9
  92. package/dist/core/runner/prepare-wiring-manifest.js +9 -7
  93. package/dist/core/runner/resume-admission.js +2 -1
  94. package/dist/core/runner/resume-internals-and-config.js +4 -0
  95. package/dist/core/runner/retired-runner-deps.d.ts +3 -0
  96. package/dist/core/runner/retired-runner-deps.js +7 -0
  97. package/dist/core/runner/run-harness-handlers.js +2 -3
  98. package/dist/core/runner/run-settle-and-teardown.d.ts +1 -1
  99. package/dist/core/runner/run-settle-and-teardown.js +3 -1
  100. package/dist/core/runner/session-file-state-replay.js +3 -3
  101. package/dist/core/runner/session-rule-policy.js +3 -4
  102. package/dist/core/runner/stream-halt-verbs.js +1 -1
  103. package/dist/core/runner/stream-steer-verb.js +1 -4
  104. package/dist/core/runner/workspace-path.js +3 -0
  105. package/dist/core/runner-deps.d.ts +30 -49
  106. package/dist/core/safe-notify.d.ts +1 -1
  107. package/dist/core/sensitive-path-policy.d.ts +1 -1
  108. package/dist/core/sensitive-path-policy.js +4 -4
  109. package/dist/core/session.d.ts +2 -2
  110. package/dist/core/skill-tool-specifier.d.ts +1 -1
  111. package/dist/core/skills-directory.d.ts +1 -1
  112. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  113. package/dist/core/strategy-store.d.ts +8 -11
  114. package/dist/core/task-event.d.ts +20 -17
  115. package/dist/core/task-limits.d.ts +1 -1
  116. package/dist/core/task-notification.d.ts +5 -5
  117. package/dist/core/task-registry-agent.d.ts +5 -5
  118. package/dist/core/task-registry-monitor.d.ts +2 -2
  119. package/dist/core/task-registry-shared.d.ts +27 -27
  120. package/dist/core/task-registry-shared.js +3 -13
  121. package/dist/core/task-registry.d.ts +8 -8
  122. package/dist/core/task-result.d.ts +37 -37
  123. package/dist/core/task-spec.d.ts +5 -5
  124. package/dist/core/task-stream.d.ts +16 -11
  125. package/dist/core/tool-catalog-entries.d.ts +1 -0
  126. package/dist/core/tool-catalog-entries.js +5 -0
  127. package/dist/core/tool-conformance.d.ts +0 -2
  128. package/dist/core/tool-conformance.js +1 -3
  129. package/dist/core/tool-errors.d.ts +5 -0
  130. package/dist/core/tool-errors.js +1 -0
  131. package/dist/core/tool-policy.d.ts +51 -22
  132. package/dist/core/tool-policy.js +28 -5
  133. package/dist/core/tool-result-budget.d.ts +1 -1
  134. package/dist/core/tool-result-store.d.ts +11 -11
  135. package/dist/core/tool-spec.d.ts +34 -38
  136. package/dist/core/tools.d.ts +2 -2
  137. package/dist/core/trace.d.ts +12 -15
  138. package/dist/core/untrusted-text.d.ts +1 -1
  139. package/dist/core/wiring-manifest.d.ts +4 -3
  140. package/dist/core/workflow-journal-store.d.ts +11 -11
  141. package/dist/engine/execution-env/node-execution-env.d.ts +3 -2
  142. package/dist/engine/execution-env/node-execution-env.js +4 -2
  143. package/dist/engine/harness/agent-harness.d.ts +47 -4
  144. package/dist/engine/harness/agent-harness.js +52 -9
  145. package/dist/engine/loop/agent-loop.js +15 -8
  146. package/dist/engine/loop/types.d.ts +5 -0
  147. package/dist/index.d.ts +19 -14
  148. package/dist/index.js +15 -10
  149. package/dist/internal/harness.d.ts +1 -0
  150. package/dist/orchestration/run-spec.d.ts +3 -0
  151. package/dist/orchestration/run-spec.js +3 -3
  152. package/dist/orchestration/run-workflow-tool.d.ts +12 -6
  153. package/dist/orchestration/run-workflow-tool.js +23 -5
  154. package/dist/orchestration/workflow-primitives.d.ts +4 -2
  155. package/dist/orchestration/workflow-primitives.js +1 -4
  156. package/dist/orchestration/workflow-types.d.ts +34 -4
  157. package/dist/orchestration/workflow-types.js +2 -0
  158. package/dist/orchestration/workflow.d.ts +39 -7
  159. package/dist/orchestration/workflow.js +234 -113
  160. package/dist/prompt-assembly/assemble.d.ts +2 -2
  161. package/dist/prompt-assembly/assemble.js +9 -9
  162. package/dist/prompt-assembly/composer.d.ts +6 -7
  163. package/dist/prompt-assembly/composer.js +10 -10
  164. package/dist/prompt-assembly/epoch.js +1 -1
  165. package/dist/prompt-assembly/event-registry.d.ts +0 -21
  166. package/dist/prompt-assembly/packs/sema-default.js +30 -30
  167. package/dist/prompt-assembly/types.d.ts +14 -14
  168. package/dist/prompts/coordinator.d.ts +0 -13
  169. package/dist/prompts/coordinator.js +0 -3
  170. package/dist/stores/file/checkpoint-store.js +1 -5
  171. package/dist/stores/file/file-history-store.js +2 -2
  172. package/dist/stores/file/permission-rule-store.d.ts +4 -35
  173. package/dist/stores/file/permission-rule-store.js +4 -148
  174. package/dist/tools/artifact/artifact-text.d.ts +62 -0
  175. package/dist/tools/artifact/artifact-text.js +68 -0
  176. package/dist/tools/artifact/artifact-tool.d.ts +52 -0
  177. package/dist/tools/artifact/artifact-tool.js +351 -0
  178. package/dist/tools/artifact/local-stub.d.ts +26 -0
  179. package/dist/tools/artifact/local-stub.js +296 -0
  180. package/dist/tools/fs/bash-readonly-classifier.d.ts +74 -8
  181. package/dist/tools/fs/bash-readonly-classifier.js +171 -60
  182. package/dist/tools/fs/fs-bash.d.ts +14 -10
  183. package/dist/tools/fs/fs-bash.js +19 -27
  184. package/dist/tools/fs/fs-search-tools.js +5 -2
  185. package/dist/tools/fs/fs-shared.d.ts +2 -0
  186. package/dist/tools/fs/fs-shared.js +3 -11
  187. package/dist/tools/fs/index.d.ts +4 -0
  188. package/dist/tools/fs/index.js +4 -1
  189. package/dist/tools/fs/safety.d.ts +117 -1
  190. package/dist/tools/fs/safety.js +131 -31
  191. package/dist/tools/fs/search.js +8 -6
  192. package/package.json +2 -1
  193. package/test/export-surface.snapshot.json +115 -39
@@ -37,10 +37,11 @@ import type { ReadFace } from "../../tools/fs/index.js";
37
37
  import type { InheritedGate, Prepared, RunInternals, RunnerSelfSeat, ToolFaceSnapshot } from "./contracts.js";
38
38
  import { type BlockedRef } from "./synthetic-tools.js";
39
39
  import { RosterBuilder } from "../tool-roster.js";
40
+ import type { ParentSpecSeat } from "../parent-spec-seat.js";
40
41
  export interface PrepareCapsAndWorkflowInput {
41
42
  /** borrowed-readonly — the REBOUND spec, whole: the roster map reads `tools`; the mounts read `enableBlockedReport`,
42
43
  * `enablePlanMode`, `interactiveTools`; the caps/compliance stations read `principal`, `selfOrchestration`,
43
- * `enableFork`, `getApiKeyAndHeaders`; the Workflow mount forwards `oneShot`, `handsReadOnly`, `interactiveTools`. The
44
+ * `enableFork`, `getApiKeyAndHeaders`; the Workflow mount forwards `handsReadOnly`, `interactiveTools`. The
44
45
  * self-orchestration predicate takes the whole spec. Never mutated. */
45
46
  spec: TaskSpec;
46
47
  /** borrowed-readonly — the deployment seats, whole: `runtimeCapsResolver`, `compliancePostureResolver`, `onError`, `onNotice`,
@@ -54,7 +55,7 @@ export interface PrepareCapsAndWorkflowInput {
54
55
  checkpointSeat: CheckpointSeat | undefined;
55
56
  /** borrowed-readonly — the trusted spawn-side channel; the Workflow mount forwards `rootSessionId`, `placementRoot`,
56
57
  * `onTaskNotification`, `workflowDepth`, and the capture-floor getter reads `memoryCaptureAncestors`. */
57
- internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume"> | undefined;
58
+ internals: Pick<RunInternals, "rootSessionId" | "placementRoot" | "onTaskNotification" | "workflowDepth" | "memoryCaptureAncestors" | "workflowParkedResume" | "questionFaceStripped"> | undefined;
58
59
  /** borrowed-readonly — the acquired session; the classifier leg rebuilds its transcript window from `buildContext`. */
59
60
  session: Pick<StoredSession, "buildContext">;
60
61
  /** borrowed-readonly — the acquired session id (operator-line context, the refusal codes' phase record). */
@@ -88,6 +89,7 @@ export interface PrepareCapsAndWorkflowInput {
88
89
  };
89
90
  /** borrowed-readonly — the FROZEN approver seat (the Workflow mount's parentOnAsk). */
90
91
  frozenOnAsk: OnAsk | undefined;
92
+ /** borrowed-readonly — the parent SPEC seat the children inherit (minted once by the inherited-gate phase; the Workflow mount's dep, the same value the delegation ctx carries). */ parentSpecSeat: ParentSpecSeat;
91
93
  /** borrowed-readonly — the ONE read-face carrier expression (the Workflow mount's parentReadFace getter calls it). */
92
94
  carrierReadFace: () => ReadFace | undefined;
93
95
  /** borrowed-readonly — the filtered display-forwarding wrapper (the Workflow mount's forwardEvent; absent ⇒ no key). */
@@ -112,7 +112,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
112
112
  return { armed: true, reason: "armed" };
113
113
  }
114
114
  export async function prepareCapsAndWorkflow(input) {
115
- const { spec, deps, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
115
+ const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
116
116
  const roster = new RosterBuilder(toolEffects);
117
117
  for (const t of spec.tools ?? []) {
118
118
  const mounted = mountCallerTool(t);
@@ -312,8 +312,7 @@ export async function prepareCapsAndWorkflow(input) {
312
312
  return mine.length > 0 ? mine : undefined;
313
313
  } }
314
314
  : {}),
315
- principal: spec.principal,
316
- oneShot: spec.oneShot,
315
+ parentSpecSeat, ...(internals?.questionFaceStripped === true ? { parentQuestionFaceStripped: true } : {}),
317
316
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
318
317
  parentMemoryCaptureState: () => {
319
318
  const memoryEngineSession = input.memoryEngineSession();
@@ -22,7 +22,7 @@
22
22
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
23
23
  import type { Model } from "../../internal/llm.js";
24
24
  import type { ThinkingLevel } from "../../internal/harness.js";
25
- import type { ContextEditMachine } from "../context-edit.js";
25
+ import { type MicroCompactMachine } from "../context-edit.js";
26
26
  import { type LockedPreflight } from "../locked-config.js";
27
27
  import { type ResolvedRole } from "../roles.js";
28
28
  import type { SessionStore } from "../session.js";
@@ -150,7 +150,7 @@ export interface PrepareConfigDoorsResult {
150
150
  * for the door + placement reasoning). The per-run state builder consumes exactly these values —
151
151
  * the deps bag is never re-read after this door. */
152
152
  microCompactKnob: {
153
- machine: "off" | ContextEditMachine;
153
+ machine: MicroCompactMachine;
154
154
  clearOnRejection: boolean;
155
155
  };
156
156
  /** owned — the resolved role; its `systemPrompt` seat is still read at prompt-input time. */
@@ -213,7 +213,7 @@ export interface PrepareConfigDoorsResult {
213
213
  * it must refuse exactly like a malformed field, never fold to the defaults. The RESOLVED values
214
214
  * returned here are immutable and are the state builder's ONLY source — nothing re-reads the bag. */
215
215
  export declare function resolveMicroCompactKnob(bag: RunnerDeps["microCompact"]): {
216
- machine: "off" | ContextEditMachine;
216
+ machine: MicroCompactMachine;
217
217
  clearOnRejection: boolean;
218
218
  };
219
219
  export declare function prepareConfigDoors(input: PrepareConfigDoorsInput): PrepareConfigDoorsResult;
@@ -1,5 +1,7 @@
1
1
  import { resolveBrainCallGuardrailMs } from "../../brain/timeout.js";
2
2
  import { assertReadFaceValue } from "../../tools/fs/index.js";
3
+ import { MICRO_COMPACT_MACHINES } from "../context-edit.js";
4
+ import { assertNoRetiredRunnerDeps } from "./retired-runner-deps.js";
3
5
  import { resolveCheckpointStore } from "../checkpoint-store.js";
4
6
  import { isModelPriced, malformedPricingField, modelCostToPricing } from "../pricing.js";
5
7
  import { preflightLockedConfig } from "../locked-config.js";
@@ -133,8 +135,8 @@ export function resolveMicroCompactKnob(bag) {
133
135
  throw microCompactConfigError(undefined, bag, "an object carrying optional machine/clearOnRejection keys");
134
136
  }
135
137
  const declaredMachine = bag?.machine;
136
- if (declaredMachine !== undefined && declaredMachine !== "off" && declaredMachine !== "legacy" && declaredMachine !== "cc") {
137
- throw microCompactConfigError("machine", declaredMachine, `"off" | "legacy" | "cc"`);
138
+ if (declaredMachine !== undefined && !MICRO_COMPACT_MACHINES.includes(declaredMachine)) {
139
+ throw microCompactConfigError("machine", declaredMachine, MICRO_COMPACT_MACHINES.map((m) => `"${m}"`).join(" | "));
138
140
  }
139
141
  return { machine: declaredMachine ?? "cc", clearOnRejection: resolveClearOnRejection(bag) };
140
142
  }
@@ -148,6 +150,7 @@ function resolveClearOnRejection(bag) {
148
150
  export function prepareConfigDoors(input) {
149
151
  const { deps, sessions, resume, internals } = input;
150
152
  let spec = input.spec;
153
+ assertNoRetiredRunnerDeps(deps);
151
154
  assertRestoreGatedToolsValue(spec.restoreGatedTools);
152
155
  const microCompactKnob = resolveMicroCompactKnob(deps.microCompact);
153
156
  const assertToolNameListValue = (value, seat) => {
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { type AgentHarness, type AgentMessage } from "../../internal/harness.js";
19
19
  import type { Model } from "../../internal/llm.js";
20
- import { type ContextEditMachine, type OccurrenceIndex } from "../context-edit.js";
20
+ import { type MicroCompactMachine, type OccurrenceIndex } from "../context-edit.js";
21
21
  import { type ToolResultStore } from "../tool-result-store.js";
22
22
  import { type RunnerDeps } from "../types.js";
23
23
  import type { Prepared, PreparedMicroCompact } from "./contracts.js";
@@ -39,7 +39,7 @@ export interface PrepareContextLaneInput {
39
39
  offloadStore: ToolResultStore | undefined;
40
40
  /** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */
41
41
  microCompactKnob: {
42
- machine: "off" | ContextEditMachine;
42
+ machine: MicroCompactMachine;
43
43
  clearOnRejection: boolean;
44
44
  };
45
45
  /** borrowed-mutable — the built harness. Writer here: `on("context", …)` — the ONE registration of the request-build
@@ -44,14 +44,8 @@ export function prepareContextLane(input) {
44
44
  let ccPass;
45
45
  const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
46
46
  budgetTokens: editAt,
47
- machine: microCompact.machine,
48
- ...(microCompact.machine === "cc" || microCompact.clearOnRejection ? { recognizeCcMarkers: true } : {}),
49
- ...(microCompact.machine === "cc"
50
- ? {
51
- clearSource: replayed,
52
- onCleared: (pass) => { ccPass = pass; },
53
- }
54
- : {}),
47
+ clearSource: replayed,
48
+ onCleared: (pass) => { ccPass = pass; },
55
49
  anchoredTotalTokens: estimateContextTokens(mediaCapped, charsPerToken).tokens,
56
50
  charsPerToken,
57
51
  ...(offloadStore
@@ -193,15 +187,10 @@ export async function runGuardChain(args) {
193
187
  };
194
188
  let working = edited;
195
189
  let anchoredWorking = estimateContextTokens(working, charsPerToken).tokens;
196
- if (microCompact.machine === "legacy") {
197
- const t = applyGuardTrim(working, anchoredWorking);
198
- return { working, trimmed: t.trimmed, trimDroppedMessages: t.dropped };
199
- }
200
190
  if (microCompact.machine === "off" && anchoredWorking > guardAt) {
201
191
  let blockingPass;
202
192
  const blockingEdited = clearStaleToolResults(working, {
203
193
  budgetTokens: guardAt,
204
- machine: "cc",
205
194
  clearSource: args.replayed,
206
195
  onCleared: (pass) => { blockingPass = pass; },
207
196
  anchoredTotalTokens: anchoredWorking,
@@ -25,7 +25,7 @@ import type { FileHistoryBoundarySeat, RunInternals } from "./contracts.js";
25
25
  export interface PrepareFileHistoryInput {
26
26
  /** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy and the
27
27
  * history store (both validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
28
- deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "onError" | "legacyRewindBoundaryProbe">;
28
+ deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "onError">;
29
29
  /** borrowed-readonly — the REBOUND spec's rewind request fields (the retired `rewindFiles` spelling is
30
30
  * read off the same object as an unknown key) and the caller's cancel signal for the restore. */
31
31
  spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "acceptPartialRestore" | "signal">;
@@ -63,7 +63,6 @@ export async function prepareFileHistory(input) {
63
63
  });
64
64
  }
65
65
  }
66
- const legacyEpochTail = (entryId, form = "at-or-above") => legacyRewindEpochTail(deps, sessionId, entryId, form);
67
66
  let restoredFilePaths = [];
68
67
  if (rewindTarget !== undefined && fileHistoryStore !== undefined && handsEnabled) {
69
68
  const historyStore = fileHistoryStore;
@@ -85,7 +84,7 @@ export async function prepareFileHistory(input) {
85
84
  }
86
85
  }
87
86
  if (anchor === undefined) {
88
- const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${await legacyEpochTail(rewindTarget)}`);
87
+ const e = new Error(`rewind-files: no file-history boundary exists at or above the "before" branch point on session "${sessionId}" — cannot restore files consistent with the rewound context. ${rewindRefusalTail("at-or-above")}`);
89
88
  e.code = "rewind_snapshot.unresolvable";
90
89
  throw e;
91
90
  }
@@ -99,7 +98,7 @@ export async function prepareFileHistory(input) {
99
98
  if (restored.error?.code === "not_found") {
100
99
  const e = new Error(rewindBefore
101
100
  ? `rewind-files: the resolved "before" history anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
102
- : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${await legacyEpochTail(rewindTarget, "keyed")}`);
101
+ : `rewind-files: no file-history boundary exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound. ${rewindRefusalTail("keyed")}`);
103
102
  e.code = "rewind_snapshot.unresolvable";
104
103
  throw e;
105
104
  }
@@ -192,55 +191,7 @@ function resolveWiredFileHistoryStore(candidate) {
192
191
  }
193
192
  return candidate;
194
193
  }
195
- const LEGACY_PROBE_TIMEOUT_MS = 2_000;
196
- const LEGACY_PROBE_TIMED_OUT = Symbol("legacy-rewind-probe-timeout");
197
- async function legacyRewindEpochTail(deps, sessionId, entryId, form) {
198
- const probe = deps.legacyRewindBoundaryProbe;
199
- let verdict;
200
- if (probe !== undefined) {
201
- try {
202
- let timer;
203
- const timeout = new Promise((resolve) => {
204
- timer = setTimeout(() => resolve(LEGACY_PROBE_TIMED_OUT), LEGACY_PROBE_TIMEOUT_MS);
205
- });
206
- try {
207
- verdict = await Promise.race([Promise.resolve(probe({ sessionId, entryId })), timeout]);
208
- }
209
- finally {
210
- clearTimeout(timer);
211
- }
212
- if (verdict === LEGACY_PROBE_TIMED_OUT) {
213
- verdict = undefined;
214
- try {
215
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe did not answer within ${LEGACY_PROBE_TIMEOUT_MS}ms while explaining a rewind refusal — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
216
- }
217
- catch {
218
- }
219
- }
220
- }
221
- catch (err) {
222
- verdict = undefined;
223
- try {
224
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe threw while explaining a rewind refusal (${err.message}) — the refusal stands and names both possibilities`), { phase: "rewind", sessionId });
225
- }
226
- catch {
227
- }
228
- }
229
- if (verdict !== undefined && typeof verdict !== "boolean") {
230
- try {
231
- deps.onError?.(new Error(`RunnerDeps.legacyRewindBoundaryProbe answered ${JSON.stringify(String(verdict))} for entry "${entryId}" — only true/false/undefined are meaningful, so the refusal names both possibilities instead of trusting it`), { phase: "rewind", sessionId });
232
- }
233
- catch {
234
- }
235
- verdict = undefined;
236
- }
237
- }
238
- if (verdict === true) {
239
- return `This deployment's legacy-epoch probe reports that entry "${entryId}" DOES carry a boundary from the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore — the two epochs are not interchangeable (a whole-tree manifest carries no tracked-set provenance, so replaying it here would converge files this session never edited). Restore it with the whole-tree-era tooling this deployment kept, or re-run the turns under the current engine.`;
240
- }
241
- if (verdict === false) {
242
- return `This deployment's legacy-epoch probe reports no whole-tree-era boundary for entry "${entryId}" either, so the boundary simply does not exist: its turns ran without a RunnerDeps.fileHistoryStore wired, or it was reaped.`;
243
- }
194
+ function rewindRefusalTail(form) {
244
195
  return form === "keyed"
245
196
  ? `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that ran with a RunnerDeps.fileHistoryStore wired; a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`
246
197
  : `Either the boundary does not exist (its turns ran without a RunnerDeps.fileHistoryStore, or it was reaped), or it was produced in the retired whole-tree snapshot era, which this engine's per-edited-file seat does not restore.`;
@@ -162,13 +162,10 @@ export interface PrepareHandsReadFaceInput {
162
162
  * when no file restore ran. Fail-closed by construction — dropping a read record only ever
163
163
  * produces refusals. */
164
164
  restoredFilePaths?: readonly string[];
165
- /** borrowed-readonly — (ref cell; the REF itself is never replaced) memory write gate:
166
- * the band's beforeWrite closure reads `.current` call-time; prepareMemory (driver, BEFORE this phase
167
- * since the memory root must be known before the toolkit compiles) installs the gate — the one
168
- * writer; this phase only reads. Declared in the driver because the SAME ref feeds prepareMemory. */
169
- memoryWriteGateRef: {
170
- current?: BeforeWriteHook;
171
- };
165
+ /** borrowed-readonly — the memory write gate the memory phase minted (it runs BEFORE this phase: the memory root
166
+ * must be known before the toolkit compiles), composed into the band's beforeWrite below; undefined for a
167
+ * memory-less run (every write passes the engine half). */
168
+ memoryWriteGate: BeforeWriteHook | undefined;
172
169
  /** borrowed-mutable — the advertised-writable-directory seat (see its module header for the one
173
170
  * rule). Present on the hands leg only (a fence exists). The memory phase, which runs BEFORE this one,
174
171
  * has already admitted its write root through it; this phase admits the deployment's scratchpad and
@@ -123,7 +123,7 @@ export function resolveHandsLessReadFace(input) {
123
123
  }
124
124
  export async function prepareHandsMount(input) {
125
125
  const envHandToolNames = new Set();
126
- const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGateRef, advertisedWritableDirs, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
126
+ const { handsEnabled, executionEnv, taskRootFinal, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, advertisedWritableDirs, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
127
127
  assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
128
128
  assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
129
129
  assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
@@ -328,7 +328,7 @@ export async function prepareHandsMount(input) {
328
328
  if (verdict !== undefined && verdict.ok === false)
329
329
  return verdict;
330
330
  }
331
- return memoryWriteGateRef.current?.(w);
331
+ return memoryWriteGate?.(w);
332
332
  },
333
333
  mountBackgroundTaskTools: false,
334
334
  monitorToolActive: backgroundTaskToolsActive && !(toolFaceSnapshot.exclude?.includes("Monitor") ?? false),
@@ -354,6 +354,7 @@ export async function prepareHandsMount(input) {
354
354
  const shellReadBoundary = () => ({
355
355
  roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
356
356
  ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
357
+ ...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
357
358
  denyMatch: (p) => readDenyMatcher?.matchPath(p)?.pattern ?? null,
358
359
  ...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
359
360
  });
@@ -5,7 +5,7 @@
5
5
  * freeze with its tighten-only intersection and the monotonic governed bit), the effective shellGate against the
6
6
  * spec, the three cells later phases fill for the children's chain (own session rules, own admitted org scopes,
7
7
  * own org verdict), the checkpoint-section closures the suspend serializer reads (face / org / F-012 stamps), the
8
- * FROZEN ask and question seats (spec over deps), the two delegation refs the memory phase binds late, the ONE
8
+ * FROZEN ask and question seats (spec over deps), the parent SPEC seat the children inherit, the two delegation refs the memory phase binds late, the ONE
9
9
  * child-chain assembly `inheritedGateForChildren`, the caller-tool ctx enrichment `enrichSpecToolCtx` shared by
10
10
  * the roster wrap and the revival spawner, the denial-limit stop seat, and the full-shell reachability predicate.
11
11
  * The interface is the dependency list the segment had implicitly.
@@ -36,6 +36,7 @@ import type { ReadFace } from "../../tools/fs/index.js";
36
36
  import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
37
37
  import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
38
38
  import { type OnQuestion } from "../ask-question.js";
39
+ import { type ParentSpecSeat } from "../parent-spec-seat.js";
39
40
  import { type HookEnvCapabilities, type Hooks } from "../hooks.js";
40
41
  import type { LockedPreflight } from "../locked-config.js";
41
42
  import type { OwnOrgAdmissionVerdict } from "../memory-admission.js";
@@ -65,7 +66,7 @@ export interface PrepareInheritedGateInput {
65
66
  /** borrowed-readonly — the trusted spawn-side channel: `inheritedGate` (the LIVE half of the chain fold), `ownOrgAdmissionRef`
66
67
  * (the same-session continuation's freeze), `delegationProvenance` (the F-012 stamp), and the identity / peer / notification
67
68
  * seats the ctx enrichment forwards. Never mutated. */
68
- internals: Pick<RunInternals, "inheritedGate" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork"> | undefined;
69
+ internals: Pick<RunInternals, "inheritedGate" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork" | "questionFaceStripped"> | undefined;
69
70
  /** borrowed-readonly — the resume leg, or undefined on a fresh one: `seed.inheritedGate` is the CHECKPOINT half of the chain
70
71
  * fold (the trust floor the live half may only tighten), `seed.readFace` the hands-less re-park's carried section. */
71
72
  resume: Pick<PrepareResume, "seed"> | undefined;
@@ -240,6 +241,8 @@ export interface PrepareInheritedGateResult {
240
241
  notifyOwnHookCrash: (err: unknown) => void;
241
242
  /** owned — the FROZEN question seat (spec over deps), the content-ask twin of `frozenOnAsk`. */
242
243
  frozenOnQuestion: OnQuestion | undefined;
244
+ /** owned — the parent SPEC seat this run hands its children (`parentSpecSeatOf(spec, frozenOnQuestion)`): the ctx seat and the Workflow mount dep are this one value; both lanes apply it with `applyParentSpecSeat` (one law). */
245
+ parentSpecSeat: ParentSpecSeat;
243
246
  /** borrowed-mutable — the delegation runtime-provenance arming face. Writer: the memory-engine-session phase
244
247
  * (prepareMemoryEngineSession), once per leg when it arms. Read lazily by delegation tools through the ctx. */
245
248
  provenanceForChildrenRef: {
@@ -1,8 +1,9 @@
1
1
  import { forkGovernanceDenial, resolveDelegationEntryCaps } from "../../agents/subagent.js";
2
2
  import { isLiveQuestionFace } from "../ask-question.js";
3
+ import { parentSpecSeatOf } from "../parent-spec-seat.js";
3
4
  import { createPreToolUseConstraintPolicy } from "../hooks.js";
4
5
  import { StubExecutionEnv } from "../stub-env.js";
5
- import { askApproverIdentity, isLiveApproverSeat } from "../tool-policy.js";
6
+ import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
6
7
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
7
8
  export function prepareInheritedGate(input) {
8
9
  const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, autoModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
@@ -93,6 +94,7 @@ export function prepareInheritedGate(input) {
93
94
  }
94
95
  };
95
96
  const frozenOnQuestion = spec.onQuestion ?? deps.onQuestion;
97
+ const parentSpecSeat = parentSpecSeatOf(spec, frozenOnQuestion);
96
98
  const provenanceForChildrenRef = {};
97
99
  const delegationProvenanceForChildren = () => provenanceForChildrenRef.current;
98
100
  const delegationSettlementRef = {};
@@ -106,8 +108,7 @@ export function prepareInheritedGate(input) {
106
108
  ...(ownSessionRulesRef.current !== undefined ? [ownSessionRulesRef.current] : []),
107
109
  ];
108
110
  const ownCallerPolicy = lockedPreflight.toolPolicy;
109
- const durableMandate = input.runtimeCaps()?.forceDurableGate === true ||
110
- (spec.durableApproval !== undefined && !isLiveApproverSeat(frozenOnAsk));
111
+ const durableMandate = durableMandateSourceOf({ forceDurableGate: input.runtimeCaps()?.forceDurableGate === true, durableApprovalDeclared: spec.durableApproval !== undefined, onAsk: frozenOnAsk }) !== undefined;
111
112
  const contentMandate = input.runtimeCaps()?.forceDurableGate === true ||
112
113
  (spec.durableApproval !== undefined && !isLiveQuestionFace(frozenOnQuestion));
113
114
  const ownPreToolUse = input.preToolUseObservational() ? undefined : input.hooks()?.preToolUse;
@@ -186,15 +187,13 @@ export function prepareInheritedGate(input) {
186
187
  ...(readDenyAdditionsNormalized.length > 0 ? { readDenyPatterns: Object.freeze(readDenyAdditionsNormalized.map((e) => ({ ...e }))) } : {}),
187
188
  ...(spec.interactiveTools === false ? { interactiveTools: false } : {}),
188
189
  oneShot: spec.oneShot,
189
- clientContext: spec.clientContext,
190
+ parentSpecSeat,
191
+ ...(internals?.questionFaceStripped === true ? { questionFaceStripped: true } : {}),
190
192
  excludeTools: toolFaceSnapshot.exclude,
191
193
  deferTools: toolFaceSnapshot.defer,
192
194
  alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
193
195
  ...(toolFaceSnapshot.restoreGated !== undefined ? { restoreGatedTools: toolFaceSnapshot.restoreGated } : {}),
194
196
  promptProfile,
195
- ...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
196
- ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
197
- ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
198
197
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
199
198
  get memoryCaptureOptedOut() {
200
199
  return input.memoryEngineSession()?.captureOptOut?.optedOut() ?? false;
@@ -263,5 +262,5 @@ export function prepareInheritedGate(input) {
263
262
  ...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
264
263
  };
265
264
  };
266
- return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
265
+ return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
267
266
  }
@@ -18,12 +18,6 @@ export interface PrepareMemoryInput {
18
18
  /** borrowed-readonly — the resolved task root: the containment boundary every derived memory dir
19
19
  * is checked against, and the repo-root fallback when `deps.rootPath` is unset. */
20
20
  taskRootPath: string;
21
- /** borrowed-mutable — the caller's write-gate cell, and THIS phase is its writer: it installs the
22
- * memory write gate into `.current` (the module header's one out-param effect). Identity is the
23
- * contract — the caller's hands/read-face seat reads this exact cell. */
24
- memoryWriteGateRef: {
25
- current?: BeforeWriteHook;
26
- };
27
21
  /**
28
22
  * borrowed-readonly — #181-F5: whether a tool NAMED `Write` (the tool the CC `# Memory` instruction names) is on the
29
23
  * ASSEMBLED roster this run, not excluded, AND can actually REACH the host-side memory root:
@@ -199,6 +193,10 @@ export interface MemoryInjectionComposition {
199
193
  export interface PrepareMemoryResult {
200
194
  memoryEngineSession: Prepared["memoryEngineSession"];
201
195
  memoryTools?: ToolSpec[];
196
+ /** owned — the memory WRITE gate (Write/Edit/NotebookEdit run their FINAL text through MemoryEngine.gateWrite when
197
+ * the target lands inside the memory writable root; the read-only plane refuses its own domain). Present iff a memory
198
+ * engine mounted; the hands phase composes it into the band's beforeWrite (a memory-less run: every write passes). */
199
+ memoryWriteGate?: BeforeWriteHook;
202
200
  /**
203
201
  * owned — #691: the injection's COMPOSITION step, run by the driver AFTER the hands phase (the mount above runs
204
202
  * BEFORE it). It performs the one destructive read this phase has — `inject()` drains the announcement queue —
@@ -13,7 +13,8 @@ import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
13
13
  import { writableRootOf } from "../memory-engine/types.js";
14
14
  let undeclaredCapturePostureAnnounced = false;
15
15
  export async function prepareMemory(input) {
16
- const { spec, deps, sessionId, taskRootPath, memoryWriteGateRef, admissionCtx } = input;
16
+ const { spec, deps, sessionId, taskRootPath, admissionCtx } = input;
17
+ let memoryWriteGate;
17
18
  const memorySpec = normalizeMemorySpec(spec.memory);
18
19
  let admittedOrgScopes = [];
19
20
  let ownOrgVerdict;
@@ -473,7 +474,7 @@ export async function prepareMemory(input) {
473
474
  writeHandle.writeScope !== null &&
474
475
  foreignUnderRoot.length === 0 &&
475
476
  (input.advertisedWritableDirs === undefined || (await input.advertisedWritableDirs.admit({ path: writableRootOf(writeHandle), advertiser: "memory", expectCanonical: writableRootOf(writeHandle) })));
476
- memoryWriteGateRef.current = async (w) => {
477
+ memoryWriteGate = async (w) => {
477
478
  if (readOnlyEngine !== undefined && readOnlyHandle !== undefined) {
478
479
  const ro = readOnlyEngine.gateWrite(readOnlyHandle, w.key, w.content);
479
480
  if (!ro.ok)
@@ -776,6 +777,7 @@ export async function prepareMemory(input) {
776
777
  ownOrgVerdict,
777
778
  effectiveMemoryScopes,
778
779
  ...(memoryTools !== undefined ? { memoryTools } : {}),
780
+ ...(memoryWriteGate !== undefined ? { memoryWriteGate } : {}),
779
781
  composeInjection,
780
782
  };
781
783
  }
@@ -75,9 +75,9 @@ export interface PreparePolicyChainInput {
75
75
  irreversible?: false;
76
76
  egress?: false;
77
77
  }>;
78
- /** borrowed-readonly — the caller tools' names, the first-await snapshot (the sandbox boundary predicate: a caller tool
79
- * is never contained by the hand tools' env). */
80
- ownToolNames: ReadonlySet<string>;
78
+ /** borrowed-readonly — the ENGINE HAND roster: every tool whose effect dispatches through the task's execution env,
79
+ * membership minted at the mount sites (the sandbox boundary predicate's one containment fact). */
80
+ envHandToolNames: ReadonlySet<string>;
81
81
  /** borrowed-readonly — the shell-seat provenance set (the offer factory's `shellGated` mandate term, read per call). */
82
82
  shellGatedTools: ReadonlySet<string>;
83
83
  /** borrowed-readonly — the tracked-cwd seat, read per call by the lanes and the offer factory (never written here). */
@@ -1,11 +1,10 @@
1
- import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
2
1
  import { policyAskClassOf } from "../ask-class.js";
3
2
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
3
  import { resolveCheckpointStore } from "../checkpoint-store.js";
5
4
  import { cloneObserverInput, resolveHookTimeoutMs } from "../hooks.js";
6
5
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
7
6
  import { isIsolated } from "../remote-env.js";
8
- import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, isLiveApproverSeat, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
7
+ import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, isLiveApproverSeat, resolveAsk, toolPolicyNameSets, attestAskUnresolvableCause } from "../tool-policy.js";
9
8
  import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
10
9
  import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
11
10
  import { emitTrace } from "../trace.js";
@@ -16,7 +15,7 @@ import { askGrantShapeOf } from "./inherited-ask-grants.js";
16
15
  import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
17
16
  import { createSessionRulePolicy } from "./session-rule-policy.js";
18
17
  export async function preparePolicyChain(input) {
19
- const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, ownToolNames, shellGatedTools, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
18
+ const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, envHandToolNames, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
20
19
  const policy = lockedPreflight.toolPolicy;
21
20
  const auditPolicyNames = (auditee) => {
22
21
  const nameGroups = toolPolicyNameSets(auditee);
@@ -144,6 +143,12 @@ export async function preparePolicyChain(input) {
144
143
  inheritedUnavailableAsks.add(toolCallId);
145
144
  return true;
146
145
  };
146
+ const mandatedCallFloats = (toolCallId) => resolveCheckpointStore(spec, deps) !== undefined && (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) && markInheritedUnavailable(toolCallId);
147
+ const mandateUnreconstructibleDeny = (creq) => attestAskUnresolvableCause({
148
+ action: "deny",
149
+ message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
150
+ }, "mandate_unreconstructible", creq);
151
+ const markerChannelFullDeny = (creq) => attestAskUnresolvableCause({ action: "deny", message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed` }, "approver_unavailable", creq);
147
152
  const inheritedAskGrants = new Map();
148
153
  const recordInheritedAskGrant = (toolCallId, approver, presented, waitMs) => {
149
154
  if (typeof approver !== "function")
@@ -190,7 +195,7 @@ export async function preparePolicyChain(input) {
190
195
  list.push({ cls, layer });
191
196
  foldAskClasses.set(toolCallId, list);
192
197
  };
193
- const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || toolName.includes("__") || ownToolNames.has(toolName) || toolName === SEND_MESSAGE_TOOL_NAME;
198
+ const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || !envHandToolNames.has(toolName);
194
199
  const emitSandboxAdmitted = (info) => {
195
200
  emitTrace(deps.tracer, () => ({
196
201
  kind: "permission.sandbox_admitted",
@@ -358,16 +363,9 @@ export async function preparePolicyChain(input) {
358
363
  };
359
364
  }
360
365
  if (pc.durableMandate === true) {
361
- if (resolveCheckpointStore(spec, deps) !== undefined &&
362
- (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
363
- markInheritedUnavailable(creq.toolCallId)) {
366
+ if (mandatedCallFloats(creq.toolCallId))
364
367
  return first;
365
- }
366
- return {
367
- action: "deny",
368
- message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
369
- `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
370
- };
368
+ return mandateUnreconstructibleDeny(creq);
371
369
  }
372
370
  const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
373
371
  if (judged.kind === "allow")
@@ -398,10 +396,7 @@ export async function preparePolicyChain(input) {
398
396
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
399
397
  if (markInheritedUnavailable(creq.toolCallId))
400
398
  return inheritedAsk;
401
- return {
402
- action: "deny",
403
- message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
404
- };
399
+ return markerChannelFullDeny(creq);
405
400
  }
406
401
  settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
407
402
  if (resolved.action !== "allow")
@@ -437,16 +432,9 @@ export async function preparePolicyChain(input) {
437
432
  };
438
433
  }
439
434
  if (pc.durableMandate === true) {
440
- if (resolveCheckpointStore(spec, deps) !== undefined &&
441
- (spec.durableApproval !== undefined || runtimeCaps?.forceDurableGate === true) &&
442
- markInheritedUnavailable(creq.toolCallId)) {
435
+ if (mandatedCallFloats(creq.toolCallId))
443
436
  return decision;
444
- }
445
- return {
446
- action: "deny",
447
- message: `inherited parent policy requires durable approval for "${creq.toolName}" — the parent's durable ` +
448
- `ask cannot be reconstructed in a delegated child; denied fail-closed (tighten-only)`,
449
- };
437
+ return mandateUnreconstructibleDeny(creq);
450
438
  }
451
439
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
452
440
  const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call" });
@@ -477,10 +465,7 @@ export async function preparePolicyChain(input) {
477
465
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
478
466
  if (markInheritedUnavailable(creq.toolCallId))
479
467
  return inheritedAsk;
480
- return {
481
- action: "deny",
482
- message: `no approver is reachable for the inherited approval of "${creq.toolName}" and the marker channel is at capacity — denied fail-closed`,
483
- };
468
+ return markerChannelFullDeny(creq);
484
469
  }
485
470
  settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
486
471
  if (resolved.action !== "allow")
@@ -95,7 +95,7 @@ export async function preparePromptAssembly(input) {
95
95
  }
96
96
  : undefined;
97
97
  const promptConstitution = assembled.constitution;
98
- const promptBlocks = assembled.legacyBlocks;
98
+ const promptBlocks = assembled.blocks;
99
99
  const collisionAudit = auditToolCollisions(tools);
100
100
  for (const y of collisionAudit.yields) {
101
101
  onceLedger.onError(new Error(`tool mount: caller tool "${y.name}" shadows another tool with the same canonical name (deliberate last-write-wins yield — the later mount serves)`), { phase: "config", sessionId });
@@ -12,7 +12,7 @@ export interface PrepareSafetyScanInput {
12
12
  * The scan's outputs. Ownership annotations (four-class form):
13
13
  *
14
14
  * `ownToolNames` is **borrowed-readonly** — a first-await snapshot with no mutation anywhere after
15
- * the scan; its one later read is the sandbox-boundary predicate's `.has()`.
15
+ * the scan (sandbox containment reads the env-hand roster, not this set).
16
16
  *
17
17
  * The six collections are **borrowed-mutable**: the driver and later phases fold into them IN
18
18
  * PLACE (identity is part of the contract — closures alias these exact objects). Mutation owners
@@ -106,7 +106,6 @@ export function prepareSuspendSaga(input) {
106
106
  activeTools: [...activeTools],
107
107
  outputRef: { value: outputRef.value, set: outputRef.set },
108
108
  nestedStats: { ...nestedStats },
109
- consolidationNotes: undefined,
110
109
  readFileState: readFileStateForCheckpoint ? [...readFileStateForCheckpoint.entries()] : undefined,
111
110
  readFace: faceCheckpointSection(),
112
111
  reminderMark,
@@ -121,7 +120,6 @@ export function prepareSuspendSaga(input) {
121
120
  .map((t) => ({ id: t.task_id, ...(t.description !== undefined ? { description: t.description } : {}) }));
122
121
  return live.length > 0 ? live : undefined;
123
122
  })(),
124
- pendingSteer: undefined,
125
123
  pendingSteerQueue: undefined,
126
124
  inheritedGate: (() => {
127
125
  const requiresParentConstraint = (inheritedParentConstraints?.length ?? 0) > 0 || seedInheritedGate?.requiresParentConstraint === true;