@sema-agent/core 7.33.8 → 7.35.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 (228) hide show
  1. package/CHANGELOG.md +462 -3
  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/errors.d.ts +23 -1
  11. package/dist/brain/errors.js +19 -2
  12. package/dist/brain/open-responses.js +1 -0
  13. package/dist/brain/openai.js +1 -0
  14. package/dist/brain/status-sink.d.ts +7 -1
  15. package/dist/brain/stream-engine.d.ts +3 -0
  16. package/dist/brain/stream-engine.js +2 -1
  17. package/dist/brain/stream-shared.d.ts +3 -1
  18. package/dist/brain/stream-shared.js +16 -4
  19. package/dist/core/a2a.d.ts +3 -1
  20. package/dist/core/a2a.js +1 -1
  21. package/dist/core/ask-origin.d.ts +379 -154
  22. package/dist/core/ask-origin.js +224 -45
  23. package/dist/core/ask-question.js +2 -1
  24. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  25. package/dist/core/ask-unresolvable-notice.js +4 -3
  26. package/dist/core/auto-mode-arming.d.ts +17 -4
  27. package/dist/core/auto-mode-arming.js +31 -6
  28. package/dist/core/auto-mode-defaults.d.ts +16 -7
  29. package/dist/core/auto-mode-defaults.js +2 -1
  30. package/dist/core/auto-mode-directive.d.ts +18 -30
  31. package/dist/core/auto-mode-directive.js +9 -18
  32. package/dist/core/auto-mode-prompt.d.ts +62 -1
  33. package/dist/core/auto-mode-prompt.js +42 -4
  34. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  35. package/dist/core/auto-mode-rebuild.js +1 -0
  36. package/dist/core/auto-mode.d.ts +32 -3
  37. package/dist/core/auto-mode.js +80 -41
  38. package/dist/core/checkpoint-store.d.ts +54 -35
  39. package/dist/core/checkpoint-store.js +58 -3
  40. package/dist/core/child-clamp-seats.js +1 -0
  41. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  42. package/dist/core/classifier-unavailable-arm.js +17 -0
  43. package/dist/core/effective-path-target.d.ts +12 -0
  44. package/dist/core/effective-path-target.js +18 -7
  45. package/dist/core/engine-notice.d.ts +53 -2
  46. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  47. package/dist/core/fs-write-gate-policy.js +4 -1
  48. package/dist/core/gate-fold.js +19 -3
  49. package/dist/core/gate-lanes.js +116 -50
  50. package/dist/core/gate-outcome.d.ts +40 -7
  51. package/dist/core/gate-outcome.js +26 -4
  52. package/dist/core/governance-codes.d.ts +18 -2
  53. package/dist/core/governance-codes.js +23 -1
  54. package/dist/core/hooks.d.ts +53 -29
  55. package/dist/core/hooks.js +1 -3
  56. package/dist/core/mcp.js +1 -1
  57. package/dist/core/message-utils.d.ts +2 -1
  58. package/dist/core/message-utils.js +2 -1
  59. package/dist/core/mode-policy.d.ts +185 -0
  60. package/dist/core/mode-policy.js +184 -0
  61. package/dist/core/oracle-isolation.d.ts +3 -3
  62. package/dist/core/permission-rule-model.d.ts +20 -3
  63. package/dist/core/permission-rule-model.js +40 -3
  64. package/dist/core/permission-rule-provider.d.ts +22 -11
  65. package/dist/core/permission-rule-provider.js +17 -7
  66. package/dist/core/permission-rules.d.ts +23 -1
  67. package/dist/core/permission-rules.js +56 -9
  68. package/dist/core/persisted-rule-arms.d.ts +14 -10
  69. package/dist/core/persisted-rule-arms.js +13 -6
  70. package/dist/core/present-plan-tool.d.ts +1 -1
  71. package/dist/core/present-plan-tool.js +3 -3
  72. package/dist/core/read-only-shell.d.ts +19 -19
  73. package/dist/core/read-only-shell.js +38 -12
  74. package/dist/core/retired-keys.d.ts +9 -3
  75. package/dist/core/retired-keys.js +5 -1
  76. package/dist/core/runner/active-skill-scope.js +5 -4
  77. package/dist/core/runner/assemble-result.js +2 -2
  78. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  79. package/dist/core/runner/classifier-ladder.js +29 -0
  80. package/dist/core/runner/contracts.d.ts +62 -38
  81. package/dist/core/runner/denial-limit-arms.d.ts +35 -7
  82. package/dist/core/runner/denial-limit-arms.js +15 -9
  83. package/dist/core/runner/env-capability-census.js +1 -1
  84. package/dist/core/runner/file-history-request.d.ts +26 -0
  85. package/dist/core/runner/file-history-request.js +90 -0
  86. package/dist/core/runner/gate-exit.d.ts +30 -4
  87. package/dist/core/runner/gate-exit.js +28 -7
  88. package/dist/core/runner/permission-rule-lanes.d.ts +11 -6
  89. package/dist/core/runner/permission-rule-lanes.js +55 -48
  90. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  91. package/dist/core/runner/persisted-path-rows.js +89 -0
  92. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  93. package/dist/core/runner/prepare-artifact.js +9 -3
  94. package/dist/core/runner/prepare-ask-lane.d.ts +6 -4
  95. package/dist/core/runner/prepare-ask-lane.js +3 -3
  96. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  97. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  98. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  99. package/dist/core/runner/prepare-file-history.js +4 -90
  100. package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
  101. package/dist/core/runner/prepare-gate-stations.js +46 -29
  102. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  103. package/dist/core/runner/prepare-hands-readface.js +64 -53
  104. package/dist/core/runner/prepare-inherited-gate.d.ts +30 -23
  105. package/dist/core/runner/prepare-inherited-gate.js +45 -25
  106. package/dist/core/runner/prepare-park-ask.d.ts +6 -4
  107. package/dist/core/runner/prepare-park-ask.js +7 -5
  108. package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
  109. package/dist/core/runner/prepare-policy-chain.js +203 -180
  110. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  111. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  112. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  113. package/dist/core/runner/prepare-run-refs.js +9 -4
  114. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  115. package/dist/core/runner/prepare-safety-scan.js +19 -3
  116. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  117. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  118. package/dist/core/runner/prepare-task.js +56 -24
  119. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  120. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  121. package/dist/core/runner/read-root-grant.d.ts +9 -0
  122. package/dist/core/runner/read-root-grant.js +12 -0
  123. package/dist/core/runner/retired-runner-deps.js +1 -0
  124. package/dist/core/runner/run-brain-sinks.js +3 -1
  125. package/dist/core/runner/run-identity-wiring.js +1 -1
  126. package/dist/core/runner/run-leg.js +29 -18
  127. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  128. package/dist/core/runner/run-notification-lane.js +3 -1
  129. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  130. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  131. package/dist/core/runner/run-turn-boundary.js +1 -1
  132. package/dist/core/runner/runtask.d.ts +1 -0
  133. package/dist/core/runner/runtask.js +8 -11
  134. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  135. package/dist/core/runner/session-mode-seats.js +73 -0
  136. package/dist/core/runner/session-rule-policy.js +3 -2
  137. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  138. package/dist/core/runner/stream-mode-verb.js +19 -0
  139. package/dist/core/runner/turn-attachments.d.ts +3 -3
  140. package/dist/core/runner/turn-attachments.js +1 -1
  141. package/dist/core/runner/write-protection-station.d.ts +7 -1
  142. package/dist/core/runner/write-protection-station.js +6 -3
  143. package/dist/core/runner-deps.d.ts +31 -34
  144. package/dist/core/runtime-caps.d.ts +12 -3
  145. package/dist/core/safety-axis-vocab.d.ts +2 -2
  146. package/dist/core/safety-axis-vocab.js +1 -1
  147. package/dist/core/secret-env.d.ts +30 -58
  148. package/dist/core/secret-env.js +149 -586
  149. package/dist/core/sensitive-path-policy.d.ts +4 -2
  150. package/dist/core/sensitive-path-policy.js +41 -31
  151. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  152. package/dist/core/task-event.d.ts +1 -1
  153. package/dist/core/task-result.d.ts +1 -1
  154. package/dist/core/task-spec.d.ts +41 -89
  155. package/dist/core/task-stream.d.ts +16 -0
  156. package/dist/core/terminal-cause.js +1 -0
  157. package/dist/core/tighten-task-spec.d.ts +2 -2
  158. package/dist/core/tighten-task-spec.js +12 -14
  159. package/dist/core/tool-catalog-entries.js +4 -3
  160. package/dist/core/tool-policy.d.ts +84 -30
  161. package/dist/core/tool-policy.js +76 -16
  162. package/dist/core/tool-registry.d.ts +38 -0
  163. package/dist/core/tool-registry.js +25 -1
  164. package/dist/core/tool-roster.d.ts +17 -2
  165. package/dist/core/tool-roster.js +11 -1
  166. package/dist/core/tool-spec.d.ts +8 -9
  167. package/dist/core/trace.d.ts +18 -6
  168. package/dist/core/types.d.ts +2 -1
  169. package/dist/core/wiring-manifest.d.ts +31 -5
  170. package/dist/core/wiring-manifest.js +28 -1
  171. package/dist/core/write-protect.d.ts +11 -1
  172. package/dist/core/write-protect.js +26 -15
  173. package/dist/engine/execution-env/node-execution-env.d.ts +6 -1
  174. package/dist/engine/execution-env/node-execution-env.js +16 -3
  175. package/dist/engine/harness/agent-harness.js +2 -1
  176. package/dist/engine/harness/types.d.ts +6 -7
  177. package/dist/engine/llm/types.d.ts +2 -4
  178. package/dist/engine/loop/types.d.ts +7 -0
  179. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  180. package/dist/index.d.ts +18 -8
  181. package/dist/index.js +15 -6
  182. package/dist/orchestration/workflow-governance.js +1 -1
  183. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  184. package/dist/orchestration/workflow-size-guideline.js +2 -2
  185. package/dist/stores/file/index.d.ts +5 -3
  186. package/dist/stores/file/session-store.d.ts +8 -2
  187. package/dist/stores/file/session-store.js +5 -5
  188. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  189. package/dist/tools/artifact/artifact-tool.js +16 -1
  190. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  191. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  192. package/dist/tools/fs/containment-face.d.ts +9 -3
  193. package/dist/tools/fs/containment-face.js +2 -0
  194. package/dist/tools/fs/file-read-check.d.ts +50 -0
  195. package/dist/tools/fs/file-read-check.js +79 -0
  196. package/dist/tools/fs/fs-bash.d.ts +3 -3
  197. package/dist/tools/fs/fs-read.d.ts +1 -0
  198. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  199. package/dist/tools/fs/fs-search-tools.js +4 -4
  200. package/dist/tools/fs/fs-shared.js +20 -6
  201. package/dist/tools/fs/fs-write.d.ts +4 -3
  202. package/dist/tools/fs/fs-write.js +9 -9
  203. package/dist/tools/fs/index.d.ts +39 -3
  204. package/dist/tools/fs/index.js +38 -15
  205. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  206. package/dist/tools/fs/read-deny-home.js +225 -0
  207. package/dist/tools/fs/read-deny.d.ts +176 -34
  208. package/dist/tools/fs/read-deny.js +268 -61
  209. package/dist/tools/fs/safety.d.ts +6 -1
  210. package/dist/tools/fs/safety.js +18 -5
  211. package/dist/tools/fs/search.d.ts +3 -0
  212. package/dist/tools/fs/search.js +47 -9
  213. package/dist/tools/fs/shell-read-check.d.ts +52 -13
  214. package/dist/tools/fs/shell-read-check.js +199 -20
  215. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  216. package/dist/tools/fs/shell-read-establish.js +134 -21
  217. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  218. package/dist/tools/fs/shell-read-identity.js +21 -0
  219. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  220. package/dist/tools/fs/shell-read-judge.js +43 -18
  221. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  222. package/dist/tools/fs/shell-read-plan.js +26 -4
  223. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  224. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  225. package/package.json +1 -1
  226. package/test/export-surface.snapshot.json +117 -11
  227. package/wire-changes/core@7.34.0.json +2621 -0
  228. package/wire-changes/core@7.35.0.json +584 -0
@@ -1,25 +1,38 @@
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";
6
7
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
7
8
  import { isIsolated } from "../remote-env.js";
8
- import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, resolveAsk, toolPolicyNameSets, attestAskUnresolvableCause } from "../tool-policy.js";
9
+ import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, resolveAsk, toolPolicyNameSets, wrapToolPolicy, attestAskUnresolvableCause } from "../tool-policy.js";
9
10
  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";
23
+ import { anyNamed, firstNamed, ruleNamesOf } from "../tool-roster.js";
21
24
  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;
25
+ 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;
26
+ const ancestorArbitrations = new Map();
27
+ const arbitrateInherited = (ask, toolCallId) => {
28
+ const verdict = arbitrateByMode(permissionMode.current, classifyAsk(ask, INHERITED_STATION_FACTS), { ...(ask.origin !== undefined ? { origin: ask.origin } : {}), ...(ask.mandate !== undefined ? { mandate: ask.mandate } : {}) });
29
+ if (verdict.kind === "ask" || verdict.kind === "accepted")
30
+ return undefined;
31
+ ancestorArbitrations.set(toolCallId, verdict);
32
+ if (verdict.kind === "released")
33
+ 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 } : {}) };
34
+ return { action: "deny", message: `refused by the ${verdict.mode} permission mode at an inherited ancestor layer (${verdict.askClass})`, decisionReason: "mode" };
35
+ };
23
36
  const policy = lockedPreflight.toolPolicy;
24
37
  const auditPolicyNames = (auditee) => {
25
38
  const nameGroups = toolPolicyNameSets(auditee);
@@ -220,7 +233,7 @@ export async function preparePolicyChain(input) {
220
233
  list.push({ cls, layer });
221
234
  foldAskClasses.set(toolCallId, list);
222
235
  };
223
- const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || !envHandToolNames.has(toolName);
236
+ const sandboxBoundaryCapable = (call) => anyNamed(egressTools, ruleNamesOf(call)) || !envHandToolNames.has(call.toolName);
224
237
  const emitSandboxAdmitted = (info) => {
225
238
  emitTrace(deps.tracer, () => ({
226
239
  kind: "permission.sandbox_admitted",
@@ -244,18 +257,16 @@ export async function preparePolicyChain(input) {
244
257
  void toolName;
245
258
  };
246
259
  const callerPolicyLayer = policy !== undefined && sameInstanceAncestorCount > 0
247
- ? {
248
- check: async (creq, csignal) => {
249
- const decision = await policy.check(creq, csignal ?? abortController.signal);
250
- sharedFirstDecision.set(creq.toolCallId, decision);
251
- if (sharedFirstDecision.size > SHARED_FIRST_DECISION_CAP) {
252
- const oldest = sharedFirstDecision.keys().next().value;
253
- if (oldest !== undefined)
254
- sharedFirstDecision.delete(oldest);
255
- }
256
- return decision;
257
- },
258
- }
260
+ ? wrapToolPolicy(policy, async (creq, csignal) => {
261
+ const decision = await policy.check(creq, csignal ?? abortController.signal);
262
+ sharedFirstDecision.set(creq.toolCallId, decision);
263
+ if (sharedFirstDecision.size > SHARED_FIRST_DECISION_CAP) {
264
+ const oldest = sharedFirstDecision.keys().next().value;
265
+ if (oldest !== undefined)
266
+ sharedFirstDecision.delete(oldest);
267
+ }
268
+ return decision;
269
+ })
259
270
  : policy;
260
271
  const askSourceIdentity = () => ({
261
272
  principal: spec.principal,
@@ -266,11 +277,12 @@ export async function preparePolicyChain(input) {
266
277
  : {}),
267
278
  ...(delegation.isDelegatedChild ? { isDelegatedChild: true } : {}),
268
279
  });
269
- const riskAxesOf = (toolName) => {
270
- const tier = irreversibilityTier.get(toolName);
271
- const negatives = axisExplicitNegatives.get(toolName);
280
+ const riskAxesOf = (call) => {
281
+ const names = ruleNamesOf(call);
282
+ const tier = firstNamed(irreversibilityTier, names)?.value;
283
+ const negatives = firstNamed(axisExplicitNegatives, names)?.value;
272
284
  const irreversible = tier === "always" || tier === "maybe" ? true : tier === "never" ? false : negatives?.irreversible;
273
- const egress = egressTools.has(toolName) ? true : negatives?.egress;
285
+ const egress = anyNamed(egressTools, names) ? true : negatives?.egress;
274
286
  if (irreversible === undefined && egress === undefined)
275
287
  return {};
276
288
  return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
@@ -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,
@@ -300,7 +314,9 @@ export async function preparePolicyChain(input) {
300
314
  cwdRef: handsCwdRef,
301
315
  deniesDirectoryRead: readDenyMatcher !== undefined ? (d) => readDenyMatcher.matchPath(d) !== null : undefined,
302
316
  });
303
- const recheckApprovedEdit = async (pol, onAskOf, creq, edit, approved, csignal, ancestorDecider, ancestorTracker) => {
317
+ const recheckApprovedEdit = async (pol, onAskOf, creq, edit, approved, csignal, ancestorAutoMode) => {
318
+ const ancestorDecider = ancestorAutoMode?.decider;
319
+ const ancestorTracker = ancestorAutoMode?.denialTracking;
304
320
  let editArgs = edit;
305
321
  let settledBy = approved;
306
322
  const vetoed = (d) => {
@@ -336,7 +352,7 @@ export async function preparePolicyChain(input) {
336
352
  }
337
353
  if (re.updatedInput !== undefined)
338
354
  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 });
355
+ const judged = await judgeInheritedClassifier({ liveMode: () => permissionMode.current, autoMode: ancestorAutoMode, 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
356
  if (judged.kind === "allow") {
341
357
  const cleared = { ...judged.result };
342
358
  carryEngineAttestations(judged.result, cleared);
@@ -347,14 +363,20 @@ export async function preparePolicyChain(input) {
347
363
  return vetoed(judged.result);
348
364
  const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
349
365
  re = editAsk;
366
+ const editArbitrated = arbitrateInherited(re, creq.toolCallId);
367
+ if (editArbitrated !== undefined) {
368
+ const out = editArbitrated.action === "allow" ? { action: "allow", updatedInput: editArgs } : editArbitrated;
369
+ carryPrecedingSettlement(settledBy, out);
370
+ return out;
371
+ }
350
372
  const rr = await resolveAsk({
351
373
  toolName: creq.toolName,
352
374
  toolCallId: creq.toolCallId,
353
375
  args: editArgs,
354
- ...ruleOffersOf(creq.toolName, editArgs, ancestorFacts(re)),
376
+ ...ruleOffersOf(creq, editArgs, ancestorFacts(re)),
355
377
  message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
356
378
  ...askSourceIdentity(),
357
- ...riskAxesOf(creq.toolName),
379
+ ...riskAxesOf(creq),
358
380
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
359
381
  ...askMandateMember(ancestorFacts(re)),
360
382
  ...(re.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: re.persistedRuleShadowed } : {}),
@@ -381,7 +403,7 @@ export async function preparePolicyChain(input) {
381
403
  pc.contentMandate !== true &&
382
404
  askApproverIdentity(pc.onAsk) === askApproverIdentity(frozenOnAsk) &&
383
405
  pc.hookEnv === hookEnvSource &&
384
- pc.autoMode?.decider === autoModeDecider
406
+ pc.autoMode?.decider === autoModeDecider()
385
407
  ? { check: () => ({ action: "allow" }) }
386
408
  : policy !== undefined && pc.policy === policy
387
409
  ? {
@@ -412,26 +434,29 @@ export async function preparePolicyChain(input) {
412
434
  return first;
413
435
  return mandateUnreconstructibleDeny(creq);
414
436
  }
415
- const judged = await judgeInheritedClassifier({ autoMode: pc.autoMode, ask: first, req: creq, signal: csignal ?? abortController.signal, subject: "this call" });
437
+ 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
438
  if (judged.kind === "allow")
417
439
  return judged.result;
418
440
  if (judged.kind === "deny")
419
441
  return judged.result;
420
442
  const { ask: inheritedAsk, fallback, mintedHere } = judged;
421
- if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
443
+ if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && classifyAsk(inheritedAsk, INHERITED_STATION_FACTS).row.sandboxAdmission) {
422
444
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
423
445
  return { action: "allow" };
424
446
  }
447
+ const arbitrated = arbitrateInherited(inheritedAsk, creq.toolCallId);
448
+ if (arbitrated !== undefined)
449
+ return arbitrated;
425
450
  const presentedArgs = creq.args;
426
451
  const askT0 = now();
427
452
  const resolved = await resolveAsk({
428
453
  toolName: creq.toolName,
429
454
  toolCallId: creq.toolCallId,
430
455
  args: presentedArgs,
431
- ...ruleOffersOf(creq.toolName, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
456
+ ...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
432
457
  message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
433
458
  ...askSourceIdentity(),
434
- ...riskAxesOf(creq.toolName),
459
+ ...riskAxesOf(creq),
435
460
  ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
436
461
  ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
437
462
  ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
@@ -449,89 +474,90 @@ export async function preparePolicyChain(input) {
449
474
  if (resolved.action !== "allow")
450
475
  return resolved;
451
476
  if (resolved.updatedInput !== undefined) {
452
- return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode?.decider, pc.autoMode?.denialTracking);
477
+ return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode);
453
478
  }
454
479
  recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved);
455
480
  return { action: "allow" };
456
481
  },
457
482
  }
458
- : {
459
- check: async (creq, csignal) => {
460
- let decision;
461
- try {
462
- decision = await pc.policy.check(creq, csignal ?? abortController.signal);
463
- }
464
- catch (err) {
465
- return {
466
- action: "deny",
467
- message: `inherited parent policy errored for "${creq.toolName}": ${thrownText(err)}`,
468
- };
469
- }
470
- if (decision.action !== "ask")
483
+ : wrapToolPolicy(pc.policy, async (creq, csignal) => {
484
+ let decision;
485
+ try {
486
+ decision = await pc.policy.check(creq, csignal ?? abortController.signal);
487
+ }
488
+ catch (err) {
489
+ return {
490
+ action: "deny",
491
+ message: `inherited parent policy errored for "${creq.toolName}": ${thrownText(err)}`,
492
+ };
493
+ }
494
+ if (decision.action !== "ask")
495
+ return decision;
496
+ if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME) {
497
+ if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId))
471
498
  return decision;
472
- if (creq.toolName === ASK_USER_QUESTION_TOOL_NAME) {
473
- if (pc.contentMandate !== true || markInheritedUnavailable(creq.toolCallId))
474
- return decision;
475
- return {
476
- action: "deny",
477
- message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` +
478
- `denied fail-closed (tighten-only)`,
479
- };
480
- }
481
- if (pc.durableMandate === true) {
482
- if (mandatedCallFloats(creq.toolCallId))
483
- return decision;
484
- return mandateUnreconstructibleDeny(creq);
485
- }
486
- 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 });
488
- if (judged.kind === "allow")
489
- return judged.result;
490
- if (judged.kind === "deny")
491
- return judged.result;
492
- const { ask: inheritedAsk, fallback, mintedHere } = judged;
493
- if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq.toolName) && !probeMandatedAsk(inheritedAsk)) {
494
- recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
495
- return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
496
- }
497
- const askT0 = now();
498
- const resolved = await resolveAsk({
499
- toolName: creq.toolName,
500
- toolCallId: creq.toolCallId,
501
- args: presentedArgs,
502
- ...ruleOffersOf(creq.toolName, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
503
- message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
504
- ...askSourceIdentity(),
505
- ...riskAxesOf(creq.toolName),
506
- ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
507
- ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
508
- ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
509
- ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
510
- ruleEvidence: inheritedAskEvidence,
511
- }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
512
- void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
513
- const askWaitMs = Math.max(0, now() - askT0);
514
- if (resolved.action === "deny" && resolved.approverUnavailable === true) {
515
- if (markInheritedUnavailable(creq.toolCallId))
516
- return inheritedAsk;
517
- return markerChannelFullDeny(creq);
518
- }
519
- settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
520
- if (resolved.action !== "allow")
521
- return resolved;
522
- if (resolved.updatedInput !== undefined) {
523
- return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode?.decider, pc.autoMode?.denialTracking);
524
- }
525
- if (pc.preToolUse === undefined) {
526
- recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved);
527
- }
528
- if (decision.updatedInput === undefined)
529
- return resolved;
530
- const approvedWithRewrite = { ...resolved, updatedInput: decision.updatedInput };
531
- carryEngineAttestations(resolved, approvedWithRewrite);
532
- return approvedWithRewrite;
533
- },
534
- });
499
+ return {
500
+ action: "deny",
501
+ message: `inherited parent policy requires durable handling of the question raised for "${creq.toolName}" and the marker channel is at capacity — ` +
502
+ `denied fail-closed (tighten-only)`,
503
+ };
504
+ }
505
+ if (pc.durableMandate === true) {
506
+ if (mandatedCallFloats(creq.toolCallId))
507
+ return decision;
508
+ return mandateUnreconstructibleDeny(creq);
509
+ }
510
+ const presentedArgs = decision.updatedInput !== undefined ? decision.updatedInput : creq.args;
511
+ 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 }) });
512
+ if (judged.kind === "allow")
513
+ return judged.result;
514
+ if (judged.kind === "deny")
515
+ return judged.result;
516
+ const { ask: inheritedAsk, fallback, mintedHere } = judged;
517
+ if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && classifyAsk(inheritedAsk, INHERITED_STATION_FACTS).row.sandboxAdmission) {
518
+ recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
519
+ return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput } : { action: "allow" };
520
+ }
521
+ const arbitrated = arbitrateInherited(inheritedAsk.action === "ask" ? { ...inheritedAsk, ...(decision.updatedInput !== undefined ? { updatedInput: decision.updatedInput } : {}) } : inheritedAsk, creq.toolCallId);
522
+ if (arbitrated !== undefined)
523
+ return arbitrated;
524
+ const askT0 = now();
525
+ const resolved = await resolveAsk({
526
+ toolName: creq.toolName,
527
+ toolCallId: creq.toolCallId,
528
+ args: presentedArgs,
529
+ ...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
530
+ message: inheritedAsk.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
531
+ ...askSourceIdentity(),
532
+ ...riskAxesOf(creq),
533
+ ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
534
+ ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
535
+ ...(inheritedAsk.action === "ask" && inheritedAsk.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: inheritedAsk.persistedRuleShadowed } : {}),
536
+ ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
537
+ ruleEvidence: inheritedAskEvidence,
538
+ }, pc.onAsk, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
539
+ void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
540
+ const askWaitMs = Math.max(0, now() - askT0);
541
+ if (resolved.action === "deny" && resolved.approverUnavailable === true) {
542
+ if (markInheritedUnavailable(creq.toolCallId))
543
+ return inheritedAsk;
544
+ return markerChannelFullDeny(creq);
545
+ }
546
+ settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
547
+ if (resolved.action !== "allow")
548
+ return resolved;
549
+ if (resolved.updatedInput !== undefined) {
550
+ return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode);
551
+ }
552
+ if (pc.preToolUse === undefined) {
553
+ recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved);
554
+ }
555
+ if (decision.updatedInput === undefined)
556
+ return resolved;
557
+ const approvedWithRewrite = { ...resolved, updatedInput: decision.updatedInput };
558
+ carryEngineAttestations(resolved, approvedWithRewrite);
559
+ return approvedWithRewrite;
560
+ }));
535
561
  const rewriteCapableLayers = policy !== undefined || parentConstraintWrappers.length > 0;
536
562
  const rewriteEmitterIndex = new Map();
537
563
  const rewriteCapableChain = [
@@ -544,17 +570,16 @@ export async function preparePolicyChain(input) {
544
570
  if (policy !== undefined && pc.policy === policy)
545
571
  sameInstanceAdapterIndices.add(j + callerSlotOffset);
546
572
  });
547
- const instrumentedChain = rewriteCapableChain.map((layer, idx) => ({
548
- check: async (creq, csignal) => {
549
- const d = await layer.check(creq, csignal);
550
- if (d.updatedInput !== undefined)
551
- rewriteEmitterIndex.set(creq.toolCallId, idx);
552
- if (d.action === "ask") {
553
- recordFoldAskClass(creq.toolCallId, idx < callerSlotOffset ? policyAskClassOf(policy) : "mandate", idx < callerSlotOffset ? "caller_policy" : "ancestor_constraint");
554
- }
555
- return d;
556
- },
573
+ const instrumentedChain = rewriteCapableChain.map((layer, idx) => wrapToolPolicy(layer, async (creq, csignal) => {
574
+ const d = await layer.check(creq, csignal);
575
+ if (d.updatedInput !== undefined)
576
+ rewriteEmitterIndex.set(creq.toolCallId, idx);
577
+ if (d.action === "ask") {
578
+ recordFoldAskClass(creq.toolCallId, idx < callerSlotOffset ? policyAskClassOf(policy) : "mandate", idx < callerSlotOffset ? "caller_policy" : "ancestor_constraint");
579
+ }
580
+ return d;
557
581
  }));
582
+ const modeGateLayer = createModeGatePolicy(permissionMode, { env: executionEnv, rootPath: taskRootFinal, acceptDirs: [taskRootFinal, ...additionalRootsCanonical], roster: toolRosterDeltas, readOnlyDeclared: (name) => mountedToolDeclaresReadOnly(tools, name), ...(scratchpadDir !== undefined ? { exemptDirs: [scratchpadDir] } : {}) });
558
583
  const policyLayers = [
559
584
  ...narrowingLayers,
560
585
  ...instrumentedChain,
@@ -562,76 +587,74 @@ export async function preparePolicyChain(input) {
562
587
  ];
563
588
  const integrityGuardLayers = [createTranscriptIntegrityPolicy(), createUnverifiableDeletePolicy()];
564
589
  const guardedPolicyLayers = rewriteCapableLayers
565
- ? [...integrityGuardLayers, ...policyLayers, ...integrityGuardLayers]
566
- : [...integrityGuardLayers, ...policyLayers];
590
+ ? [...integrityGuardLayers, ...policyLayers, modeGateLayer, ...integrityGuardLayers]
591
+ : [...integrityGuardLayers, ...policyLayers, modeGateLayer];
567
592
  const foldedPolicy = guardedPolicyLayers.length === 1 ? guardedPolicyLayers[0] : combinePolicies(...guardedPolicyLayers);
568
593
  const effectivePolicy = foldedPolicy !== undefined && rewriteCapableLayers
569
- ? {
570
- check: async (req, signal) => {
571
- let decision;
572
- try {
573
- decision = await foldedPolicy.check(req, signal);
574
- }
575
- catch (err) {
576
- rewriteEmitterIndex.delete(req.toolCallId);
577
- throw err;
578
- }
579
- const emitterIdx = rewriteEmitterIndex.get(req.toolCallId);
594
+ ? wrapToolPolicy(foldedPolicy, async (req, signal) => {
595
+ let decision;
596
+ try {
597
+ decision = await foldedPolicy.check(req, signal);
598
+ }
599
+ catch (err) {
580
600
  rewriteEmitterIndex.delete(req.toolCallId);
581
- if (decision.action === "deny" || decision.updatedInput === undefined)
582
- return decision;
583
- const recheckPrefix = rewriteCapableChain.slice(0, emitterIdx ?? rewriteCapableChain.length);
584
- const postEmitterSameInstanceAdapters = [];
585
- if (emitterIdx !== undefined && emitterIdx > 0) {
586
- for (let i = emitterIdx + 1; i < rewriteCapableChain.length; i++) {
587
- if (sameInstanceAdapterIndices.has(i))
588
- postEmitterSameInstanceAdapters.push(rewriteCapableChain[i]);
589
- }
601
+ throw err;
602
+ }
603
+ const emitterIdx = rewriteEmitterIndex.get(req.toolCallId);
604
+ rewriteEmitterIndex.delete(req.toolCallId);
605
+ if (decision.action === "deny" || decision.updatedInput === undefined)
606
+ return decision;
607
+ const recheckPrefix = rewriteCapableChain.slice(0, emitterIdx ?? rewriteCapableChain.length);
608
+ const postEmitterSameInstanceAdapters = [];
609
+ if (emitterIdx !== undefined && emitterIdx > 0) {
610
+ for (let i = emitterIdx + 1; i < rewriteCapableChain.length; i++) {
611
+ if (sameInstanceAdapterIndices.has(i))
612
+ postEmitterSameInstanceAdapters.push(rewriteCapableChain[i]);
590
613
  }
591
- const recheckLayers = [...recheckPrefix, ...postEmitterSameInstanceAdapters];
592
- const finalReq = { ...req, args: decision.updatedInput };
593
- let asked;
594
- const revalidated = {};
595
- try {
596
- for (const layer of recheckLayers) {
597
- const d = await layer.check(finalReq, signal);
598
- if (d.action === "deny") {
599
- const carried = { ...d, updatedInput: decision.updatedInput };
600
- carryEngineAttestations(d, carried);
601
- carryPrecedingSettlement(decision, carried);
602
- return carried;
603
- }
604
- if (d.updatedInput !== undefined) {
605
- return {
606
- action: "deny",
607
- message: "policy required a rewrite during final-form re-check — the arguments that would execute were not accepted as-is (fail-closed; rewrite chains are not iterated to a fixpoint)",
608
- updatedInput: decision.updatedInput,
609
- };
610
- }
611
- if (d.action === "ask" && asked === undefined)
612
- asked = d;
613
- carryEngineAttestations(d, revalidated);
614
+ }
615
+ const recheckLayers = [...recheckPrefix, ...postEmitterSameInstanceAdapters];
616
+ const finalReq = { ...req, args: decision.updatedInput };
617
+ let asked;
618
+ const revalidated = {};
619
+ try {
620
+ for (const layer of recheckLayers) {
621
+ const d = await layer.check(finalReq, signal);
622
+ if (d.action === "deny") {
623
+ const carried = { ...d, updatedInput: decision.updatedInput };
624
+ carryEngineAttestations(d, carried);
625
+ carryPrecedingSettlement(decision, carried);
626
+ return carried;
614
627
  }
628
+ if (d.updatedInput !== undefined) {
629
+ return {
630
+ action: "deny",
631
+ message: "policy required a rewrite during final-form re-check — the arguments that would execute were not accepted as-is (fail-closed; rewrite chains are not iterated to a fixpoint)",
632
+ updatedInput: decision.updatedInput,
633
+ };
634
+ }
635
+ if (d.action === "ask" && asked === undefined)
636
+ asked = d;
637
+ carryEngineAttestations(d, revalidated);
615
638
  }
616
- finally {
617
- rewriteEmitterIndex.delete(req.toolCallId);
618
- }
619
- if (asked !== undefined && decision.action === "allow") {
620
- const surfaced = { ...asked, updatedInput: decision.updatedInput };
621
- carryEngineAttestations(asked, surfaced);
622
- return surfaced;
623
- }
624
- if (decision.action === "allow") {
625
- const owned = { ...decision };
626
- carryEngineAttestations(decision, owned);
627
- carryEngineAttestations(revalidated, owned);
628
- return owned;
629
- }
630
- return decision;
631
- },
632
- }
639
+ }
640
+ finally {
641
+ rewriteEmitterIndex.delete(req.toolCallId);
642
+ }
643
+ if (asked !== undefined && decision.action === "allow") {
644
+ const surfaced = { ...asked, updatedInput: decision.updatedInput };
645
+ carryEngineAttestations(asked, surfaced);
646
+ return surfaced;
647
+ }
648
+ if (decision.action === "allow") {
649
+ const owned = { ...decision };
650
+ carryEngineAttestations(decision, owned);
651
+ carryEngineAttestations(revalidated, owned);
652
+ return owned;
653
+ }
654
+ return decision;
655
+ })
633
656
  : foldedPolicy;
634
- return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions };
657
+ return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
635
658
  }
636
659
  const advisedPolicyNames = new WeakMap();
637
660
  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");