@sema-agent/core 7.33.7 → 7.34.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 (206) hide show
  1. package/CHANGELOG.md +357 -0
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/open-responses.js +1 -0
  11. package/dist/brain/openai.js +1 -0
  12. package/dist/brain/stream-engine.d.ts +3 -0
  13. package/dist/brain/stream-engine.js +2 -1
  14. package/dist/core/a2a.d.ts +3 -1
  15. package/dist/core/a2a.js +1 -1
  16. package/dist/core/ask-origin.d.ts +351 -154
  17. package/dist/core/ask-origin.js +223 -45
  18. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  19. package/dist/core/ask-unresolvable-notice.js +4 -3
  20. package/dist/core/auto-mode-arming.d.ts +17 -4
  21. package/dist/core/auto-mode-arming.js +31 -6
  22. package/dist/core/auto-mode-defaults.d.ts +16 -7
  23. package/dist/core/auto-mode-defaults.js +2 -1
  24. package/dist/core/auto-mode-directive.d.ts +18 -30
  25. package/dist/core/auto-mode-directive.js +9 -18
  26. package/dist/core/auto-mode-prompt.d.ts +62 -1
  27. package/dist/core/auto-mode-prompt.js +42 -4
  28. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  29. package/dist/core/auto-mode-rebuild.js +1 -0
  30. package/dist/core/auto-mode.d.ts +32 -3
  31. package/dist/core/auto-mode.js +80 -41
  32. package/dist/core/checkpoint-store.d.ts +50 -35
  33. package/dist/core/checkpoint-store.js +58 -3
  34. package/dist/core/child-clamp-seats.js +1 -0
  35. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  36. package/dist/core/classifier-unavailable-arm.js +17 -0
  37. package/dist/core/effective-path-target.d.ts +12 -0
  38. package/dist/core/effective-path-target.js +18 -7
  39. package/dist/core/engine-notice.d.ts +53 -2
  40. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  41. package/dist/core/fs-write-gate-policy.js +4 -1
  42. package/dist/core/gate-fold.js +17 -1
  43. package/dist/core/gate-lanes.js +111 -46
  44. package/dist/core/gate-outcome.d.ts +40 -7
  45. package/dist/core/gate-outcome.js +26 -4
  46. package/dist/core/governance-codes.d.ts +1 -1
  47. package/dist/core/governance-codes.js +12 -0
  48. package/dist/core/hooks.d.ts +50 -29
  49. package/dist/core/hooks.js +1 -3
  50. package/dist/core/mcp.js +1 -1
  51. package/dist/core/mode-policy.d.ts +182 -0
  52. package/dist/core/mode-policy.js +179 -0
  53. package/dist/core/oracle-isolation.d.ts +3 -3
  54. package/dist/core/permission-rule-model.d.ts +16 -3
  55. package/dist/core/permission-rule-model.js +36 -3
  56. package/dist/core/permission-rule-provider.d.ts +22 -11
  57. package/dist/core/permission-rule-provider.js +17 -7
  58. package/dist/core/permission-rules.d.ts +23 -1
  59. package/dist/core/permission-rules.js +35 -8
  60. package/dist/core/persisted-rule-arms.d.ts +10 -9
  61. package/dist/core/persisted-rule-arms.js +9 -6
  62. package/dist/core/present-plan-tool.d.ts +1 -1
  63. package/dist/core/present-plan-tool.js +3 -3
  64. package/dist/core/read-only-shell.d.ts +19 -19
  65. package/dist/core/read-only-shell.js +38 -12
  66. package/dist/core/retired-keys.d.ts +9 -3
  67. package/dist/core/retired-keys.js +5 -1
  68. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  69. package/dist/core/runner/classifier-ladder.js +29 -0
  70. package/dist/core/runner/contracts.d.ts +37 -35
  71. package/dist/core/runner/denial-limit-arms.d.ts +30 -7
  72. package/dist/core/runner/denial-limit-arms.js +15 -9
  73. package/dist/core/runner/env-capability-census.js +1 -1
  74. package/dist/core/runner/gate-exit.d.ts +30 -4
  75. package/dist/core/runner/gate-exit.js +28 -7
  76. package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
  77. package/dist/core/runner/permission-rule-lanes.js +44 -40
  78. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  79. package/dist/core/runner/persisted-path-rows.js +89 -0
  80. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  81. package/dist/core/runner/prepare-artifact.js +9 -3
  82. package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
  83. package/dist/core/runner/prepare-ask-lane.js +1 -1
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  85. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  86. package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
  87. package/dist/core/runner/prepare-gate-stations.js +32 -19
  88. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  89. package/dist/core/runner/prepare-hands-readface.js +64 -53
  90. package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
  91. package/dist/core/runner/prepare-inherited-gate.js +37 -23
  92. package/dist/core/runner/prepare-park-ask.d.ts +2 -2
  93. package/dist/core/runner/prepare-park-ask.js +2 -1
  94. package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
  95. package/dist/core/runner/prepare-policy-chain.js +39 -12
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  97. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  98. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  99. package/dist/core/runner/prepare-run-refs.js +9 -4
  100. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  101. package/dist/core/runner/prepare-safety-scan.js +19 -3
  102. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  103. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  104. package/dist/core/runner/prepare-task.js +51 -22
  105. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  106. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  107. package/dist/core/runner/read-root-grant.d.ts +9 -0
  108. package/dist/core/runner/read-root-grant.js +12 -0
  109. package/dist/core/runner/retired-runner-deps.js +1 -0
  110. package/dist/core/runner/run-identity-wiring.js +1 -1
  111. package/dist/core/runner/run-leg.js +24 -15
  112. package/dist/core/runner/run-notification-lane.js +1 -0
  113. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  114. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  115. package/dist/core/runner/run-turn-boundary.js +1 -1
  116. package/dist/core/runner/runtask.js +3 -0
  117. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  118. package/dist/core/runner/stream-mode-verb.js +19 -0
  119. package/dist/core/runner/turn-attachments.d.ts +3 -3
  120. package/dist/core/runner/turn-attachments.js +1 -1
  121. package/dist/core/runner/write-protection-station.d.ts +7 -1
  122. package/dist/core/runner/write-protection-station.js +6 -3
  123. package/dist/core/runner-deps.d.ts +31 -34
  124. package/dist/core/runtime-caps.d.ts +12 -3
  125. package/dist/core/safety-axis-vocab.d.ts +2 -2
  126. package/dist/core/safety-axis-vocab.js +1 -1
  127. package/dist/core/secret-env.d.ts +16 -2
  128. package/dist/core/secret-env.js +25 -1
  129. package/dist/core/sensitive-path-policy.d.ts +4 -2
  130. package/dist/core/sensitive-path-policy.js +39 -30
  131. package/dist/core/spec-contract.d.ts +15 -0
  132. package/dist/core/spec-contract.js +19 -10
  133. package/dist/core/task-event.d.ts +1 -1
  134. package/dist/core/task-result.d.ts +1 -1
  135. package/dist/core/task-spec.d.ts +41 -89
  136. package/dist/core/task-stream.d.ts +16 -0
  137. package/dist/core/terminal-cause.js +1 -0
  138. package/dist/core/tighten-task-spec.d.ts +2 -2
  139. package/dist/core/tighten-task-spec.js +12 -14
  140. package/dist/core/tool-catalog-entries.js +4 -3
  141. package/dist/core/tool-policy.d.ts +54 -25
  142. package/dist/core/tool-policy.js +43 -2
  143. package/dist/core/tool-registry.d.ts +2 -0
  144. package/dist/core/tool-registry.js +3 -0
  145. package/dist/core/tool-spec.d.ts +8 -9
  146. package/dist/core/trace.d.ts +7 -6
  147. package/dist/core/types.d.ts +2 -1
  148. package/dist/core/wiring-manifest.d.ts +31 -5
  149. package/dist/core/wiring-manifest.js +28 -1
  150. package/dist/core/write-protect.d.ts +11 -1
  151. package/dist/core/write-protect.js +26 -15
  152. package/dist/engine/execution-env/kill-tree.js +4 -1
  153. package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
  154. package/dist/engine/execution-env/node-execution-env.js +34 -35
  155. package/dist/engine/harness/types.d.ts +6 -7
  156. package/dist/engine/llm/types.d.ts +2 -4
  157. package/dist/engine/loop/types.d.ts +7 -0
  158. package/dist/index.d.ts +15 -5
  159. package/dist/index.js +12 -3
  160. package/dist/orchestration/run-spec.d.ts +4 -0
  161. package/dist/orchestration/run-spec.js +4 -3
  162. package/dist/orchestration/workflow-governance.js +1 -1
  163. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  164. package/dist/orchestration/workflow-size-guideline.js +2 -2
  165. package/dist/stores/file/fs-atomic.js +2 -1
  166. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  167. package/dist/tools/artifact/artifact-tool.js +16 -1
  168. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  169. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  170. package/dist/tools/fs/containment-face.d.ts +9 -3
  171. package/dist/tools/fs/containment-face.js +2 -0
  172. package/dist/tools/fs/file-read-check.d.ts +49 -0
  173. package/dist/tools/fs/file-read-check.js +71 -0
  174. package/dist/tools/fs/fs-bash.d.ts +3 -3
  175. package/dist/tools/fs/fs-read.d.ts +1 -0
  176. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  177. package/dist/tools/fs/fs-search-tools.js +4 -4
  178. package/dist/tools/fs/fs-shared.js +20 -6
  179. package/dist/tools/fs/fs-write.d.ts +4 -3
  180. package/dist/tools/fs/fs-write.js +9 -9
  181. package/dist/tools/fs/index.d.ts +39 -3
  182. package/dist/tools/fs/index.js +38 -15
  183. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  184. package/dist/tools/fs/read-deny-home.js +225 -0
  185. package/dist/tools/fs/read-deny.d.ts +176 -34
  186. package/dist/tools/fs/read-deny.js +268 -61
  187. package/dist/tools/fs/safety.d.ts +6 -1
  188. package/dist/tools/fs/safety.js +18 -5
  189. package/dist/tools/fs/search.d.ts +3 -0
  190. package/dist/tools/fs/search.js +47 -9
  191. package/dist/tools/fs/shell-read-check.d.ts +42 -11
  192. package/dist/tools/fs/shell-read-check.js +188 -13
  193. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  194. package/dist/tools/fs/shell-read-establish.js +134 -21
  195. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  196. package/dist/tools/fs/shell-read-identity.js +21 -0
  197. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  198. package/dist/tools/fs/shell-read-judge.js +43 -18
  199. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  200. package/dist/tools/fs/shell-read-plan.js +26 -4
  201. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  202. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  203. package/package.json +3 -1
  204. package/test/export-surface.snapshot.json +103 -11
  205. package/wire-changes/core@7.33.8.json +165 -0
  206. package/wire-changes/core@7.34.0.json +2621 -0
@@ -1,5 +1,6 @@
1
1
  import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js";
2
2
  import type { IrreversibilityTier, ReversibilityProbes } from "./contracts.js";
3
+ import type { ToolMarkSource } from "../ask-origin.js";
3
4
  export interface PrepareSafetyScanInput {
4
5
  /** borrowed-readonly — the REBOUND spec (`spec′`, the config-doors result). The scan reads
5
6
  * `tools`, `handsReadOnly` and `enablePlanMode`; it never mutates. */
@@ -42,6 +43,10 @@ export interface PrepareSafetyScanResult {
42
43
  }>;
43
44
  /** borrowed-mutable — declared reversibility probes by tool name. */
44
45
  reversibilityProbes: ReversibilityProbes;
46
+ /** borrowed-mutable — WHO put each tool's egress / irreversibility mark on its seat (the caller roster's marks are
47
+ * the deployment's own; the protocol fold adds `mcp_annotation` / `a2a_default`). Read by the gate's class
48
+ * derivation: a mark with no recorded source reads as the deployment's (fail-closed). */
49
+ toolMarkSources: Map<string, Set<ToolMarkSource>>;
45
50
  /** borrowed-readonly — the caller roster's own names, snapshotted before the first await. */
46
51
  ownToolNames: ReadonlySet<string>;
47
52
  }
@@ -14,6 +14,14 @@ export function prepareSafetyScan(input) {
14
14
  const irreversibilityTier = new Map();
15
15
  const axisExplicitNegatives = new Map();
16
16
  const reversibilityProbes = new Map();
17
+ const toolMarkSources = new Map();
18
+ const markedBy = (name, source) => {
19
+ const set = toolMarkSources.get(name);
20
+ if (set !== undefined)
21
+ set.add(source);
22
+ else
23
+ toolMarkSources.set(name, new Set([source]));
24
+ };
17
25
  for (const t of spec.tools ?? []) {
18
26
  {
19
27
  const bound = rosterMemberBoundProblem({
@@ -72,8 +80,10 @@ export function prepareSafetyScan(input) {
72
80
  const tier = t.irreversibility ?? (t.reversibilityProbe ? "maybe" : undefined);
73
81
  if (tier !== undefined) {
74
82
  irreversibilityTier.set(t.name, tier);
75
- if (tier === "always" || tier === "maybe")
83
+ if (tier === "always" || tier === "maybe") {
76
84
  irreversibleTools.add(t.name);
85
+ markedBy(t.name, "deployment");
86
+ }
77
87
  }
78
88
  if (t.reversibilityProbe)
79
89
  reversibilityProbes.set(t.name, t.reversibilityProbe);
@@ -84,6 +94,7 @@ export function prepareSafetyScan(input) {
84
94
  throw e;
85
95
  }
86
96
  egressTools.add(t.name);
97
+ markedBy(t.name, "deployment");
87
98
  }
88
99
  for (const alias of t.aliases ?? []) {
89
100
  if (alias === t.name || ownToolNames.has(alias))
@@ -95,13 +106,17 @@ export function prepareSafetyScan(input) {
95
106
  toolEffects.set(alias, t.effect);
96
107
  if (tier !== undefined) {
97
108
  irreversibilityTier.set(alias, tier);
98
- if (tier === "always" || tier === "maybe")
109
+ if (tier === "always" || tier === "maybe") {
99
110
  irreversibleTools.add(alias);
111
+ markedBy(alias, "deployment");
112
+ }
100
113
  }
101
114
  if (t.reversibilityProbe)
102
115
  reversibilityProbes.set(alias, t.reversibilityProbe);
103
- if (t.egress)
116
+ if (t.egress) {
104
117
  egressTools.add(alias);
118
+ markedBy(alias, "deployment");
119
+ }
105
120
  }
106
121
  }
107
122
  toolEffects.set(OFFLOAD_TOOL_NAME, "read");
@@ -122,6 +137,7 @@ export function prepareSafetyScan(input) {
122
137
  irreversibilityTier,
123
138
  axisExplicitNegatives,
124
139
  reversibilityProbes,
140
+ toolMarkSources,
125
141
  ownToolNames,
126
142
  };
127
143
  }
@@ -26,6 +26,7 @@ import type { ReadFileState } from "../../tools/fs/safety.js";
26
26
  import type { OutputRef } from "./synthetic-tools.js";
27
27
  import type { HookInvocationIdentity } from "../hooks.js";
28
28
  import type { InheritedGate, Prepared, PrepareResume, RunInternals, SuspendSaga } from "./contracts.js";
29
+ import type { PermissionModeController } from "../mode-policy.js";
29
30
  /**
30
31
  * The ONE compensation for a paused-but-unparked VM, shared by the fence's
31
32
  * post-pause checkpoints (③ in the park closure, ④ in the saga) and the saga's put-failure absent
@@ -130,12 +131,11 @@ export interface PrepareSuspendSagaInput {
130
131
  * mid-run and enforced here has to survive suspend exactly as it survives a spawn, and the same
131
132
  * projection serves both). Cloned onto the row. */
132
133
  ancestorRulesNow: () => InheritedGate["ancestorRules"];
133
- /** borrowed-readonly — the inherited shell-gate doctrine, persisted verbatim. */
134
+ /** borrowed-readonly — the inherited shell floor, persisted verbatim. */
134
135
  inheritedShellGate: InheritedGate["shellGate"];
135
- /** borrowed-readonly — the auto-mode intent (seat ∨ live ∨ seed), carried while the latch is healthy. */
136
- autoModeIntent: boolean;
137
- /** borrowed-readonly — the resolved approver posture (seat, else live, else seed), persisted verbatim. */
138
- approverPosture: InheritedGate["approverPosture"];
136
+ /** borrowed-readonly — the leg's live permission-mode seat; the DECLARED mode (request clamped under the chain's ceiling,
137
+ * before the deployment's fold) is what the row records — the resume leg re-judges the fold for its own deployment. */
138
+ permissionMode: PermissionModeController;
139
139
  /** borrowed-readonly — the auto-mode decider, or undefined (the latch-health reading only). */
140
140
  /** borrowed-readonly — the inherited admitted org scopes, or undefined (copied onto the row). */
141
141
  inheritedAdmittedOrgScopes: readonly string[] | undefined;
@@ -79,7 +79,7 @@ function stampPlacementRootSessionId(placementRootResolved) {
79
79
  return placementValueOrAbsent(placementRootResolved);
80
80
  }
81
81
  export function prepareSuspendSaga(input) {
82
- const { gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, autoModeIntent, approverPosture, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter } = input;
82
+ const { gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter } = input;
83
83
  if (!gateMachineryActive)
84
84
  return { saga: undefined };
85
85
  const inFlightSpendMicroUsd = () => {
@@ -134,12 +134,12 @@ export function prepareSuspendSaga(input) {
134
134
  const constraintDigest = (inheritedParentConstraints?.length ?? 0) > 0
135
135
  ? constraintChainDigest(constraintChain)
136
136
  : seedInheritedGate?.constraintDigest;
137
- const autoModeIntentCarried = autoModeIntent;
137
+ const declaredMode = permissionMode.declared;
138
+ const modeRecorded = declaredMode !== "default" || permissionMode.requested !== undefined || seedInheritedGate?.permissionMode !== undefined;
138
139
  const ancestorRulesAtMint = ancestorRulesNow();
139
140
  return ancestorRulesAtMint !== undefined ||
140
141
  inheritedShellGate !== undefined ||
141
- autoModeIntentCarried ||
142
- approverPosture !== undefined ||
142
+ modeRecorded ||
143
143
  inheritedAdmittedOrgScopes !== undefined ||
144
144
  ownOrgVerdictRef.current !== undefined ||
145
145
  orgGovernedProvenance ||
@@ -147,8 +147,8 @@ export function prepareSuspendSaga(input) {
147
147
  ? {
148
148
  ...(ancestorRulesAtMint !== undefined ? { ancestorRules: structuredClone(ancestorRulesAtMint) } : {}),
149
149
  ...(inheritedShellGate !== undefined ? { shellGate: inheritedShellGate } : {}),
150
- ...(autoModeIntentCarried ? { autoModeRequested: true } : {}),
151
- ...(approverPosture !== undefined ? { approverPosture } : {}),
150
+ ...(modeRecorded ? { permissionMode: declaredMode } : {}),
151
+ ...(permissionMode.prePlanMode !== undefined ? { prePlanMode: permissionMode.prePlanMode } : {}),
152
152
  ...(inheritedAdmittedOrgScopes !== undefined ? { admittedOrgScopes: [...inheritedAdmittedOrgScopes] } : {}),
153
153
  ...(ownOrgVerdictRef.current !== undefined
154
154
  ? { ownAdmittedOrgScopes: [...ownOrgVerdictRef.current.scopes], ownAdmittedOrgWriteScope: ownOrgVerdictRef.current.writeScope }
@@ -8,10 +8,15 @@ import { brainToRuntime } from "../runtime.js";
8
8
  import { hasSessionFork } from "../session.js";
9
9
  import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
10
10
  import { callerToolMembership } from "../../agents/inherited-tool-pool.js";
11
+ import { toolPolicyPathDenyRules, toolPolicyWriteTargetGuard } from "../tool-policy.js";
11
12
  import { loadAnnounceOnceLedger } from "./announce-once-ledger.js";
12
13
  import { isValidReminderMark, mintReminderMark } from "../reminder-mint.js";
13
- import { refuseInvalidApproverPosture } from "../auto-mode-directive.js";
14
+ import { clampPermissionMode, isPermissionMode, PERMISSION_MODES } from "../ask-origin.js";
15
+ import { modePolicyOf, modeRequestAfterResume } from "../mode-policy.js";
16
+ import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "../retired-keys.js";
17
+ import { migrateLegacyModeSeed } from "../checkpoint-store.js";
14
18
  import { emitTrace } from "../trace.js";
19
+ import { persistedPathRowsOfTask, ruleStoreUnreadableNotice } from "./persisted-path-rows.js";
15
20
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
16
21
  import { STALL_FIRST_BYTE_MS, STALL_FIRST_TOKEN_MS, STALL_IDLE_MS, withBrainCallGuardrail } from "../../brain/timeout.js";
17
22
  import { OFFLOAD_TOOL_NAME } from "../tool-result-store.js";
@@ -260,13 +265,21 @@ function refuseRegistryScopeMismatch(spec, internals) {
260
265
  function validateInputFaultNotice(onError, sessionId) {
261
266
  return ({ toolName, toolCallId, error }) => onError?.(new Error(`tool input pre-validation (validateInput) threw for ${toolName} (call ${toolCallId}) — read as no verdict; the call proceeded to the gate: ${thrownText(error)}`), { phase: "hook", sessionId });
262
267
  }
263
- function refuseInvalidAutoModeSeat(spec) {
264
- if (spec.autoModeRequested !== undefined && spec.autoModeRequested !== true) {
265
- const e = new Error(`autoModeRequested must be the literal true when present (got ${JSON.stringify(spec.autoModeRequested)}) — omit it for a non-auto task; a non-boolean or false value is refused rather than read as either polarity.`);
266
- e.code = "config.auto_mode_requested_invalid";
268
+ function refusePermissionModeDoors(spec) {
269
+ assertNoRetiredKeys(spec, RETIRED_TASK_SPEC_KEYS, "TaskSpec");
270
+ const mode = spec.permissionMode;
271
+ if (mode !== undefined && !isPermissionMode(mode)) {
272
+ const e = new Error(`permissionMode must be one of ${PERMISSION_MODES.map((w) => JSON.stringify(w)).join(", ")} when present (got ${JSON.stringify(mode)}) — omit it for the default mode; an unrecognized word is refused rather than read as any mode.`);
273
+ e.code = "config.permission_mode_invalid";
267
274
  throw e;
268
275
  }
269
- return spec.autoModeRequested === true;
276
+ const floor = spec.shellGate;
277
+ if (floor !== undefined && floor !== "always") {
278
+ const e = new Error(`shellGate must be the floor word "always" when present (got ${JSON.stringify(floor)}) — the doctrine values "off" / "classify" are retired: the shell's shape tier is installed on every full shell and the permission mode (permissionMode) decides what its asks become; omit shellGate for no operator floor.`);
279
+ e.code = "config.shell_gate_value_retired";
280
+ throw e;
281
+ }
282
+ return mode;
270
283
  }
271
284
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink) {
272
285
  const runId = uuidv7();
@@ -276,12 +289,18 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
276
289
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
277
290
  spec = doors.spec;
278
291
  const checkpointStore = resolveCheckpointStore(spec, deps), checkpointSeat = checkpointSeatOf(spec, checkpointStore);
279
- const autoModeSeat = refuseInvalidAutoModeSeat(spec);
280
- const approverPostureSeat = refuseInvalidApproverPosture(spec);
292
+ const permissionModeSeat = refusePermissionModeDoors(spec);
293
+ const declaredModeAtDoor = (() => {
294
+ const requested = modeRequestAfterResume(permissionModeSeat, migrateLegacyModeSeed(resume?.seed.inheritedGate), resume?.outcome);
295
+ const ceiling = internals?.inheritedGate?.permissionModeCeiling?.current ?? internals?.inheritedGate?.permissionMode;
296
+ return ceiling === undefined ? (requested ?? "default") : clampPermissionMode(requested, ceiling).effective;
297
+ })();
298
+ if (modePolicyOf(declaredModeAtDoor).enablePlanMode)
299
+ spec = { ...spec, enablePlanMode: true };
281
300
  const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, microCompactKnob, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, memoryProvenance, memoryCapturePolicy, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
282
301
  announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
283
302
  const modelGateManifest = toolModelGateManifest(doors.modelGate);
284
- const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes } = prepareSafetyScan({ spec, deps });
303
+ const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, toolMarkSources } = prepareSafetyScan({ spec, deps });
285
304
  let shellGatedTools = new Set();
286
305
  refuseRequireExistingWithoutSession(spec);
287
306
  refuseRegistryScopeMismatch(spec, internals);
@@ -385,15 +404,22 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
385
404
  const { taskRootFinal, rebaseRestoredPath } = await prepareWorkspaceRestore({ ownedEnv, deps, spec, internals, resume, taskRootInitial, abortController, sessionId });
386
405
  const { canonical: taskRootCanonical } = await resolveTaskRootCanonical({ env: executionEnv, given: taskRootFinal, onNotice: deps.onNotice, sessionId, runId });
387
406
  const { trackFileEdit, fileHistoryBoundary, restoredFilePaths, rewindNotes, fileHistoryEnabled, fileHistoryFact, historyScope, historyRoot, historyFs } = await prepareFileHistory({ deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController });
388
- const { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation } = prepareRunRefs({ spec, internals, executionEnv, taskRootFinal });
407
+ const { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, bindPermissionModeSeat, enterPlanMode, subagentRetain, worktreeIsolation } = prepareRunRefs({ spec, internals, executionEnv, taskRootFinal });
389
408
  const legContextOverlay = () => runnerSelf?.contextOverlayOf?.(harnessRef.current?.getModelRef() ?? resolvedRole.modelRef);
390
409
  let resolvedReadFace;
391
410
  let resolvedWriteFace;
392
411
  let readDenyAdditionsNormalized = [];
393
412
  let handsLessResolvedFace;
394
413
  let handsLessResolvedWriteFace;
395
- const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, approverPosture: approverPostureResolved, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, autoModeSeat, approverPostureSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDecider, autoModeDenialTracking: () => autoModeDenialTracking, autoModeArming: () => autoModeArming, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
396
- const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDecider, autoModeDenialTracking, autoModeArming, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, parentSpecSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, autoModeIntent, peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
414
+ const { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx } = prepareInheritedGate({ spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, checkpointSeat, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun: () => { abortController.abort(); void harness.abort(); }, runtimeCaps: () => runtimeCaps, hooks: () => hooks, preToolUseObservational: () => preToolUseObservational, hookTimeoutMs: () => hookTimeoutMs, hookEnvFace: () => hookEnvFace, autoModeDecider: () => autoModeDeciderRef.current, autoModeDenialTracking: () => autoModeDenialTrackingRef.current, autoModeArming: () => autoModeArmingRef.current, agentForkDenial: () => agentForkDenial, observersActive: () => observersActive, resolvedReadFace: () => resolvedReadFace, resolvedWriteFace: () => resolvedWriteFace, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized, handsLessResolvedFace: () => handsLessResolvedFace, handsLessResolvedWriteFace: () => handsLessResolvedWriteFace, centerAdoption: () => centerAdoptionRef.current, memoryEngineSession: () => memoryEngineSession });
415
+ bindPermissionModeSeat(permissionMode);
416
+ const announceRuleStoreSkipped = ruleStoreUnreadableNotice(deps.onNotice, { sessionId, runId });
417
+ const persistedPathRowsRead = persistedPathRowsOfTask({ deps, principal: spec.principal, sessionId, root: taskRootFinal, home: executionEnv.homeDir, taskId: hostTaskId, handsEnabled, signals: [abortController.signal, ...(spec.signal !== undefined ? [spec.signal] : [])], onUnread: announceRuleStoreSkipped });
418
+ const persistedPathRows = persistedPathRowsRead === undefined ? { readDirectories: [], denyRows: [] } : await persistedPathRowsRead;
419
+ const effectivePathDenyRules = [...toolPolicyPathDenyRules([lockedPreflight.toolPolicy, ...(inheritedParentConstraints ?? []).map((c) => c.policy)]), ...persistedPathRows.denyRows];
420
+ const writeProtectionJudgeRef = {};
421
+ const writeGuards = { protection: () => writeProtectionJudgeRef.current, target: toolPolicyWriteTargetGuard([lockedPreflight.toolPolicy, ...(inheritedParentConstraints ?? []).map((c) => c.policy)]) };
422
+ const { roster, blockedRef, runtimeCaps, runtimeCapsFaulted, complianceDenies, complianceDegraded, agentForkDenial, observersActive, autoModeArmReason, autoModeDeciderRef, autoModeDenialTrackingRef, autoModeArmingRef, armAutoMode, selfOrchestrationActive, workflowToolsActive, workflowSizeGuideline } = await prepareCapsAndWorkflow({ spec, deps, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, checkpointSeat, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, parentSpecSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries: () => ({ entries: readDenyMatcher?.entries ?? [], ruleRows: effectivePathDenyRules }), peerLaneActive, peerSendMessageBuiltIn, runnerSelf, memoryEngineSession: () => memoryEngineSession, centerAdoption: () => centerAdoptionRef.current, readDenyAdditionsNormalized: () => readDenyAdditionsNormalized });
397
423
  const tools = roster.tools;
398
424
  const pausedRef = {};
399
425
  const undeliveredContinuationRef = { current: { parkedSteers: [], orphanTasks: [] } };
@@ -454,7 +480,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
454
480
  roster.mount(createOutputTool(outputRef, spec.outputSchema, compiled.strict ? compiled.modelSchema : undefined), { source: "synthetic", mountedBy: "optional" });
455
481
  }
456
482
  const rosterSeat = new ToolRosterDeltaSeat();
457
- const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef, mcpRedial } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback });
483
+ const { mcp, a2a, rebuildHarnessToolsRef, contentOriginWrapRef, toolCallGateArmedRef, mcpRedial } = await prepareProtocolTools({ lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, toolMarkSources, abortController, rollback });
458
484
  const callIssuedAtRef = {};
459
485
  const workCutRef = { continuation: false, abortedBeforeTurnEnd: false };
460
486
  const advertisedWritableDirs = handsEnabled
@@ -530,7 +556,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
530
556
  if (internals?.ownOrgAdmissionRef !== undefined && ownOrgVerdictRef.current !== undefined) {
531
557
  internals.ownOrgAdmissionRef.current = ownOrgVerdictRef.current;
532
558
  }
533
- const handsReadFaceInput = { handsEnabled, executionEnv, taskRootFinal, taskRootCanonical, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, harnessRef, toolProgressRef, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
559
+ const handsReadFaceInput = { permissionMode, pathDenyRules: effectivePathDenyRules, writeGuards, persistedReadDirectories: persistedPathRows.readDirectories, handsEnabled, executionEnv, taskRootFinal, taskRootCanonical, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes, reminderMark, reminderDisclosureCounts, ...(trackFileEdit !== undefined ? { trackFileEdit } : {}), onFileEdited: noteFileEdited, harnessRef, toolProgressRef, ...(restoredFilePaths.length > 0 ? { restoredFilePaths } : {}) };
534
560
  const handsReadFace = handsEnabled ? await prepareHandsMount(handsReadFaceInput) : resolveHandsLessReadFace(handsReadFaceInput);
535
561
  const { readFileStateForCheckpoint, seedContextFiles, handsCwdRef, workspaceStateSettle, wsSnapshot, rebaseWsPath, backgroundTaskToolsActive, shellReadCheck, shellReadAdmission, additionalRootsCanonical, additionalReadRootsCanonical, attachmentRootCanonical, readDenyMatcher, envHandToolNames, sealReadStateSeat } = handsReadFace;
536
562
  resolvedReadFace = handsReadFace.resolvedReadFace;
@@ -539,6 +565,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
539
565
  handsLessResolvedFace = handsReadFace.handsLessResolvedFace;
540
566
  handsLessResolvedWriteFace = handsReadFace.handsLessResolvedWriteFace;
541
567
  shellGatedTools = handsReadFace.shellGatedTools;
568
+ await armAutoMode();
542
569
  const { worktreeSessionRef } = prepareDelegationSurface({ spec, deps, internals, resume, sessionId, hostTaskId, taskScope, taskRootFinal, taskRootCanonical, executionEnv, runnerSelf, backgroundTaskToolsActive, shellReadAdmission, workflowToolsActive, peerLaneActive, peerSendMessageBuiltIn, peerLaneRefs, sessionBox, roster, toolEffects, axisExplicitNegatives, envHandToolNames, toolFaceSnapshot, firstPartyOffload, offloadStore, subagentRetain, enrichSpecToolCtx, handsCwdRef, wsSnapshot, rebaseWsPath, rebaseRestoredPath, workspaceStateSettle });
543
570
  const { liveQuestionFace, contentAskBindings, contentAskRoutable, lateStrandedAnswers, discloseStrandedAnswers, settleContentAskBindings, durableQuestionFace, questionToolMounted, mountedQuestionTool } = prepareQuestionFace({ frozenOnQuestion, spec, deps, checkpointStore, runtimeCaps, resolvedInteractionPosture, resume, sessionId, roster, inheritedUnavailableAsks: () => inheritedUnavailableAsks });
544
571
  if (spec.handsReadOnly !== true) {
@@ -559,7 +586,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
559
586
  `(per-principal entitlement governance, not a misconfiguration — Agent(subagent_type:"fork") will refuse honestly)`), { phase: "config", sessionId });
560
587
  }
561
588
  const { lspDiagnostics, nudgeLspOnEdit, lspRunIdent, managerWired: lspManagerWired } = prepareLsp({ spec, deps, executionEnv, taskRootFinal, handsEnabled, sessionId, handsCwdRef, roster, envHandToolNames });
562
- prepareArtifact({ deps, runId, taskId: hostTaskId, sessionId, agentName: internals?.explicitAgentName, onAsk: frozenOnAsk, executionEnv, rootCanonical: attachmentRootCanonical, additionalRootsCanonical: () => [...additionalRootsCanonical, ...additionalReadRootsCanonical], readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, ...(handsReadFace.artifactStoreRef !== undefined ? { artifactStoreRef: handsReadFace.artifactStoreRef } : {}) });
589
+ prepareArtifact({ deps, runId, taskId: hostTaskId, sessionId, agentName: internals?.explicitAgentName, onAsk: frozenOnAsk, permissionMode, executionEnv, rootCanonical: attachmentRootCanonical, additionalRootsCanonical: () => [...additionalRootsCanonical, ...additionalReadRootsCanonical], readDenyMatcher, resolvedReadFace, handsCwdRef, roster, firstPartyOffload, envHandToolNames, ...(handsReadFace.artifactStoreRef !== undefined ? { artifactStoreRef: handsReadFace.artifactStoreRef } : {}) });
563
590
  if (internals?.delegationProvenance !== undefined && resume !== undefined) {
564
591
  const live = internals.delegationProvenance.ref.current;
565
592
  const seed = resume.seed.delegationProvenance;
@@ -598,7 +625,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
598
625
  }
599
626
  const projectContext = await prepareProjectContext({ deps, spec, internals, session, sessionId, hostTaskId, taskRootFinal, handsEnabled, delegation, seedContextFiles, memoryBlockFromEngine, skillScope, roster, toolFaceSnapshot, onceLedger, memoryTools, memoryEngineSession, memorySearchToolsPlanned, memoryPairExcludedName, memoryPairOccupiedName, resume, skillListingBudget: doors.skillListingBudget });
600
627
  const { memoryBlock: memoryBlockWithProject, instructionSources, projectInstructionContent, hasSkillManifest, skillsListing, sharedMemoryPairMounted, memoryEnginePairMounted } = projectContext;
601
- const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, dateAnchor, gitStatusRef } = await preparePromptInputs({ spec, deps, contextOverlay: legContextOverlay(), failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace, toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, session, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}) });
628
+ const { provider, featureFlags, userSystemPrompt, userAppendSystemPrompt, mcpInstructionsBlock, envFacts, tzValid, userTz, dateAnchor, gitStatusRef } = await preparePromptInputs({ spec, deps, contextOverlay: legContextOverlay(), failClosedReason: selfOrchestrationFailClosedReason(spec, deps), lockedPreflight, selfOrchestrationActive, executionEnv, reminderMark, resolvedReadFace, resolvedWriteFace: resolvedWriteFace?.inForce(), toolFaceSnapshot, promptProfile, fableMitigations, internals, thinking, ownedEnv, delegation, resolvedRole, mcp, model, externalContentTargetActive, resume, handsEnabled, taskRootFinal, additionalRootsCanonical, additionalReadRootsCanonical, sessionId, session, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}) });
602
629
  const { deferred, memoryBlock, toolSearch: toolSearchPolicy } = prepareDeferClassify({ spec, deps, roster, toolFaceSnapshot, handsEnabled, fullShellReachable, promptProfile, model, mcp, a2a, sharedMemoryPairMounted, memoryEnginePairMounted, memoryBlock: memoryBlockWithProject, memoryRecallSegment, onceLedger, sessionId });
603
630
  prepareMemoryEngineSession({ memoryEngineSession, internals, memoryDelegationEvidence, externalContentTargetActive, lockedPreflight, mcp, sessionId, runId, deps, provenanceForChildrenRef, delegationSettlementRef, spec, envHandToolNames, roster, contentOriginWrapRef });
604
631
  const { systemPromptSeat, renderWithDate, systemBlocks, promptManifest, epochDeclaredSections, epochArtifactDigestForSnapshot, buildAssembleInputs, centerAdoptionRef } = await preparePromptAssembly({ session, deps, internals, resume, provider, userSystemPrompt, userAppendSystemPrompt, featureFlags, mcpInstructionsBlock, memoryBlock, envFacts, promptProfile, fableMitigations, tools, model, sessionId, reminderMark, onceLedger });
@@ -710,25 +737,27 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
710
737
  releaseSignal = () => sig.removeEventListener("abort", onExternalAbort);
711
738
  }
712
739
  }
713
- const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, envHandToolNames, handsCwdRef, readDenyMatcher, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
714
- const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtectionCheck, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: shellReadCheck !== undefined, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
740
+ const { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations } = await preparePolicyChain({ spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, hasSkillManifest, skillScope, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider: () => autoModeDeciderRef.current, permissionMode, scratchpadDir: spec.envFacts?.scratchpadDir, toolRosterDeltas: rosterSeat, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef });
741
+ const { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, durableApproval, wiringManifest, writeProtection, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound } = prepareWiringManifest({ spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, shellReadStationArmed: handsReadFace.shellReadStationArmed, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive: handsReadFace.readDenyBuiltinTiersActive, lspManagerWired, lspDiagnosticsLive: lspDiagnostics !== undefined, fileHistoryFact, sessionBox });
742
+ const writeProtectionCheck = writeProtection?.check;
743
+ writeProtectionJudgeRef.current = writeProtection?.judgeTarget;
715
744
  if (fileHistoryBoundary !== undefined) {
716
745
  harness.on("tool_call", async () => {
717
746
  await fileHistoryBoundary.settle();
718
747
  return undefined;
719
748
  });
720
749
  }
721
- const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
722
- const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, autoModeIntent, approverPosture: approverPostureResolved, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
750
+ const { askLane } = prepareAskLane({ gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking: autoModeDenialTrackingRef, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot: handsReadFace.grantReadRoot });
751
+ const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
723
752
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
724
753
  const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef });
725
- prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
754
+ prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: wiringManifest.ask, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
726
755
  const postureFrameRef = {};
727
756
  const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, contextOverlay: legContextOverlay, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
728
757
  const preparedHolder = {};
729
758
  const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
730
759
  await onceLedger.settle(session, announcedListingsRef);
731
- const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { autoMode: autoModeIntent, ...(approverPostureResolved !== undefined ? { approverPosture: approverPostureResolved } : {}) }, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
760
+ const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: wiringManifest.ask.effective ?? "unresolved" }, permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
732
761
  rollback.commit();
733
762
  const prepared = buildPrepared();
734
763
  preparedHolder.current = prepared;
@@ -29,10 +29,11 @@ import { type ToolResultStore } from "../tool-result-store.js";
29
29
  import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
30
30
  import type { GateOutcome } from "../gate-outcome.js";
31
31
  import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect, ToolExecuteContext } from "../types.js";
32
- import type { WriteProtectionCheck } from "../write-protect.js";
32
+ import { type LegWriteProtection } from "./write-protection-station.js";
33
33
  import { type UsageWindow } from "../usage-window-store.js";
34
34
  import type { ToolRoster } from "../tool-roster.js";
35
35
  import { type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry } from "../wiring-manifest.js";
36
+ import type { PermissionModeController } from "../mode-policy.js";
36
37
  import type { AnnounceOnceSink, Prepared, PrepareResume, RunInternals, UsageGovernance } from "./contracts.js";
37
38
  export interface PrepareWiringManifestInput {
38
39
  /** borrowed-readonly — the REBOUND spec. Read: `onAsk` (the own seat), `onQuestion` (the question seam), `handsReadOnly` / `tools` (the ungated scan),
@@ -119,8 +120,11 @@ export interface PrepareWiringManifestInput {
119
120
  /** borrowed-readonly — from the LSP phase's manager fold: gates the manifest's LSP section (absent ⇒ byte-identical manifest). */ lspManagerWired: boolean;
120
121
  /** borrowed-readonly — from the LSP phase: is the diagnostics lane live? The `lane` word's only input. */ lspDiagnosticsLive: boolean;
121
122
  /** borrowed-readonly — from the file-history phase's one fold of the two off switches: the manifest's word, relayed. */ fileHistoryFact: NonNullable<WiringManifest["fileHistory"]>;
122
- /** borrowed-readonly — the arming outcome the caps phase minted (the manifest's auto-mode face). */
123
- autoModeArmReason: AutoModeArmFact;
123
+ /** borrowed-readonly — the arming outcome the caps phase reads live (the manifest's auto-mode face at this leg's start). */
124
+ autoModeArmReason: () => AutoModeArmFact;
125
+ /** borrowed-readonly — the leg's live permission-mode seat: its receipt at this leg's start rides the manifest, and the
126
+ * peer lane reads the effective mode through the refs filled here. */
127
+ permissionMode: PermissionModeController;
124
128
  /** borrowed-readonly — the frozen preflight; only `mcp` (the declared servers the manifest face joins). */
125
129
  lockedPreflight: Pick<LockedPreflight, "mcp">;
126
130
  /** borrowed-mutable — the peer lane's late-bound facts. Writer here: `askEffective` ← the manifest's ask derivation (the
@@ -176,9 +180,9 @@ export interface PrepareWiringManifestResult {
176
180
  durableApproval: TaskSpec["durableApproval"];
177
181
  /** owned — this leg's EFFECTIVE wiring manifest (`Prepared.wiringManifest`). */
178
182
  wiringManifest: WiringManifest;
179
- /** owned — the leg's write-protection gate input, or undefined when the table was emptied (no judge, no
180
- * tighten mounted); handed to the gate station rather than compiled a second time there. */
181
- writeProtectionCheck: WriteProtectionCheck | undefined;
183
+ /** owned — the leg's write-protection judge, or undefined when the table was emptied (no judge, no tighten mounted):
184
+ * its gate input (`check`) for the gate station and its target judge for the shell station — one compile, never two. */
185
+ writeProtection: LegWriteProtection | undefined;
182
186
  /** owned — THE park predicate as one named const (`parkLane.effective === true`). */
183
187
  parkLaneArmed: boolean;
184
188
  /** owned — THE gate-machinery activation predicate as one named const: a policy, a screening hook, an egress or
@@ -116,7 +116,7 @@ function announceDurableGateUnavailable(args) {
116
116
  });
117
117
  }
118
118
  export function prepareWiringManifest(input) {
119
- const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, lockedPreflight, peerLaneRefs, frameSettlement, sessionBox, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, shellReadStationArmed, lspDiagnosticsLive, fileHistoryFact } = input;
119
+ const { spec, deps, internals, resume, sessionId, runId, hostTaskId, taskScope, hooks, preToolUseObservational, policyLayers, effectivePolicy, handsEnabled, taskRootFinal, mcp, irreversibleTools, egressTools, toolEffects, ownedEnv, executionEnv, offloadStore, onceLedger, usageWindows, envLifetimeSuspendAt, runtimeCaps, resolvedInteractionPosture, durableQuestionFace, liveQuestionFace, checkpointStore, sessions, modelGateManifest, autoModeArmReason, permissionMode, lockedPreflight, peerLaneRefs, frameSettlement, sessionBox, harness, pausedRef, frozenOnAsk, liveInheritedGate, delegation, toolRoster, readDenyBuiltinTiersActive, lspManagerWired, shellReadStationArmed, lspDiagnosticsLive, fileHistoryFact } = input;
120
120
  const onAsk = spec.onAsk ?? deps.onAsk;
121
121
  const handWriteTools = toolRoster.entries.filter((e) => e.mountedBy === "hands" && (e.effect === "write" || e.effect === "idempotent")).map((e) => e.name);
122
122
  const hasEffectAwareGate = Boolean(policyLayers.length > 0 || (hooks?.preToolUse !== undefined && !preToolUseObservational));
@@ -210,7 +210,8 @@ export function prepareWiringManifest(input) {
210
210
  memoryAdmissionWired: deps.memoryScopeAdmission !== undefined,
211
211
  retentionPolicyWired: deps.retentionPolicy !== undefined,
212
212
  ...(modelGateManifest !== undefined ? { modelGate: modelGateManifest } : {}),
213
- autoMode: { ...autoModeArmReason },
213
+ autoMode: { ...autoModeArmReason() },
214
+ permissionMode: permissionMode.receipt(),
214
215
  mcp: mcpManifestEntries(lockedPreflight.mcp, mcp.statuses),
215
216
  ...(writeProtection !== undefined ? { writeProtectionTargetView: writeProtection.targetView } : {}),
216
217
  readDenyBuiltinTiersActive,
@@ -222,6 +223,7 @@ export function prepareWiringManifest(input) {
222
223
  fileHistory: fileHistoryFact,
223
224
  });
224
225
  peerLaneRefs.askEffective = wiringManifest.ask.effective;
226
+ peerLaneRefs.permissionMode = () => permissionMode.current;
225
227
  const sessionBoxBound = sessionBox !== undefined && internals?.onTaskNotification !== undefined ? sessionBox : undefined;
226
228
  if (sessionBoxBound !== undefined && internals?.onTaskNotification !== undefined) {
227
229
  bindPeerLaneDrain(harness, {
@@ -261,5 +263,5 @@ export function prepareWiringManifest(input) {
261
263
  });
262
264
  const hookContextConsumerWired = hooks?.preToolUse !== undefined || hooks?.postToolUse !== undefined || hooks?.postToolUseFailure !== undefined;
263
265
  const hookEnvFace = hookContextConsumerWired && (ownedEnv ?? deps.executionEnv) != null ? createHookEnvCapabilities(executionEnv) : undefined;
264
- return { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, platformSuspendArmed, durableApproval, wiringManifest, writeProtectionCheck: writeProtection?.check, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound };
266
+ return { onAsk, preToolContexts, blockedToolCalls, gateOutcomes, batchHaltRef, blockedTracked, incompleteSuspendAdapter, durableSuspendInfraReady, resourceSuspendEligible, usageGovernance, platformSuspendArmed, durableApproval, wiringManifest, writeProtection, parkLaneArmed, gateMachineryActive, hookIdentity, hookEnvFace, sessionBoxBound };
265
267
  }
@@ -25,7 +25,16 @@ export declare function rootAdmittingArtifactStore(storePhysical: string, rootsC
25
25
  * reader of the hosting seat) once the declaration validates. */
26
26
  export interface ArtifactStoreRef {
27
27
  current: string | undefined;
28
+ /** The person's STANDING read grants — their persisted `Read(//dir/**)` rows, joined to the live read set at prepare —
29
+ * held to the in-flight grant's store rule by {@link withdrawStandingGrantsAdmitting} once the store is known. */
30
+ standing?: {
31
+ readonly keys: readonly string[];
32
+ readonly readRoots: string[];
33
+ };
28
34
  }
35
+ /** The store rule applied to the standing grants: each one that admits `storePhysical` leaves the live read set (so no
36
+ * read face reaches the store through it); returns the withdrawn keys, for the caller to announce. */
37
+ export declare function withdrawStandingGrantsAdmitting(storePhysical: string, ref: ArtifactStoreRef): string[];
29
38
  /** Why the run's station applied no grant: the spelling did not resolve (or its lookup was cut), it no longer
30
39
  * resolves to itself (a directory swapped for a link while the card waited), or it would admit the local artifact
31
40
  * store (the store-isolation rule the Artifact phase judges its mount by). */
@@ -7,6 +7,18 @@ export function rootAdmittingArtifactStore(storePhysical, rootsCanonical) {
7
7
  const storeFamily = pathFamilyOf({ root: storePhysical });
8
8
  return rootsCanonical.find((root) => pathFamilyOf({ root }) === storeFamily && (withinAnyRoot([root], storePhysical) || withinAnyRoot([storePhysical], root)));
9
9
  }
10
+ export function withdrawStandingGrantsAdmitting(storePhysical, ref) {
11
+ const standing = ref.standing;
12
+ if (standing === undefined)
13
+ return [];
14
+ const withdrawn = standing.keys.filter((key) => rootAdmittingArtifactStore(storePhysical, [key]) !== undefined);
15
+ for (const key of withdrawn) {
16
+ const at = standing.readRoots.indexOf(key);
17
+ if (at >= 0)
18
+ standing.readRoots.splice(at, 1);
19
+ }
20
+ return withdrawn;
21
+ }
10
22
  export function readRootGrantOver(seats) {
11
23
  const { env, writeRoots, readRoots, artifactStore } = seats;
12
24
  return async (candidate, signal) => {
@@ -2,6 +2,7 @@ import { assertNoRetiredKeys } from "../retired-keys.js";
2
2
  const RETIRED_RUNNER_DEPS_KEYS = {
3
3
  legacyRewindBoundaryProbe: "the rewind refusal always names both possibilities (never existed / retired whole-tree era); a deployment that holds that era's table answers the question on its own surface",
4
4
  peerAdmission: "the peer lane's knob table is `RunnerDeps.peerLane` (the same fields: admission gate, delivery budgets, listing window)",
5
+ graderEnvFactory: "the grader env reaches the repair loop as `RepairLoopConfig.graderEnv`, minted by the trusted control plane (`assertOracleIsolation` / `GraderEnv` unchanged)",
5
6
  };
6
7
  export function assertNoRetiredRunnerDeps(deps) {
7
8
  assertNoRetiredKeys(deps, RETIRED_RUNNER_DEPS_KEYS, "RunnerDeps");
@@ -111,7 +111,7 @@ export function runIdentityWiring(input) {
111
111
  }
112
112
  }
113
113
  const loopLatch = { ended: false, userInterrupted: false, userHalted: false };
114
- onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, runId: prepared.runId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity });
114
+ onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark, sessionId: prepared.sessionId, runId: prepared.runId, hookTimeoutMs: prepared.hookTimeoutMs, hookIdentity: prepared.hookIdentity, permissionMode: prepared.permissionMode });
115
115
  const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
116
116
  return { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, internalsNotifier, undrainedUserAtEnd: { get current() { return undrainedUserAtEnd; } }, userInputLostAtEnd: { get current() { return userInputLostAtEnd; } } };
117
117
  }
@@ -683,22 +683,30 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
683
683
  }
684
684
  if (prepared.persistedRuleLane !== undefined) {
685
685
  const unreadable = { unreadable: true };
686
- const read = await settleOrgVerdictWithin(Promise.resolve()
687
- .then(() => prepared.persistedRuleLane.adjudicate({
688
- toolName: pendingAction.toolName,
689
- args: resolvedArgs,
690
- toolCallId: pendingAction.toolCallId,
691
- ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)),
692
- budget: beltBudget,
693
- ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
694
- ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs),
695
- }))
696
- .then((answer) => normalizePersistedRuleHit(answer)), unreadable, { signal: prepared.abortController.signal, timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
686
+ let laneWaitCancelled = false;
687
+ let laneRead;
688
+ try {
689
+ laneRead = Promise.resolve(prepared.persistedRuleLane.adjudicate({
690
+ toolName: pendingAction.toolName,
691
+ args: resolvedArgs,
692
+ toolCallId: pendingAction.toolCallId,
693
+ ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)),
694
+ budget: beltBudget,
695
+ ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}),
696
+ ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs),
697
+ })).then((answer) => normalizePersistedRuleHit(answer));
698
+ }
699
+ catch {
700
+ laneRead = Promise.resolve(unreadable);
701
+ }
702
+ const read = await settleOrgVerdictWithin(laneRead, unreadable, { signal: prepared.abortController.signal, timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS, onFallback: (cause) => void (laneWaitCancelled = cause === "aborted") });
697
703
  const tightened = applyPersistedTightening({ action: "allow" }, read);
698
- if (tightened.tightened === "deny" || tightened.tightened === "unreadable") {
699
- const why = tightened.tightened === "deny"
700
- ? `a standing deny rule of yours (${disclosedRuleSet(read.hit?.rules ?? [])}) now refuses it`
701
- : "your persisted permission rules could not be read on this leg";
704
+ if (laneWaitCancelled || tightened.tightened === "deny" || tightened.tightened === "unreadable") {
705
+ const why = laneWaitCancelled
706
+ ? "the task was cancelled before your persisted permission rules could be read"
707
+ : tightened.tightened === "deny"
708
+ ? `a standing deny rule of yours (${disclosedRuleSet(read.hit?.rules ?? [])}) now refuses it`
709
+ : `it could not be read against your standing deny/ask rules (${read.reason})`;
702
710
  const ruleDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${why}. This approval is spent — the call has to be re-issued and approved again.`, prepared.reminderMark);
703
711
  await settleEnd(true, { content: ruleDenial }, vetoed("persisted_rule"));
704
712
  const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, ruleDenial, true));
@@ -729,6 +737,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
729
737
  emitCommitted(eid, "toolResult", pendingAction.toolCallId);
730
738
  return;
731
739
  }
740
+ verdict.admit?.();
732
741
  }
733
742
  }
734
743
  const tool = prepared.tools.find((t) => t.name === pendingAction.toolName);
@@ -14,6 +14,7 @@ async function runNotificationLaneAs(input, obligations) {
14
14
  leafId: resume.cp.leafId,
15
15
  suspendedBatch: new Set(resume.cp.pendingAction.kind === "tool_approval" ? resume.cp.pendingAction.batchToolCallIds : []),
16
16
  seed: resume.cp.state,
17
+ outcome: resume.outcome,
17
18
  priorSuspendCount: resume.cp.suspendCount,
18
19
  priorLedger: resume.cp.resourceLedger,
19
20
  priorHumanReview: resume.cp.humanReview,
@@ -6,8 +6,8 @@ import type { Prepared, ResumeRun, RunnerDepsSeat, RunState, TaskIdRef } from ".
6
6
  export interface RunTelemetryAndBudgetSeatsInput {
7
7
  /** borrowed-readonly — the task spec: limits, degrade, tracer, output retries, the resource-suspend totals. */
8
8
  spec: TaskSpec;
9
- /** borrowed-mutable — the leg's prepared seat: `liveSpendRef.get` is installed, `humanReviewRef` and `planModeRef`
10
- * are written on a resume; the model, the ledger, the session id and the usage governance are read. */
9
+ /** borrowed-mutable — the leg's prepared seat: `liveSpendRef.get` is installed, `humanReviewRef` is written and the
10
+ * mode seat steered on a resume; the model, the ledger, the session id and the usage governance are read. */
11
11
  prepared: Prepared;
12
12
  /** borrowed-readonly — the resume plan when this leg resumes a checkpoint (the human-review latency, the plan re-arm). */
13
13
  resume: ResumeRun | undefined;
@@ -33,7 +33,7 @@ export function runTelemetryAndBudgetSeats(input) {
33
33
  });
34
34
  }
35
35
  if (resume !== undefined && resume.outcome.gate === "plan_review" && resume.outcome.decision === "reject") {
36
- prepared.planModeRef.active = true;
36
+ void prepared.permissionMode.steer("plan");
37
37
  }
38
38
  rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
39
39
  rs.telemetry.pricing = runner.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);