@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,8 +1,8 @@
1
1
  /**
2
2
  * PrepareTask's INHERITED-GATE phase, verbatim from the driver: the live/seed fold of the
3
- * chain this run inherits (ancestor rules rev-monotonic per (session, principal), shellGate by max rank, the
4
- * re-supplied parent constraints with their rebuilt denial trackers, the auto-mode intent, the org-admission
5
- * freeze with its tighten-only intersection and the monotonic governed bit), the effective shellGate against the
3
+ * chain this run inherits (ancestor rules rev-monotonic per (session, principal), the shell floor, the
4
+ * re-supplied parent constraints with their rebuilt denial trackers, the permission mode's live seat, the org-admission
5
+ * freeze with its tighten-only intersection and the monotonic governed bit), the effective shell floor 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
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
@@ -44,9 +44,10 @@ import type { DelegationContentSafety } from "../memory-engine/delegation-proven
44
44
  import { type SessionPermissionRules } from "../session-policy-store.js";
45
45
  import type { SessionStore } from "../session.js";
46
46
  import { type OnAsk } from "../tool-policy.js";
47
- import type { ApproverPosture, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext, ToolSpec } from "../types.js";
47
+ import type { PermissionMode, RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskEvent, TaskSpec, ToolExecuteContext, ToolSpec } from "../types.js";
48
+ import { PermissionModeController } from "../mode-policy.js";
48
49
  import type { ActiveSkillScope } from "./active-skill-scope.js";
49
- import type { InheritedGate, Prepared, PrepareResume, RunInternals, StopForDenialLimit, ToolFaceSnapshot } from "./contracts.js";
50
+ import type { InheritedGate, Prepared, PrepareResume, RunInternals, StopForDenialLimit, ToolFaceSnapshot, WriteFaceSeat } from "./contracts.js";
50
51
  export interface PrepareInheritedGateInput {
51
52
  /** borrowed-readonly — the REBOUND spec. Read: `shellGate` (the own doctrine the chain fold tightens), `durableApproval`
52
53
  * (the mandate tests + the children's copy), `onAsk`/`onQuestion` (the frozen seats), `handsReadOnly` / `interactiveTools`
@@ -68,7 +69,7 @@ export interface PrepareInheritedGateInput {
68
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
- resume: Pick<PrepareResume, "seed"> | undefined;
72
+ resume: Pick<PrepareResume, "seed" | "outcome"> | undefined;
72
73
  /** borrowed-readonly — the session store's fork face: `fork` / `release` are bound into the ctx's `hostSessionFork` (present
73
74
  * only when the store can fork). */
74
75
  sessions: Pick<SessionStore, "fork" | "release">;
@@ -95,10 +96,9 @@ export interface PrepareInheritedGateInput {
95
96
  promptProfile: ToolExecuteContext["promptProfile"];
96
97
  /** borrowed-readonly — the resolved interaction posture (ctx seat; absent ⇒ no key). */
97
98
  resolvedInteractionPosture: ToolExecuteContext["interactionPosture"];
98
- /** borrowed-readonly — the task's own auto-mode intent seat, screened and snapshotted before the first await. */
99
- autoModeSeat: boolean;
100
- /** borrowed-readonly — the task's own approver-posture seat, screened and snapshotted before the first await. */
101
- approverPostureSeat: ApproverPosture | undefined;
99
+ /** borrowed-readonly — the task's own permission-mode key, screened and snapshotted before the first await (undefined =
100
+ * declared nothing: a root runs `default`, a child inherits). */
101
+ permissionModeSeat: PermissionMode | undefined;
102
102
  /** borrowed-readonly — whether an execution env is wired at all (the full-shell predicate's first term). */
103
103
  handsEnabled: boolean;
104
104
  /** borrowed-readonly — the nested-usage sink the ctx forwards. */
@@ -121,8 +121,8 @@ export interface PrepareInheritedGateInput {
121
121
  }) => void;
122
122
  /** borrowed-readonly — see `requestReview`. */
123
123
  requestStopAfterTurn: () => void;
124
- /** borrowed-readonly — see `requestReview`. */
125
- enterPlanMode: () => void;
124
+ /** borrowed-readonly — see `requestReview`; a steer of the leg's mode seat into `plan`. */
125
+ enterPlanMode: () => Promise<void>;
126
126
  /** borrowed-readonly — the leg's reminder mark (ctx seat). */
127
127
  reminderMark: string;
128
128
  /** borrowed-readonly — the leg's disclosure counters (ctx seat; the outlets that bump them are not in this phase). */
@@ -163,8 +163,8 @@ export interface PrepareInheritedGateInput {
163
163
  observersActive: () => boolean;
164
164
  /** borrowed-readonly — LATE-BOUND: the hands mount's resolved read face (undefined until/unless hands mount). */
165
165
  resolvedReadFace: () => ContainmentFace | undefined;
166
- /** borrowed-readonly — LATE-BOUND: the hands mount's resolved WRITE face (the read seat's twin). */
167
- resolvedWriteFace: () => ContainmentFace | undefined;
166
+ /** borrowed-readonly — LATE-BOUND: the hands mount's WRITE face seat (the read seat's twin; read per call, and whether its fence is declared). */
167
+ resolvedWriteFace: () => WriteFaceSeat | undefined;
168
168
  /** borrowed-readonly — LATE-BOUND: the normalized deny additions in force (empty until the hands mount resolves them). */
169
169
  readDenyAdditionsNormalized: () => ReadonlyArray<{
170
170
  pattern: string;
@@ -173,7 +173,7 @@ export interface PrepareInheritedGateInput {
173
173
  /** borrowed-readonly — LATE-BOUND: the hands-less legs' resolved face (the carrier's second source). */
174
174
  handsLessResolvedFace: () => ContainmentFace | undefined;
175
175
  /** borrowed-readonly — LATE-BOUND: the hands-less legs' resolved WRITE face (same second source, write seat). */
176
- handsLessResolvedWriteFace: () => ContainmentFace | undefined;
176
+ handsLessResolvedWriteFace: () => WriteFaceSeat | undefined;
177
177
  /** borrowed-readonly — LATE-BOUND: the adopted center artifact (ctx `centerArtifactDigest` / `centerSourceRevision`). */
178
178
  centerAdoption: () => {
179
179
  artifact: PromptEpochArtifact;
@@ -189,22 +189,22 @@ export interface PrepareInheritedGateResult {
189
189
  seedInheritedGate: PrepareResume["seed"]["inheritedGate"];
190
190
  /** owned — the rev-monotonic fold of the two halves' ancestor rules (live replaces per (session, principal) only at ≥ rev). */
191
191
  inheritedAncestorRules: InheritedGate["ancestorRules"];
192
- /** owned — the max-rank fold of the two halves' shellGate doctrines (before the spec's own is applied). */
192
+ /** owned — the chain's shell floor (live or seed; the seed's migrated word), before the spec's own is applied. */
193
193
  inheritedShellGate: InheritedGate["shellGate"];
194
194
  /** owned — the live parent constraints with a fresh denial tracker attached per rebuilt decider. */
195
195
  inheritedParentConstraints: NonNullable<InheritedGate["parentConstraints"]>[number][] | undefined;
196
- /** owned — the auto-mode INTENT (own seat ∨ live chain ∨ checkpoint bit); the arming site judges the other two arms. */
197
- autoModeIntent: boolean;
198
- /** owned — the resolved approver POSTURE (own seat, else the live chain's, else the checkpoint's); rendered, never enforced. */
199
- approverPosture: ApproverPosture | undefined;
196
+ /** owned — the leg's live permission-mode seat: the spec's request clamped under the chain's ceiling (a parent's live
197
+ * seat, or the checkpoint's recorded word), folded against the arming facts the caps phase binds later. Every mode
198
+ * consumer reads `current` at its own call; the steer verb writes it. */
199
+ permissionMode: PermissionModeController;
200
200
  /** owned — the seed ∩ live admitted org scopes (one side absent ⇒ the other; both absent ⇒ undefined). */
201
201
  inheritedAdmittedOrgScopes: readonly string[] | undefined;
202
202
  /** owned — the prior own org verdict: checkpoint ∩ same-session ref (either alone stands; neither ⇒ undefined). */
203
203
  priorOwnOrgVerdict: OwnOrgAdmissionVerdict | undefined;
204
204
  /** owned — the monotonic governed-provenance bit for this leg. */
205
205
  orgGovernedProvenance: boolean;
206
- /** owned — the spec's own shellGate tightened by the inherited doctrine. */
207
- effectiveShellGate: ShellGateDoctrine;
206
+ /** owned — the operator's shell floor in force (the spec's, or the chain's); undefined = no floor. */
207
+ effectiveShellGate: ShellGateDoctrine | undefined;
208
208
  /** borrowed-mutable — THIS task's own session-rule snapshot for its children's chain. Writers (R-3): the policy-chain
209
209
  * station's session-policy read, once per leg, when a store is wired and the task carries its own sessionId. Read by
210
210
  * `inheritedGateForChildren` at spawn time. */
@@ -5,10 +5,13 @@ import { createPreToolUseConstraintPolicy } from "../hooks.js";
5
5
  import { continuesLineage } from "../session-policy-store.js";
6
6
  import { StubExecutionEnv } from "../stub-env.js";
7
7
  import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
8
+ import { deliverEngineNotice } from "../types.js";
9
+ import { PermissionModeController, modeRequestAfterResume } from "../mode-policy.js";
10
+ import { migrateLegacyModeSeed } from "../checkpoint-store.js";
8
11
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
9
12
  import { asThrownError } from "../thrown-value.js";
10
13
  export function prepareInheritedGate(input) {
11
- const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, autoModeSeat, approverPostureSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
14
+ const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillScope, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
12
15
  const liveInheritedGate = internals?.inheritedGate;
13
16
  const seedInheritedGate = resume?.seed.inheritedGate;
14
17
  const inheritedAncestorRules = (() => {
@@ -28,19 +31,31 @@ export function prepareInheritedGate(input) {
28
31
  const extras = liveRules.filter((l) => !seedRules.some((s) => s.sessionId === l.sessionId && s.principal === l.principal));
29
32
  return [...merged, ...extras];
30
33
  })();
31
- const shellGateRank = { off: 0, classify: 1, always: 2 };
32
- const liveShellGate = liveInheritedGate?.shellGate;
33
- const seedShellGate = seedInheritedGate?.shellGate;
34
- const inheritedShellGate = liveShellGate === undefined
35
- ? seedShellGate
36
- : seedShellGate === undefined
37
- ? liveShellGate
38
- : shellGateRank[liveShellGate] >= shellGateRank[seedShellGate]
39
- ? liveShellGate
40
- : seedShellGate;
34
+ const modeSeed = migrateLegacyModeSeed(seedInheritedGate);
35
+ if (modeSeed.migrated !== undefined) {
36
+ deliverEngineNotice(deps.onNotice, {
37
+ code: "resume.legacy_mode_seed_migrated",
38
+ message: `the resumed checkpoint row carried pre-mode keys (${modeSeed.migrated.droppedKeys.join(", ")}); read once into ${modeSeed.migrated.permissionMode !== undefined ? `permissionMode: "${modeSeed.migrated.permissionMode}"` : "no mode of its own (the chain's ceiling stands)"}${modeSeed.shellGate !== undefined ? ' with the shell floor "always" kept' : ""} — the next suspend writes the one key; from 7.35.0 a row still carrying these keys is refused`,
39
+ detail: { migrated: { ...(modeSeed.migrated.permissionMode !== undefined ? { permissionMode: modeSeed.migrated.permissionMode } : {}), droppedKeys: [...modeSeed.migrated.droppedKeys] }, sessionId, runId },
40
+ });
41
+ }
42
+ const inheritedShellGate = liveInheritedGate?.shellGate ?? modeSeed.shellGate;
41
43
  const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
42
- const autoModeIntent = autoModeSeat || liveInheritedGate?.autoModeRequested === true || seedInheritedGate?.autoModeRequested === true;
43
- const approverPosture = [approverPostureSeat, liveInheritedGate?.approverPosture, seedInheritedGate?.approverPosture].find((w) => w !== undefined);
44
+ const parentSeat = liveInheritedGate?.permissionModeCeiling;
45
+ const ceiling = parentSeat ?? liveInheritedGate?.permissionMode;
46
+ const announceModeFold = (receipt) => {
47
+ if (receipt.foldedBy === undefined)
48
+ return;
49
+ const requested = receipt.requested ?? "default";
50
+ const because = receipt.foldedBy === "operator_deny" ? "the deployment denies it for this principal" : receipt.foldedBy === "face_absent" ? "no auto-mode classifier face is wired on this deployment" : `the parent leg's mode is the ceiling a child's declaration is clamped under`;
51
+ const message = `permission mode "${requested}" is not available on this leg (${because}) — it runs under "${receipt.effective}"`;
52
+ deliverEngineNotice(deps.onNotice, { code: "config.permission_mode_unavailable", message, detail: { requested, effective: receipt.effective, by: receipt.foldedBy, sessionId, runId } });
53
+ if (receipt.foldedBy === "inherited_ceiling" && parentSeat !== undefined) {
54
+ deliverEngineNotice(deps.onNotice, { code: "config.permission_mode_unavailable", message: `${message} (a child leg, ${sessionId})`, detail: { requested, effective: receipt.effective, by: receipt.foldedBy, sessionId: parentSeat.sessionId, childSessionId: sessionId, runId } });
55
+ }
56
+ };
57
+ const permissionMode = new PermissionModeController(modeRequestAfterResume(permissionModeSeat, modeSeed, resume?.outcome), ceiling, { onFold: announceModeFold, ...(modeSeed.prePlanMode !== undefined ? { prePlanMode: modeSeed.prePlanMode } : {}) });
58
+ announceModeFold(permissionMode.receipt());
44
59
  const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
45
60
  const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
46
61
  const inheritedAdmittedOrgScopes = liveAdmittedOrg === undefined ? seedAdmittedOrg : seedAdmittedOrg === undefined ? liveAdmittedOrg : liveAdmittedOrg.filter((s) => seedAdmittedOrg.includes(s));
@@ -64,8 +79,7 @@ export function prepareInheritedGate(input) {
64
79
  deps.compliancePostureResolver !== undefined ||
65
80
  inheritedOrgGoverned ||
66
81
  priorOwnOrgVerdict !== undefined;
67
- const specShellGate = spec.shellGate ?? "off";
68
- const effectiveShellGate = inheritedShellGate !== undefined && shellGateRank[inheritedShellGate] > shellGateRank[specShellGate] ? inheritedShellGate : specShellGate;
82
+ const effectiveShellGate = spec.shellGate ?? inheritedShellGate;
69
83
  const ownSessionRulesRef = {};
70
84
  const ancestorSessionRulesRef = { current: new Map() };
71
85
  const ancestorRulesNow = () => inheritedAncestorRules === undefined ? undefined : inheritedAncestorRules.map((a, i) => ancestorSessionRulesRef.current.get(i) ?? a);
@@ -92,8 +106,8 @@ export function prepareInheritedGate(input) {
92
106
  };
93
107
  const faceCheckpointState = () => faceCheckpointSection() !== undefined;
94
108
  const writeFaceCheckpointSection = () => {
95
- const face = input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace();
96
- return face === "open" ? { face } : undefined;
109
+ const seat = input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace();
110
+ return seat === undefined ? undefined : seat.inForce() === "open" ? { face: "open" } : seat.fenced ? { face: "roots" } : undefined;
97
111
  };
98
112
  const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
99
113
  seedInheritedGate?.constraintChain !== undefined ||
@@ -114,7 +128,7 @@ export function prepareInheritedGate(input) {
114
128
  const delegationSettlementRef = {};
115
129
  const delegationSettlementForChildren = () => delegationSettlementRef.current;
116
130
  const inheritedGateForChildren = () => {
117
- const autoModeDecider = input.autoModeDecider();
131
+ const autoModeDecider = permissionMode.current === "auto" ? input.autoModeDecider() : undefined;
118
132
  const autoModeDenialTracking = input.autoModeDenialTracking();
119
133
  const autoModeArming = input.autoModeArming();
120
134
  const ancestorRules = [
@@ -166,16 +180,16 @@ export function prepareInheritedGate(input) {
166
180
  ];
167
181
  return {
168
182
  ...(ancestorRules.length > 0 ? { ancestorRules } : {}),
169
- ...(effectiveShellGate !== "off" ? { shellGate: effectiveShellGate } : {}),
170
- ...(autoModeIntent ? { autoModeRequested: true } : {}),
171
- ...(approverPosture !== undefined ? { approverPosture } : {}),
183
+ ...(effectiveShellGate !== undefined ? { shellGate: effectiveShellGate } : {}),
184
+ permissionMode: permissionMode.current,
185
+ permissionModeCeiling: { get current() { return permissionMode.current; }, sessionId },
172
186
  ...(parentConstraints.length > 0 ? { parentConstraints } : {}),
173
187
  admittedOrgScopes: memoryAdmittedOrgScopesRef.current,
174
188
  ...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
175
189
  };
176
190
  };
177
191
  const carrierReadFace = () => input.resolvedReadFace() ?? input.handsLessResolvedFace();
178
- const carrierWriteFace = () => input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace();
192
+ const carrierWriteFace = () => (input.resolvedWriteFace() ?? input.handsLessResolvedWriteFace())?.inForce();
179
193
  const fullShellReachable = handsEnabled &&
180
194
  !(executionEnv instanceof StubExecutionEnv) &&
181
195
  spec.handsReadOnly !== true &&
@@ -281,5 +295,5 @@ export function prepareInheritedGate(input) {
281
295
  ...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
282
296
  };
283
297
  };
284
- return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, autoModeIntent, approverPosture, 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 };
298
+ return { 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 };
285
299
  }
@@ -114,8 +114,8 @@ export interface PrepareParkAskInput {
114
114
  /** borrowed-readonly — the shell-seat provenance set: the shell-running tools whose tier is the engine's shell seat's, not a
115
115
  * deployment mark (the risk descriptor's `shellGated` is the gated tool's membership). */
116
116
  shellGatedTools: ReadonlySet<string>;
117
- /** borrowed-readonly — the effective shell-gate doctrine persisted on the descriptor. */
118
- effectiveShellGate: ShellGateDoctrine;
117
+ /** borrowed-readonly — the operator's shell floor (`"always"`), persisted on a shell-gated descriptor; undefined = no floor. */
118
+ effectiveShellGate: ShellGateDoctrine | undefined;
119
119
  /** borrowed-readonly — the EFFECTIVE durable-approval opt-in (the caller's, or the synthesized mandate scope): picks the
120
120
  * gate kind, the scope and the ttl. */
121
121
  durableApproval: TaskSpec["durableApproval"];
@@ -242,7 +242,7 @@ export function prepareParkAsk(input) {
242
242
  ...(probeReason !== undefined ? { probeReason } : {}),
243
243
  ...(probeCause !== undefined ? { probeCause } : {}),
244
244
  shellGated: shellGatedTools.has(req.toolName),
245
- shellGateDoctrine: effectiveShellGate,
245
+ ...(effectiveShellGate !== undefined ? { shellGateDoctrine: effectiveShellGate } : {}),
246
246
  });
247
247
  gate =
248
248
  safety !== undefined || rowRealApproval !== undefined
@@ -281,6 +281,7 @@ export function prepareParkAsk(input) {
281
281
  const clearanceFacts = {
282
282
  ...(carry?.mandated === true || storedShapeMandated ? { mandated: true } : {}),
283
283
  ...(carry?.mandate !== undefined && !storedShapeMandated ? { mandate: carry.mandate } : {}),
284
+ ...(carry?.authoredBy === "deployment" ? { authoredBy: "deployment" } : {}),
284
285
  ...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}),
285
286
  ...(shadowedRule !== undefined ? { persistedRuleShadowed: shadowedRule } : {}),
286
287
  ...(askDecisionReason !== undefined ? { decisionReason: askDecisionReason } : {}),
@@ -32,6 +32,8 @@ import type { CwdRef } from "../../tools/fs/fs-shared.js";
32
32
  import type { ReadDenyMatcher } from "../../tools/fs/read-deny.js";
33
33
  import { type ActiveSkillScope } from "./active-skill-scope.js";
34
34
  import type { InheritedGate, IrreversibilityTier, PrepareResume, RunInternals, StopForDenialLimit } from "./contracts.js";
35
+ import { type Arbitration } from "../ask-origin.js";
36
+ import { type ModeGateSeats, type PermissionModeController } from "../mode-policy.js";
35
37
  import { createRuleOffersOf, type PermissionRuleLanes } from "./permission-rule-lanes.js";
36
38
  export interface PreparePolicyChainInput {
37
39
  /** borrowed-readonly — the REBOUND spec. Read: `sessionId` / `principal` (the session-rule read and the lanes' identity),
@@ -54,6 +56,9 @@ export interface PreparePolicyChainInput {
54
56
  hostTaskId: string;
55
57
  /** borrowed-readonly — the FINAL task root (path rules resolve against it; the lanes' and the offers' root). */
56
58
  taskRootFinal: string;
59
+ /** borrowed-readonly — the leg's canonical write roots beyond the task root (`additionalDirectories` + advertised writable
60
+ * dirs): the `acceptEdits` mode gate allows writes inside the root and these, and asks outside them all. */
61
+ additionalRootsCanonical: readonly string[];
57
62
  /** borrowed-readonly — the run's resolved env (path rules resolve in it; the sandbox declaration is read off it). */
58
63
  executionEnv: ExecutionEnv;
59
64
  /** borrowed-readonly — the frozen preflight: `toolPolicy` (the caller slot) and `basePolicyForResumeEdit` (the lock-aware
@@ -83,6 +88,8 @@ export interface PreparePolicyChainInput {
83
88
  handsCwdRef: CwdRef | undefined;
84
89
  /** borrowed-readonly — the compiled read-deny judge, or undefined (the offer factory's directory negative control). */
85
90
  readDenyMatcher: ReadDenyMatcher | undefined;
91
+ /** borrowed-readonly — the run's one notice for a person's rule store that could not be read (`ruleStoreUnreadableNotice`). */
92
+ announceRuleStoreSkipped: (at: "prepare" | "call") => void;
86
93
  /** borrowed-readonly — whether the question tool mounted (the lanes' question-tool exclusion). */
87
94
  questionToolMounted: boolean;
88
95
  /** borrowed-readonly — the resolved per-principal entitlements; only `forceDurableGate` (the mandate-park facility and
@@ -117,8 +124,15 @@ export interface PreparePolicyChainInput {
117
124
  hookEnvSource: unknown;
118
125
  /** borrowed-readonly — the own-hook crash reporter (the timeout seat's bad-value disclosure). */
119
126
  notifyOwnHookCrash: (err: unknown) => void;
120
- /** borrowed-readonly — this task's own classifier instance, or undefined (the pass-through test's decider term). */
121
- autoModeDecider: AutoModeDecider | undefined;
127
+ /** borrowed-readonly — LATE-BOUND: this task's own classifier instance (the pass-through test's decider term), read at chain
128
+ * assembly (the arming runs after this phase, and again on a steer into `auto`). */
129
+ autoModeDecider: () => AutoModeDecider | undefined;
130
+ /** borrowed-readonly — the leg's live permission-mode seat: the mode gate policy layer and the inherited stations' arbitration
131
+ * read `current` per call. */
132
+ permissionMode: PermissionModeController;
133
+ /** borrowed-readonly — the task's scratchpad directory (the mode write gate's one exemption), or undefined. */
134
+ scratchpadDir: string | undefined;
135
+ /** borrowed-readonly — the leg's live roster seat; the mode write gate reads `current` per call. */ toolRosterDeltas: NonNullable<ModeGateSeats["roster"]>;
122
136
  /** borrowed-readonly — the run's ONE typed stop for a denial-limit bound (the fallback settlements call it). */
123
137
  stopForDenialLimit: StopForDenialLimit;
124
138
  /** borrowed-readonly — the effective delegation facts; only `isDelegatedChild` (the ask source identity). */
@@ -161,6 +175,12 @@ export interface PreparePolicyChainResult {
161
175
  /** borrowed-mutable — the inherited-unavailable marker channel. Writers: the inherited arms here (mark); the gate station
162
176
  * (the lifecycle-end sweep) and the park lane's consumers (delete on consumption). */
163
177
  inheritedUnavailableAsks: Set<string>;
178
+ /** owned — the inherited stations' MODE ARBITRATIONS by tool call (a release or a refusal made inside the fold,
179
+ * where an ancestor's ask is arbitrated before its frozen approver). Writers: the three inherited stations here; the gate's
180
+ * exit reads it onto the disposition and the gate station deletes it at the call's end. */
181
+ ancestorArbitrations: Map<string, Exclude<Arbitration, {
182
+ kind: "ask";
183
+ }>>;
164
184
  /** borrowed-mutable — the grant record. Writers: the inherited arms here (record a clean allow at a live approver);
165
185
  * the gate station (consume on reuse, sweep at settlement). */
166
186
  inheritedAskGrants: Map<string, {
@@ -1,5 +1,6 @@
1
1
  import { policyAskClassOf } from "../ask-class.js";
2
2
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
3
+ import { classifierUnavailableSeat } from "./classifier-ladder.js";
3
4
  import { resolveCheckpointStore } from "../checkpoint-store.js";
4
5
  import { cloneObserverInput, resolveHookTimeoutMs } from "../hooks.js";
5
6
  import { assertHookSeatsCallable } from "../hook-seats.js";
@@ -10,16 +11,27 @@ import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
10
11
  import { NAMESPACED_NAME_SHAPES, protocolOf } from "../protocol-table.js";
11
12
  import { emitTrace } from "../trace.js";
12
13
  import { createActiveSkillScopePolicy } from "./active-skill-scope.js";
13
- import { askMandateMember, probeMandatedAsk } from "../ask-origin.js";
14
+ import { arbitrateByMode, askMandateMember, classifyAsk } from "../ask-origin.js";
15
+ import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js";
14
16
  const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true });
15
- import { headlessDenyAtFold, headlessDenyAtRecheck, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
17
+ import { headlessDenyAtFold, headlessDenyAtRecheck, INHERITED_STATION_FACTS, inheritedAskCarry, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
16
18
  import { settleReadRootGrant } from "./read-root-grant.js";
17
19
  import { askGrantShapeOf } from "./inherited-ask-grants.js";
18
20
  import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
19
21
  import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-policy.js";
20
22
  import { thrownText } from "../thrown-value.js";
21
23
  export async function preparePolicyChain(input) {
22
- const { 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 } = input;
24
+ const { 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, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
25
+ const ancestorArbitrations = new Map();
26
+ const arbitrateInherited = (ask, toolCallId) => {
27
+ const verdict = arbitrateByMode(permissionMode.current, classifyAsk(ask, INHERITED_STATION_FACTS), { ...(ask.origin !== undefined ? { origin: ask.origin } : {}), ...(ask.mandate !== undefined ? { mandate: ask.mandate } : {}) });
28
+ if (verdict.kind === "ask" || verdict.kind === "accepted")
29
+ return undefined;
30
+ ancestorArbitrations.set(toolCallId, verdict);
31
+ if (verdict.kind === "released")
32
+ return { action: "allow", message: `released by the ${verdict.mode} permission mode at an inherited ancestor layer (the ${verdict.askClass} ask was not put to anyone)`, decisionReason: "mode", ...(ask.updatedInput !== undefined ? { updatedInput: ask.updatedInput } : {}) };
33
+ return { action: "deny", message: `refused by the ${verdict.mode} permission mode at an inherited ancestor layer (${verdict.askClass})`, decisionReason: "mode" };
34
+ };
23
35
  const policy = lockedPreflight.toolPolicy;
24
36
  const auditPolicyNames = (auditee) => {
25
37
  const nameGroups = toolPolicyNameSets(auditee);
@@ -288,6 +300,8 @@ export async function preparePolicyChain(input) {
288
300
  questionToolName: ASK_USER_QUESTION_TOOL_NAME,
289
301
  onRevisionDefect: (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }),
290
302
  onDisclosure: (message) => emitTrace(deps.tracer, () => ({ kind: "permission.rule_store_unreadable", version: 1, taskId: hostTaskId, message, ts: Date.now() })),
303
+ onStoreUnreadable: announceRuleStoreSkipped,
304
+ signal: abortController.signal,
291
305
  });
292
306
  const ruleOffersOf = createRuleOffersOf({
293
307
  laneArmed: permissionRuleLane !== undefined,
@@ -336,7 +350,7 @@ export async function preparePolicyChain(input) {
336
350
  }
337
351
  if (re.updatedInput !== undefined)
338
352
  editArgs = re.updatedInput;
339
- const judged = await judgeInheritedClassifier({ autoMode: ancestorDecider !== undefined ? { decider: ancestorDecider, denialTracking: ancestorTracker } : undefined, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit", allowRewrite: editArgs });
353
+ const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: ancestorDecider !== undefined ? { decider: ancestorDecider, denialTracking: ancestorTracker } : undefined, ask: re, req: { ...creq, args: editArgs }, signal: csignal ?? abortController.signal, subject: "the approved edit", allowRewrite: editArgs, unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(ancestorDecider?.timeoutMs !== undefined ? { budgetMs: ancestorDecider.timeoutMs } : {}), approvalSeat: isLiveApproverSeat(onAskOf), onNotice: deps.onNotice, sessionId, runId }) });
340
354
  if (judged.kind === "allow") {
341
355
  const cleared = { ...judged.result };
342
356
  carryEngineAttestations(judged.result, cleared);
@@ -347,6 +361,12 @@ export async function preparePolicyChain(input) {
347
361
  return vetoed(judged.result);
348
362
  const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
349
363
  re = editAsk;
364
+ const editArbitrated = arbitrateInherited(re, creq.toolCallId);
365
+ if (editArbitrated !== undefined) {
366
+ const out = editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs } : editArbitrated;
367
+ carryPrecedingSettlement(settledBy, out);
368
+ return out;
369
+ }
350
370
  const rr = await resolveAsk({
351
371
  toolName: creq.toolName,
352
372
  toolCallId: creq.toolCallId,
@@ -381,7 +401,7 @@ export async function preparePolicyChain(input) {
381
401
  pc.contentMandate !== true &&
382
402
  askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
383
403
  pc.hookEnv === hookEnvSource &&
384
- pc.autoMode?.decider === autoModeDecider
404
+ pc.autoMode?.decider === autoModeDecider()
385
405
  ? { check: () => ({ action: "allow" }) }
386
406
  : policy !== undefined && pc.policy === policy
387
407
  ? {
@@ -412,16 +432,19 @@ export async function preparePolicyChain(input) {
412
432
  return first;
413
433
  return mandateUnreconstructibleDeny(creq);
414
434
  }
415
- const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
435
+ const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call", unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(pc.autoMode?.decider.timeoutMs !== undefined ? { budgetMs: pc.autoMode.decider.timeoutMs } : {}), approvalSeat: isLiveApproverSeat(pc.onAsk), onNotice: deps.onNotice, sessionId, runId }) });
416
436
  if (judged.kind === "allow")
417
437
  return judged.result;
418
438
  if (judged.kind === "deny")
419
439
  return judged.result;
420
440
  const { ask: inheritedAsk, fallback, mintedHere } = judged;
421
- if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
441
+ if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && classifyAsk(inheritedAsk, INHERITED_STATION_FACTS).row.sandboxAdmission) {
422
442
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
423
443
  return { action: "allow" };
424
444
  }
445
+ const arbitrated = arbitrateInherited(inheritedAsk, creq.toolCallId);
446
+ if (arbitrated !== undefined)
447
+ return arbitrated;
425
448
  const presentedArgs = creq.args;
426
449
  const askT0 = now();
427
450
  const resolved = await resolveAsk({
@@ -484,16 +507,19 @@ export async function preparePolicyChain(input) {
484
507
  return mandateUnreconstructibleDeny(creq);
485
508
  }
486
509
  const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
487
- const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call", allowRewrite: decision.updatedInput });
510
+ const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: pc.autoMode, ask: decision, req: { ...creq, args: presentedArgs }, signal: csignal ?? abortController.signal, subject: "this call", allowRewrite: decision.updatedInput, unavailableSeat: classifierUnavailableSeat({ station: "ancestor", ...(pc.autoMode?.decider.timeoutMs !== undefined ? { budgetMs: pc.autoMode.decider.timeoutMs } : {}), approvalSeat: isLiveApproverSeat(pc.onAsk), onNotice: deps.onNotice, sessionId, runId }) });
488
511
  if (judged.kind === "allow")
489
512
  return judged.result;
490
513
  if (judged.kind === "deny")
491
514
  return judged.result;
492
515
  const { ask: inheritedAsk, fallback, mintedHere } = judged;
493
- if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
516
+ if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && classifyAsk(inheritedAsk, INHERITED_STATION_FACTS).row.sandboxAdmission) {
494
517
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
495
518
  return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
496
519
  }
520
+ const arbitrated = arbitrateInherited(inheritedAsk.action === "ask" ? { ...inheritedAsk, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) } : inheritedAsk, creq.toolCallId);
521
+ if (arbitrated !== undefined)
522
+ return arbitrated;
497
523
  const askT0 = now();
498
524
  const resolved = await resolveAsk({
499
525
  toolName: creq.toolName,
@@ -555,6 +581,7 @@ export async function preparePolicyChain(input) {
555
581
  return d;
556
582
  },
557
583
  }));
584
+ const modeGateLayer = createModeGatePolicy(permissionMode, { env: executionEnv, rootPath: taskRootFinal, acceptDirs: [taskRootFinal, ...additionalRootsCanonical], roster: toolRosterDeltas, readOnlyDeclared: (name) => mountedToolDeclaresReadOnly(tools, name), ...(scratchpadDir !== undefined ? { exemptDirs: [scratchpadDir] } : {}) });
558
585
  const policyLayers = [
559
586
  ...narrowingLayers,
560
587
  ...instrumentedChain,
@@ -562,8 +589,8 @@ export async function preparePolicyChain(input) {
562
589
  ];
563
590
  const integrityGuardLayers = [createTranscriptIntegrityPolicy(), createUnverifiableDeletePolicy()];
564
591
  const guardedPolicyLayers = rewriteCapableLayers
565
- ? [...integrityGuardLayers, ...policyLayers, ...integrityGuardLayers]
566
- : [...integrityGuardLayers, ...policyLayers];
592
+ ? [...integrityGuardLayers, ...policyLayers, modeGateLayer, ...integrityGuardLayers]
593
+ : [...integrityGuardLayers, ...policyLayers, modeGateLayer];
567
594
  const foldedPolicy = guardedPolicyLayers.length === 1 ? guardedPolicyLayers[0] : combinePolicies(...guardedPolicyLayers);
568
595
  const effectivePolicy = foldedPolicy !== undefined && rewriteCapableLayers
569
596
  ? {
@@ -631,7 +658,7 @@ export async function preparePolicyChain(input) {
631
658
  },
632
659
  }
633
660
  : foldedPolicy;
634
- return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions };
661
+ return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
635
662
  }
636
663
  const advisedPolicyNames = new WeakMap();
637
664
  const ADVISED_KEYS_CAP = 64;
@@ -1,3 +1,28 @@
1
+ /**
2
+ * PrepareTask's PROTOCOL-TOOLS phase, verbatim from the driver: the MCP materialization
3
+ * with its operator face (fail-open warnings once per session, the intake drops, the announce-lane prune), the
4
+ * reserved-name refusal, the roster push through the large-result wrapper, the RefreshMcpTools seam with its
5
+ * refresh closure, the two run-time seats the rebuild closures reach (`rebuildHarnessToolsRef`,
6
+ * `contentOriginWrapRef`) and the gate-armed honesty flag, the protocol-SHARED axis fold, and the A2A twin
7
+ * (materialize, warnings straight to the sink, its own reserved-name refusal, the same fold). The interface
8
+ * is the dependency list the segment had implicitly.
9
+ *
10
+ * ASYNC PHASE: the segment awaits the two materializers, exactly where the driver awaited them; the phase adds
11
+ * no yield BEFORE the first of those awaits. Read-stability of the host handles for the call:
12
+ * {@link RunInternals} (`@contract prepare.deps-read-stable`).
13
+ *
14
+ * The handles and the rollback stack. This phase is the first that acquires a handle INSIDE
15
+ * its body: the driver used to see `mcp` / `a2a` only because they were hoisted `let`s at function scope, so
16
+ * its catch could dispose a half-acquired pair. Those hoists are gone. The moment a materializer returns a
17
+ * real handle the phase pushes its `dispose` onto the driver-owned `rollback`; a prepare that throws AFTER
18
+ * this phase reaches the driver's catch, whose `unwindAll` disposes the peers, then the clients — the reverse
19
+ * acquisition order the hoisted legs kept, each bounded and reported. The two reserved-name arms only THROW:
20
+ * the release of what they refused is the rollback entry, called exactly once by that unwind (they used to
21
+ * dispose inline as well — a hoist-era leftover that leaned on dispose being idempotent; the transcript pins
22
+ * the single call). On the success path the stack is committed and the handles belong to `Prepared` (the
23
+ * Runner's finish releases them).
24
+ */
25
+ import type { ToolMarkSource } from "../ask-origin.js";
1
26
  import type { AgentTool } from "../../internal/harness.js";
2
27
  import { type MaterializedMcp, type McpDroppedTool, type McpReconnectResult } from "../mcp.js";
3
28
  import { type MaterializedA2a } from "../a2a.js";
@@ -59,6 +84,9 @@ export interface PrepareProtocolToolsInput {
59
84
  irreversibleTools: Set<string>;
60
85
  /** borrowed-mutable — the egress set, added to by the fold (tighten-only; the effect:"write" invariant). */
61
86
  egressTools: Set<string>;
87
+ /** borrowed-mutable — WHO marked each tool (the safety scan's map); the protocol fold adds its own source word per
88
+ * axis it tightens (`mcp_annotation` for a server's self-report, `a2a_default` for a peer skill's egress default). */
89
+ toolMarkSources: Map<string, Set<ToolMarkSource>>;
62
90
  /** borrowed-readonly — the run's abort controller; its `signal` rides into the A2A materialization. */
63
91
  abortController: Pick<AbortController, "signal">;
64
92
  /** borrowed-mutable — the driver-owned prepare-failure stack; this phase pushes one entry per REAL handle
@@ -11,7 +11,14 @@ import { mintNamespacePrefix } from "../protocol-naming.js";
11
11
  import { applyMcpToolFaces } from "./tool-face-overlay.js";
12
12
  import { thrownText } from "../thrown-value.js";
13
13
  export async function prepareProtocolTools(input) {
14
- const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, abortController, rollback } = input;
14
+ const { lockedPreflight, spec, deps, reminderMark, reminderDisclosureCounts, runId, sessionId, onceLedger, roster, rosterSeat, remoteToolOffload, toolFaceSnapshot, toolEffects, axisExplicitNegatives, irreversibilityTier, irreversibleTools, egressTools, toolMarkSources, abortController, rollback } = input;
15
+ const markedBy = (name, source) => {
16
+ const set = toolMarkSources.get(name);
17
+ if (set !== undefined)
18
+ set.add(source);
19
+ else
20
+ toolMarkSources.set(name, new Set([source]));
21
+ };
15
22
  const overPeer = (lockedPreflight.mcp ?? []).find((s) => s.name.length > TOOL_WIRE_NAME_MAX_CHARS);
16
23
  if (overPeer !== undefined) {
17
24
  throw Object.assign(new Error(`MCP server name ${JSON.stringify(overPeer.name.slice(0, 40) + "…")} is ${overPeer.name.length} characters; a peer rides every roster row of its tools and the roster's bound on a name is ${TOOL_WIRE_NAME_MAX_CHARS}.`), { code: "config.tool_name_too_long" });
@@ -123,10 +130,12 @@ export async function prepareProtocolTools(input) {
123
130
  }, { source: "builtin", mountedBy: "optional" });
124
131
  }
125
132
  const foldProtocolAxes = (axes, protocolLabel) => {
133
+ const source = protocolLabel === "A2A" ? "a2a_default" : "mcp_annotation";
126
134
  for (const axis of axes) {
127
135
  if (axis.irreversibility === "always") {
128
136
  irreversibilityTier.set(axis.name, "always");
129
137
  irreversibleTools.add(axis.name);
138
+ markedBy(axis.name, source);
130
139
  }
131
140
  if (axis.irreversibility === "never") {
132
141
  axisExplicitNegatives.set(axis.name, { ...axisExplicitNegatives.get(axis.name), irreversible: false });
@@ -141,9 +150,10 @@ export async function prepareProtocolTools(input) {
141
150
  throw e;
142
151
  }
143
152
  egressTools.add(axis.name);
153
+ markedBy(axis.name, source);
144
154
  }
145
- if (!toolEffects.has(axis.name))
146
- toolEffects.set(axis.name, axis.effect ?? "write");
155
+ if (axis.effect !== undefined && !toolEffects.has(axis.name))
156
+ toolEffects.set(axis.name, axis.effect);
147
157
  }
148
158
  };
149
159
  foldProtocolAxes(mcp.toolAxes, "MCP");
@@ -21,6 +21,7 @@ import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
21
21
  import type { TaskEvent, TaskSpec } from "../types.js";
22
22
  import { ActiveSkillScope } from "./active-skill-scope.js";
23
23
  import type { Prepared, RunInternals, ToolProgressLane } from "./contracts.js";
24
+ import type { PermissionModeController } from "../mode-policy.js";
24
25
  import { type EditedFilesLedger } from "./edited-files-ledger.js";
25
26
  export interface PrepareRunRefsInput {
26
27
  /** borrowed-readonly — the REBOUND spec. Read: `forwardSubagentEvents` (widens the forwarding filter),
@@ -82,12 +83,11 @@ export interface PrepareRunRefsResult {
82
83
  stopRequestedRef: Prepared["stopRequestedRef"];
83
84
  /** owned — the ctx primitive: flips the latch and mirrors it onto the harness's one-way flag. */
84
85
  requestStopAfterTurn: () => void;
85
- /** borrowed-mutable — the plan-mode flag (`Prepared.planModeRef`). Writers: `enterPlanMode` here, and the run
86
- * loop's resume ladder, which re-arms it when a REJECTED `plan_review` resumes into a re-plan. Read by the
87
- * tool-call gate (the write-effect refusal). */
88
- planModeRef: Prepared["planModeRef"];
89
- /** owned — the ctx primitive (mounted on the ctx only under `enablePlanMode`). */
90
- enterPlanMode: () => void;
86
+ /** owned — binds the leg's permission-mode seat once the inherited-gate phase minted it; `enterPlanMode` steers it
87
+ * (the run loop's resume ladder steers the same seat when a REJECTED `plan_review` resumes into a re-plan). */
88
+ bindPermissionModeSeat: (seat: PermissionModeController) => void;
89
+ /** owned — the ctx primitive (mounted on the ctx only under `enablePlanMode`): a steer of the bound seat into `plan`. */
90
+ enterPlanMode: () => Promise<void>;
91
91
  /** ownership-ref — the parent-run subagent-retain ledger, or undefined when the spec did not opt in. Its
92
92
  * lifecycle protocol belongs to the Runner (disposed in the task's terminal finally); a
93
93
  * prepare that throws has nothing to release here — the ledger is empty until a delegation retains. */
@@ -37,11 +37,16 @@ export function prepareRunRefs(input) {
37
37
  stopRequestedRef.current = true;
38
38
  harnessRef.current?.requestStopAfterTurn();
39
39
  };
40
- const planModeRef = { active: false };
41
- const enterPlanMode = () => {
42
- planModeRef.active = true;
40
+ let permissionModeSeat;
41
+ const bindPermissionModeSeat = (seat) => {
42
+ permissionModeSeat = seat;
43
+ };
44
+ const enterPlanMode = async () => {
45
+ if (permissionModeSeat === undefined)
46
+ throw new Error("enterPlanMode: the leg's permission-mode seat is not bound yet");
47
+ await permissionModeSeat.steer("plan");
43
48
  };
44
49
  const subagentRetain = spec.retainSubagentSessions ? new SubagentRetainLedger(spec.retainSubagentSessions) : undefined;
45
50
  const worktreeIsolation = spec.handsReadOnly !== true ? createSubagentWorktreeHelper(executionEnv, taskRootFinal) : undefined;
46
- return { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, planModeRef, enterPlanMode, subagentRetain, worktreeIsolation };
51
+ return { noteFileEdited, editedFilesSnapshot, harnessRef, toolProgressRef, skillScope, forwardEvent, reviewRequestRef, requestReview, stopRequestedRef, requestStopAfterTurn, bindPermissionModeSeat, enterPlanMode, subagentRetain, worktreeIsolation };
47
52
  }