@sema-agent/core 7.42.1 → 7.44.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 (115) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +3 -1
  4. package/dist/agents/subagent.js +5 -3
  5. package/dist/agents/teacher.js +1 -1
  6. package/dist/agents/verify.js +2 -0
  7. package/dist/brain/errors.d.ts +23 -2
  8. package/dist/brain/errors.js +26 -2
  9. package/dist/brain/stream-engine.d.ts +2 -1
  10. package/dist/brain/stream-engine.js +10 -9
  11. package/dist/core/allow-entry.d.ts +50 -31
  12. package/dist/core/allow-entry.js +100 -37
  13. package/dist/core/ask-origin.d.ts +16 -5
  14. package/dist/core/ask-origin.js +11 -3
  15. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  16. package/dist/core/ask-unresolvable-notice.js +1 -1
  17. package/dist/core/auto-mode.d.ts +5 -4
  18. package/dist/core/auto-mode.js +3 -1
  19. package/dist/core/checkpoint-store.d.ts +44 -20
  20. package/dist/core/checkpoint-store.js +10 -16
  21. package/dist/core/child-clamp-seats.d.ts +15 -1
  22. package/dist/core/child-clamp-seats.js +6 -2
  23. package/dist/core/classifier-unavailable-arm.d.ts +42 -23
  24. package/dist/core/classifier-unavailable-arm.js +14 -3
  25. package/dist/core/decision-receipt.js +2 -0
  26. package/dist/core/engine-notice.d.ts +16 -14
  27. package/dist/core/engine-notice.js +4 -3
  28. package/dist/core/gate-lanes.js +37 -29
  29. package/dist/core/governance-codes.d.ts +4 -3
  30. package/dist/core/governance-codes.js +1 -0
  31. package/dist/core/hooks.d.ts +6 -1
  32. package/dist/core/hooks.js +3 -0
  33. package/dist/core/memory-engine/engine.d.ts +2 -2
  34. package/dist/core/memory-engine/engine.js +7 -14
  35. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  36. package/dist/core/memory-engine/file-backend.js +5 -7
  37. package/dist/core/memory-engine/layout.d.ts +37 -4
  38. package/dist/core/memory-engine/layout.js +34 -5
  39. package/dist/core/mode-policy.d.ts +5 -4
  40. package/dist/core/mode-policy.js +4 -0
  41. package/dist/core/permission-rule-consent.d.ts +3 -2
  42. package/dist/core/permission-rule-consent.js +25 -20
  43. package/dist/core/permission-rule-model.d.ts +47 -7
  44. package/dist/core/permission-rule-model.js +80 -6
  45. package/dist/core/permission-rules.d.ts +8 -2
  46. package/dist/core/permission-rules.js +26 -9
  47. package/dist/core/runner/classifier-ladder.d.ts +10 -8
  48. package/dist/core/runner/classifier-ladder.js +3 -1
  49. package/dist/core/runner/contracts.d.ts +1 -16
  50. package/dist/core/runner/denial-limit-arms.d.ts +25 -8
  51. package/dist/core/runner/denial-limit-arms.js +24 -10
  52. package/dist/core/runner/gate-exit.js +2 -1
  53. package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
  54. package/dist/core/runner/permission-rule-lanes.js +8 -2
  55. package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
  56. package/dist/core/runner/prepare-ask-lane.js +2 -1
  57. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -3
  58. package/dist/core/runner/prepare-caps-and-workflow.js +3 -2
  59. package/dist/core/runner/prepare-config-doors.d.ts +3 -0
  60. package/dist/core/runner/prepare-config-doors.js +6 -5
  61. package/dist/core/runner/prepare-gate-stations.js +1 -1
  62. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
  63. package/dist/core/runner/prepare-inherited-gate.js +5 -3
  64. package/dist/core/runner/prepare-park-ask.d.ts +4 -4
  65. package/dist/core/runner/prepare-park-ask.js +2 -4
  66. package/dist/core/runner/prepare-policy-chain.d.ts +7 -1
  67. package/dist/core/runner/prepare-policy-chain.js +59 -38
  68. package/dist/core/runner/prepare-project-context.js +6 -2
  69. package/dist/core/runner/prepare-task.js +6 -6
  70. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -5
  71. package/dist/core/runner/prepare-wiring-manifest.js +8 -9
  72. package/dist/core/runner/resume-checkpoint-screen.js +1 -1
  73. package/dist/core/runner/runtask.js +1 -5
  74. package/dist/core/runner/steer-admission.d.ts +10 -11
  75. package/dist/core/runner/steer-admission.js +6 -8
  76. package/dist/core/runner/stream-steer-verb.d.ts +5 -4
  77. package/dist/core/runner/stream-steer-verb.js +6 -8
  78. package/dist/core/runner/synthetic-tools.d.ts +3 -2
  79. package/dist/core/runner/synthetic-tools.js +1 -1
  80. package/dist/core/session-policy-store.d.ts +3 -3
  81. package/dist/core/session-policy-store.js +18 -4
  82. package/dist/core/skill-grant-binding.d.ts +44 -0
  83. package/dist/core/skill-grant-binding.js +22 -0
  84. package/dist/core/skill-grant.d.ts +59 -28
  85. package/dist/core/skill-grant.js +96 -23
  86. package/dist/core/steer-replay.d.ts +46 -0
  87. package/dist/core/steer-replay.js +22 -0
  88. package/dist/core/task-event.d.ts +3 -2
  89. package/dist/core/task-spec.d.ts +14 -1
  90. package/dist/core/task-stream.d.ts +12 -6
  91. package/dist/core/tighten-task-spec.d.ts +2 -0
  92. package/dist/core/tighten-task-spec.js +3 -0
  93. package/dist/core/tool-errors.d.ts +2 -2
  94. package/dist/core/tool-name-aliases.d.ts +35 -5
  95. package/dist/core/tool-name-aliases.js +29 -4
  96. package/dist/core/tool-policy.d.ts +10 -0
  97. package/dist/core/tool-policy.js +9 -2
  98. package/dist/core/tool-spec.d.ts +7 -0
  99. package/dist/core/types.d.ts +2 -2
  100. package/dist/core/types.js +1 -1
  101. package/dist/core/wiring-manifest.d.ts +56 -14
  102. package/dist/core/wiring-manifest.js +29 -4
  103. package/dist/index.d.ts +6 -4
  104. package/dist/index.js +5 -4
  105. package/dist/orchestration/run-workflow-tool.d.ts +4 -0
  106. package/dist/orchestration/run-workflow-tool.js +1 -1
  107. package/dist/orchestration/workflow-primitives.d.ts +3 -1
  108. package/dist/orchestration/workflow-primitives.js +6 -6
  109. package/dist/orchestration/workflow-types.d.ts +5 -0
  110. package/dist/orchestration/workflow.d.ts +3 -2
  111. package/dist/orchestration/workflow.js +14 -6
  112. package/package.json +1 -1
  113. package/test/export-surface.snapshot.json +21 -5
  114. package/wire-changes/core@7.43.0.json +985 -0
  115. package/wire-changes/core@7.44.0.json +469 -0
@@ -1,6 +1,6 @@
1
1
  import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
2
2
  import {} from "../hooks.js";
3
- import { adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, faceAskSpeaksFor, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
3
+ import { adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, faceAskSpeaksFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
4
4
  import { isAbsolutePathForm } from "../../tools/fs/safety.js";
5
5
  import { effectivePathTargetOf } from "../effective-path-target.js";
6
6
  import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
@@ -155,6 +155,8 @@ export function createPermissionRuleLanes(cfg) {
155
155
  const liveCwd = cfg.liveCwd();
156
156
  if (grammar === "path")
157
157
  return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
158
+ if (grammar === "name")
159
+ return persistedRuleAnswerOf(adjudicatePersistedNameRules(table, { tool: req.toolName, name: nameRuleSubjectOf(req.toolName, req.args), cwd: cfg.root, sessionId: cfg.sessionId }));
158
160
  const command = req.args?.command;
159
161
  if (typeof command !== "string")
160
162
  return undefined;
@@ -172,7 +174,7 @@ export function createPermissionRuleLanes(cfg) {
172
174
  export function createRuleOffersOf(cfg) {
173
175
  return (call, args, ask) => {
174
176
  const toolName = wireOf("offer.laneTool", call);
175
- if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
177
+ if (!cfg.laneArmed || (toolName !== PERSISTED_RULE_TOOL && ruleToolGrammarOf(toolName) !== "name"))
176
178
  return {};
177
179
  if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
178
180
  return {};
@@ -184,6 +186,10 @@ export function createRuleOffersOf(cfg) {
184
186
  }).word;
185
187
  if (closedDoor !== undefined)
186
188
  return { ruleOffersAbsence: closedDoor };
189
+ if (ruleToolGrammarOf(toolName) === "name") {
190
+ const offer = suggestRuleForName(toolName, nameRuleSubjectOf(toolName, args) ?? "");
191
+ return offer !== undefined ? { ruleOffers: [offer] } : { ruleOffersAbsence: "lane_cannot_speak" };
192
+ }
187
193
  const command = args?.command;
188
194
  if (typeof command !== "string")
189
195
  return { ruleOffersAbsence: "lane_cannot_speak" };
@@ -6,6 +6,7 @@ import { type OnAsk, type ToolCallRequest, type ToolPolicy } from "../tool-polic
6
6
  import type { RunnerDeps, TaskSpec } from "../types.js";
7
7
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
8
8
  import type { AskLane, Prepared } from "./contracts.js";
9
+ import type { GrantBindingOf } from "../skill-grant-binding.js";
9
10
  import { consumeInheritedAskGrant } from "./inherited-ask-grants.js";
10
11
  import { type GrantReadRoot } from "./read-root-grant.js";
11
12
  import type { createRuleOffersOf } from "./permission-rule-lanes.js";
@@ -49,6 +50,8 @@ export interface PrepareAskLaneInput {
49
50
  now: () => number;
50
51
  /** borrowed-readonly — the policy-chain phase's rule-offer factory (the suggestion members the ask mint threads). */
51
52
  ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
53
+ /** borrowed-readonly — the policy-chain phase's Skill-card binding factory (`boundGrants` / `boundGrantDigest` on the ask). */
54
+ grantBindingOf: GrantBindingOf;
52
55
  /** borrowed-readonly — the policy-chain phase's Runner-filled source identity (one factory, every ask mint site). */
53
56
  askSourceIdentity: () => {
54
57
  principal?: string;
@@ -59,7 +59,7 @@ function resolveApprovalPreview(tools, toolName, args) {
59
59
  }
60
60
  }
61
61
  export function prepareAskLane(input) {
62
- const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
62
+ const { gateMachineryActive, abortController, effectivePolicy, budgetSnapshot, handsCwdRef, advertisedWritableDirs, tools, inheritedUnavailableAsks, inheritedAskGrants, onAsk, humanReviewRef, now, ruleOffersOf, grantBindingOf, askSourceIdentity, riskAxesOf, autoModeDenialTracking, spec, deps, sessionId, runId, hooks, hookTimeoutMs, notifyOwnHookCrash, grantReadRoot } = input;
63
63
  if (!gateMachineryActive)
64
64
  return { askLane: undefined };
65
65
  const composedCallSignal = (callSignal) => composeCallSignal(abortController.signal, callSignal);
@@ -98,6 +98,7 @@ export function prepareAskLane(input) {
98
98
  return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
99
99
  })(),
100
100
  ...ruleOffersOf(req, req.args, decision.action === "ask" ? decision : undefined),
101
+ ...grantBindingOf(req, req.args),
101
102
  ...cardDisclosureOf(decision, `approval required for "${req.toolName}"`),
102
103
  ...askSourceIdentity(),
103
104
  ...riskAxesOf(req),
@@ -30,7 +30,6 @@ import { type CheckpointSeat } from "../checkpoint-seat.js";
30
30
  import { type ComplianceCapability } from "../compliance.js";
31
31
  import type { LockedPreflight } from "../locked-config.js";
32
32
  import type { StoredSession } from "../session.js";
33
- import type { OnAsk } from "../tool-policy.js";
34
33
  import type { RunnerDeps, RuntimeCaps, TaskEvent, TaskSpec, ToolExecuteContext } from "../types.js";
35
34
  import type { AutoModeArmFact } from "../wiring-manifest.js";
36
35
  import type { AssertAllKeysHandled } from "../ask-origin.js";
@@ -88,8 +87,8 @@ export interface PrepareCapsAndWorkflowInput {
88
87
  harnessRef: {
89
88
  current?: AgentHarness;
90
89
  };
91
- /** borrowed-readonly — the FROZEN approver seat (the Workflow mount's parentOnAsk). */
92
- frozenOnAsk: OnAsk | undefined;
90
+ /** borrowed-readonly — the run's approval seat as the config doors froze it (the Workflow mount's parentOnAsk / parentApprovalReach). */
91
+ askSeat: import("../wiring-manifest.js").AskSeat;
93
92
  /** borrowed-readonly — the parent SPEC seat the children inherit (minted once by the inherited-gate phase; the Workflow mount's dep, the same value the delegation ctx carries). */ parentSpecSeat: ParentSpecSeat;
94
93
  /** borrowed-readonly — the ONE read-face carrier expression (the Workflow mount's parentReadFace getter calls it). */
95
94
  carrierReadFace: () => ContainmentFace | undefined;
@@ -114,7 +114,7 @@ function autoModeArmReasonOf(intent, facePresent, caps, capsFaulted) {
114
114
  return { armed: true, reason: "armed" };
115
115
  }
116
116
  export async function prepareCapsAndWorkflow(input) {
117
- const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, frozenOnAsk, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
117
+ const { spec, deps, parentSpecSeat, checkpointSeat, internals, session, sessionId, runId, hostTaskId, taskScope, taskRootFinal, model, thinking, lockedPreflight, toolEffects, toolFaceSnapshot, promptProfile, resolvedInteractionPosture, harnessRef, askSeat, carrierReadFace, carrierWriteFace, forwardEvent, inheritedGateForChildren, enrichSpecToolCtx, maybeOffload, requestReview, enterPlanMode, permissionMode, readDenyEntries, peerLaneActive, peerSendMessageBuiltIn, runnerSelf } = input;
118
118
  const roster = new RosterBuilder(toolEffects);
119
119
  for (const t of spec.tools ?? []) {
120
120
  const mounted = mountCallerTool(t);
@@ -326,7 +326,8 @@ export async function prepareCapsAndWorkflow(input) {
326
326
  return model === undefined ? undefined : { model, ...(typeof ref === "string" ? { modelRef: ref } : {}) };
327
327
  },
328
328
  ...(spec.getApiKeyAndHeaders !== undefined ? { parentGetApiKeyAndHeaders: spec.getApiKeyAndHeaders } : {}),
329
- ...(frozenOnAsk !== undefined ? { parentOnAsk: frozenOnAsk } : {}),
329
+ ...(askSeat.value !== undefined ? { parentOnAsk: askSeat.value } : {}),
330
+ ...(askSeat.reach !== undefined ? { parentApprovalReach: askSeat.reach } : {}),
330
331
  ...(spec.durableApproval !== undefined ? { parentDurableApproval: { ...spec.durableApproval } } : {}),
331
332
  ...(internals?.workflowParkedResume !== undefined
332
333
  ? { parkedResume: (resumeFromRunId) => {
@@ -29,6 +29,7 @@ import { type ResolvedSkillListingBudget } from "../skill-listing-budget.js";
29
29
  import type { SessionStore } from "../session.js";
30
30
  import type { RunnerDeps, TaskLimits, TaskSpec } from "../types.js";
31
31
  import { type UsageWindow } from "../usage-window-store.js";
32
+ import { type AskSeat } from "../wiring-manifest.js";
32
33
  import type { PrepareResume, RunInternals, ToolFaceSnapshot } from "./contracts.js";
33
34
  export declare function limitConfigError(code: string, message: string): Error & {
34
35
  code?: string;
@@ -147,6 +148,8 @@ export interface PrepareConfigDoorsResult {
147
148
  /** owned — the resolved interaction posture (spec > spawning run > deployment); the
148
149
  * AskUserQuestion mount and the child-ctx injection read this same value. */
149
150
  resolvedInteractionPosture: "interactive" | "headless" | undefined;
151
+ /** owned, frozen — the run's approval seat ({@link resolveAskSeat}), the one read of the seats; later phases read only this. */
152
+ askSeat: AskSeat;
150
153
  /** owned — the validated/resolved microCompact knob ({@link resolveMicroCompactKnob}
151
154
  * for the door + placement reasoning). The per-run state builder consumes exactly these values —
152
155
  * the deps bag is never re-read after this door. */
@@ -12,7 +12,7 @@ import { resolveSkillListingBudget } from "../skill-listing-budget.js";
12
12
  import { describeRejectedValue } from "../tool-search-setting.js";
13
13
  import { applyToolModelGate, assertRestoreGatedToolsValue, modelIdTail } from "../tool-model-gate.js";
14
14
  import { resolveUsageWindows } from "../usage-window-store.js";
15
- import { deriveAskEffective, resolveAskSeamForm, resolveQuestionSeam } from "../wiring-manifest.js";
15
+ import { deriveAskEffective, resolveAskSeat, resolveQuestionSeam } from "../wiring-manifest.js";
16
16
  import { assertPeerPresenceValue } from "../../agents/peer-directory.js";
17
17
  import { TOTAL_TOKENS_REMINDER_MODES } from "./turn-attachments.js";
18
18
  const TASK_LIMIT_KEY_DICT = {
@@ -286,6 +286,7 @@ export function prepareConfigDoors(input) {
286
286
  ],
287
287
  });
288
288
  const resolvedInteractionPosture = spec.interactionPosture ?? internals?.parentInteractionPosture ?? deps.interactionPosture;
289
+ const askSeat = resolveAskSeat(spec, deps);
289
290
  {
290
291
  const interactionPosture = resolvedInteractionPosture;
291
292
  const discloseInteractionPostureRefusal = (err) => {
@@ -303,8 +304,7 @@ export function prepareConfigDoors(input) {
303
304
  throw e;
304
305
  }
305
306
  if (interactionPosture === "interactive") {
306
- const askDoor = resolveAskSeamForm(spec, deps);
307
- const humanReachable = deriveAskEffective(askDoor.form, "unresolved") === "human_reachable";
307
+ const humanReachable = deriveAskEffective(askSeat.form, "unresolved") === "human_reachable";
308
308
  const questionDoor = resolveQuestionSeam(spec, deps);
309
309
  const strippedByEngine = internals?.questionFaceStripped === true;
310
310
  if (spec.interactiveTools === false) {
@@ -318,9 +318,9 @@ export function prepareConfigDoors(input) {
318
318
  if (!humanReachable || !(questionDoor.wired || strippedByEngine)) {
319
319
  const missing = [];
320
320
  if (!humanReachable) {
321
- missing.push(askDoor.form === "absent"
321
+ missing.push(askSeat.form === "declared_none" ? 'this run declares approvalReach: "none" — nobody on it can be asked to approve' : askSeat.form === "absent"
322
322
  ? "no onAsk approver is wired (spec.onAsk ?? deps.onAsk is absent — asks would auto-deny or park)"
323
- : `the resolved onAsk seat is the blanket policy "${askDoor.form}" (${askDoor.provenance ?? "?"}) — a policy setting is not a reachable human`);
323
+ : `the resolved onAsk seat is the blanket policy "${askSeat.form}" (${askSeat.provenance ?? "?"}) — a policy setting is not a reachable human`);
324
324
  }
325
325
  if (!questionDoor.wired && !strippedByEngine) {
326
326
  missing.push("no content-question channel (spec.onQuestion ?? deps.onQuestion is absent, and this leg is not an engine-stripped background lane)");
@@ -433,6 +433,7 @@ export function prepareConfigDoors(input) {
433
433
  promptProfile,
434
434
  lockedPreflight,
435
435
  resolvedInteractionPosture,
436
+ askSeat,
436
437
  microCompactKnob,
437
438
  resolvedRole,
438
439
  model,
@@ -137,7 +137,7 @@ export function prepareGateStations(input) {
137
137
  onHookError: notifyHookError,
138
138
  shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
139
139
  ...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
140
- permissionMode: () => permissionMode.current,
140
+ permissionMode: () => permissionMode.current, askSeatForm: askRoute.form,
141
141
  ...(markSources.size > 0 ? { toolMarkSources: markSources } : {}),
142
142
  ...seatInputsFor(e.toolName, wireOf("gate.skillActivationSeat", call)),
143
143
  ...(mountedToolRequiresUserInteraction(tools, e.toolName) ? { requiresUserInteraction: true } : {}),
@@ -50,19 +50,21 @@ import { type SkillGrantTable } from "../skill-grant.js";
50
50
  import type { InheritedGate, Prepared, PrepareResume, RunInternals, StopForDenialLimit, ToolFaceSnapshot, WriteFaceSeat } from "./contracts.js";
51
51
  export interface PrepareInheritedGateInput {
52
52
  /** borrowed-readonly — the REBOUND spec. Read: `shellGate` (the own doctrine the chain fold tightens), `durableApproval`
53
- * (the mandate tests + the children's copy), `onAsk`/`onQuestion` (the frozen seats), `handsReadOnly` / `interactiveTools`
53
+ * (the mandate tests + the children's copy), `onQuestion` (the frozen question seat), `handsReadOnly` / `interactiveTools`
54
54
  * / `oneShot` / `clientContext` / `principal` / `additionalDirectories` / `additionalReadDirectories` / `envFacts` /
55
55
  * `memoryPersistenceCapable` / `getApiKeyAndHeaders` / `backgroundScope` / `enablePlanMode` (the ctx enrichment's
56
56
  * trusted seats). Never mutated. */
57
- spec: Pick<TaskSpec, "shellGate" | "durableApproval" | "onAsk" | "onQuestion" | "handsReadOnly" | "interactiveTools" | "oneShot" | "clientContext" | "principal" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "getApiKeyAndHeaders" | "backgroundScope" | "enablePlanMode" | "excludeAllTools">;
57
+ spec: Pick<TaskSpec, "shellGate" | "durableApproval" | "onQuestion" | "handsReadOnly" | "interactiveTools" | "oneShot" | "clientContext" | "principal" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "getApiKeyAndHeaders" | "backgroundScope" | "enablePlanMode" | "excludeAllTools">;
58
58
  /** borrowed-readonly — the seat this run hands its children (`checkpointSeatOf`, minted once by the driver after the config doors): the ctx's `checkpointStoreForChildren`, absent when the run has no seat. */
59
59
  checkpointSeat: import("../checkpoint-seat.js").CheckpointSeat | undefined;
60
60
  /** borrowed-readonly — the deployment seats: `autoMode.denialLimit` (the rebuilt trackers' bounds), `memoryScopeAdmission` /
61
61
  * `deploymentMemoryScopes` / `compliancePostureResolver` (the governed-provenance bit), `onError` (the screening-face crash
62
62
  * lane), `onNotice` (the stop seat's notice), `executionEnv` (the hook-env identity predicate), `delegationEntryCaps` (resolved
63
- * and validated once here), `rosterStore` / `onBackgroundChildEvent` (ctx seats), `onAsk` / `onQuestion` (the deps half of the
64
- * frozen seats). */
65
- deps: Pick<RunnerDeps, "autoMode" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "compliancePostureResolver" | "onError" | "onNotice" | "executionEnv" | "delegationEntryCaps" | "rosterStore" | "onBackgroundChildEvent" | "onAsk" | "onQuestion">;
63
+ * and validated once here), `rosterStore` / `onBackgroundChildEvent` (ctx seats), `onQuestion` (the deps half of the
64
+ * frozen question seat). */
65
+ deps: Pick<RunnerDeps, "autoMode" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "compliancePostureResolver" | "onError" | "onNotice" | "executionEnv" | "delegationEntryCaps" | "rosterStore" | "onBackgroundChildEvent" | "onQuestion">;
66
+ /** borrowed-readonly — the run's approval seat as the config doors froze it: the frozen approver, and this leg's chain view. */
67
+ askSeat: import("../wiring-manifest.js").AskSeat;
66
68
  /** borrowed-readonly — the trusted spawn-side channel: `inheritedGate` (the LIVE half of the chain fold), `ownOrgAdmissionRef`
67
69
  * (the same-session continuation's freeze), `delegationProvenance` (the F-012 stamp), and the identity / peer / notification
68
70
  * seats the ctx enrichment forwards. Never mutated. */
@@ -13,7 +13,7 @@ import { frozenSkillGrants } from "../skill-grant.js";
13
13
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
14
14
  import { asThrownError } from "../thrown-value.js";
15
15
  export function prepareInheritedGate(input) {
16
- const { spec, deps, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
16
+ const { spec, deps, askSeat, internals, resume, sessions, sessionId, runId, hostTaskId, taskRootFinal, executionEnv, ownedEnv, lockedPreflight, toolFaceSnapshot, mountsCallerTool, promptProfile, resolvedInteractionPosture, permissionModeSeat, handsEnabled, reportUsage, harnessRef, skillGrants, forwardEvent, subagentRetain, worktreeIsolation, requestReview, requestStopAfterTurn, enterPlanMode, reminderMark, reminderDisclosureCounts, fileHistoryEnabled, historyScope, historyRoot, historyFs, abortRun } = input;
17
17
  const liveInheritedGate = internals?.inheritedGate;
18
18
  const seedInheritedGate = resume?.seed.inheritedGate;
19
19
  const inheritedAncestorRules = (() => {
@@ -35,7 +35,8 @@ export function prepareInheritedGate(input) {
35
35
  })();
36
36
  const modeSeed = recordedModeSeed(seedInheritedGate);
37
37
  const inheritedShellGate = liveInheritedGate?.shellGate ?? modeSeed.shellGate;
38
- const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
38
+ const chainView = askSeat.reach === "none" ? liveInheritedGate?.parentConstraints?.map((pc) => (typeof pc.onAsk === "function" ? ((({ onAsk: _person, ...rest }) => rest)(pc)) : pc)) : liveInheritedGate?.parentConstraints;
39
+ const inheritedParentConstraints = attachRebuiltDenialTrackers(chainView, deps.autoMode?.denialLimit);
39
40
  const parentSeat = liveInheritedGate?.permissionModeCeiling;
40
41
  const ceiling = parentSeat ?? liveInheritedGate?.permissionMode;
41
42
  const announceModeFold = (receipt) => {
@@ -113,7 +114,7 @@ export function prepareInheritedGate(input) {
113
114
  const f012CheckpointState = () => (inheritedParentConstraints?.length ?? 0) > 0 ||
114
115
  seedInheritedGate?.constraintChain !== undefined ||
115
116
  internals?.delegationProvenance !== undefined;
116
- const frozenOnAsk = spec.onAsk ?? deps.onAsk;
117
+ const frozenOnAsk = askSeat.value;
117
118
  const hookEnvSource = (ownedEnv ?? deps.executionEnv) != null ? executionEnv : undefined;
118
119
  const notifyOwnHookCrash = (err) => {
119
120
  try {
@@ -213,6 +214,7 @@ export function prepareInheritedGate(input) {
213
214
  thinkingLevel: harnessRef.current?.getThinkingLevel(),
214
215
  principal: spec.principal,
215
216
  ...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
217
+ ...(askSeat.reach !== undefined ? { approvalReach: askSeat.reach } : {}),
216
218
  ...(frozenOnQuestion !== undefined ? { onQuestion: frozenOnQuestion } : {}),
217
219
  ...(spec.handsReadOnly === true ? { handsReadOnly: true } : {}),
218
220
  ...(carrierReadFace() === "roots" ? { readFace: "roots" } : {}),
@@ -22,6 +22,7 @@
22
22
  import type { AgentHarness, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
23
23
  import { type OnQuestion } from "../ask-question.js";
24
24
  import { type CheckpointStore } from "../checkpoint-store.js";
25
+ import type { GrantBindingOf } from "../skill-grant-binding.js";
25
26
  import type { SessionStore } from "../session.js";
26
27
  import { type OnAsk, type ToolCallRequest, type ToolPolicy } from "../tool-policy.js";
27
28
  import { type ToolResultStore } from "../tool-result-store.js";
@@ -32,7 +33,6 @@ import { type ContentAskBinding } from "./content-ask-bindings.js";
32
33
  import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, SuspendSaga } from "./contracts.js";
33
34
  import type { createRuleOffersOf } from "./permission-rule-lanes.js";
34
35
  import { type ToolRosterDeltaSeat } from "../tool-roster.js";
35
- import { type SkillGrantSource } from "../skill-grant.js";
36
36
  export interface PrepareParkAskInput {
37
37
  /** borrowed-readonly — the in-stream ask lane, or undefined when the gate machinery is inactive (then both closures are
38
38
  * absent). Read: `composedCallSignal` (the fence and the delivery race), `approvalPreviewOf` (the row's preview). */
@@ -141,9 +141,9 @@ export interface PrepareParkAskInput {
141
141
  orgAdmissionCheckpointState: () => boolean;
142
142
  /** borrowed-readonly — the policy-chain phase's rule-offer factory (the row's suggestion members). */
143
143
  ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
144
- /** borrowed-readonly — the leg's loaded skills (what its `Skill` tool serves); a parked `Skill` call's row binds the named
145
- * skill — its name and its lists — from them (`boundGrantDigest`). Empty when the leg loads none. */
146
- skillSpecs: readonly SkillGrantSource[];
144
+ /** borrowed-readonly — the policy-chain phase's Skill-card binding factory: a parked `Skill` call's row carries the named skill and
145
+ * its lists (`boundGrants`) and their hash (`boundGrantDigest`) from it, as the live ask does. */
146
+ grantBindingOf: GrantBindingOf;
147
147
  /** borrowed-readonly — the injectable wall clock (the row's `suspendedAt`). */
148
148
  now: () => number;
149
149
  /** borrowed-readonly — the per-task human-review accumulator; snapshotted onto the row (never written here). */
@@ -17,8 +17,6 @@ import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedT
17
17
  import { remoteEnvFailureNote } from "./remote-env-retry.js";
18
18
  import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js";
19
19
  import { seatOf, wireOf } from "../tool-name-readings.js";
20
- import { skillCallGrantDigest } from "../skill-grant.js";
21
- import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
22
20
  class ParkRefusal extends Error {
23
21
  constructor(message, options) {
24
22
  super(message, options);
@@ -29,7 +27,7 @@ const wordlessDemand = (toolName) => {
29
27
  throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
30
28
  };
31
29
  export function prepareParkAsk(input) {
32
- const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, skillSpecs, now, humanReviewRef, abortController, harness, pausedRef } = input;
30
+ const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, grantBindingOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
33
31
  if (askLane === undefined || saga === undefined)
34
32
  return { parkAsk: undefined };
35
33
  const { composedCallSignal, approvalPreviewOf } = askLane;
@@ -318,7 +316,7 @@ export function prepareParkAsk(input) {
318
316
  ...askCarryRowMembers(carry),
319
317
  ...ruleOffersOf(req, parkedArgs, clearanceFacts),
320
318
  boundInputHash: boundInputHashOf(parkedArgs),
321
- ...((grantDigest) => (grantDigest !== undefined ? { boundGrantDigest: grantDigest } : {}))(skillCallGrantDigest(req, parkedArgs, skillSpecs, SKILL_TOOL_NAME)),
319
+ ...grantBindingOf(req, parkedArgs),
322
320
  batchToolCallIds,
323
321
  completedCallIds,
324
322
  }),
@@ -30,7 +30,8 @@ import { type OnAsk, type ToolPolicy } from "../tool-policy.js";
30
30
  import type { RunnerDeps, RuntimeCaps, TaskSpec, ToolEffect } from "../types.js";
31
31
  import type { CwdRef } from "../../tools/fs/fs-shared.js";
32
32
  import type { ReadDenyMatcher } from "../../tools/fs/read-deny.js";
33
- import type { SkillGrantLane, SkillGrantTable } from "../skill-grant.js";
33
+ import { type SkillGrantLane, type SkillGrantSource, type SkillGrantTable } from "../skill-grant.js";
34
+ import type { GrantBindingOf } from "../skill-grant-binding.js";
34
35
  import { type AllowEntryLane, type AllowEntryTable } from "../allow-entry.js";
35
36
  import type { InheritedGate, IrreversibilityTier, PrepareResume, RunInternals, StopForDenialLimit } from "./contracts.js";
36
37
  import { type Arbitration } from "../ask-origin.js";
@@ -122,6 +123,8 @@ export interface PreparePolicyChainInput {
122
123
  /** borrowed-readonly — the run's skill grant table; this phase binds its lane to the call facts the person's rule lane
123
124
  * reads (root, session, live cwd, the execution environment's home). Not written here. */
124
125
  skillGrants: SkillGrantTable;
126
+ /** borrowed-readonly — the loaded skills (the table the `Skill` tool activates from); {@link PreparePolicyChainResult.grantBindingOf} reads them. */
127
+ skillSpecs: readonly SkillGrantSource[];
125
128
  /** borrowed-readonly — the request's and the deployment's allow-entry tables (prepare seeded them); this phase binds
126
129
  * their lanes to the same call facts. Not written here. */
127
130
  allowedToolTables: {
@@ -218,6 +221,9 @@ export interface PreparePolicyChainResult {
218
221
  };
219
222
  /** owned — the ONE rule-offer factory for all five ask-mint sites. */
220
223
  ruleOffersOf: ReturnType<typeof createRuleOffersOf>;
224
+ /** owned — the ONE Skill-card binding factory for the same five sites: a call to the engine's `Skill` tool carries the skill and
225
+ * its two lists (`boundGrants`) and their hash (`boundGrantDigest`) on the live ask, an ancestor's and the parked row alike. */
226
+ grantBindingOf: GrantBindingOf;
221
227
  /** owned — the personal permission-rule lane, or undefined. */
222
228
  permissionRuleLane: PermissionRuleLanes["personal"];
223
229
  /** owned — the org permission-rule lane (`Prepared.permissionRuleOrg`), or undefined. */
@@ -7,14 +7,16 @@ import { assertHookSeatsCallable } from "../hook-seats.js";
7
7
  import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "../permission-rules.js";
8
8
  import { isIsolated } from "../remote-env.js";
9
9
  import { askApproverIdentity, carryEngineAttestations, carryPrecedingSettlement, checkToolPolicyProjection, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, durableParkOptInDeclared, isLiveApproverSeat, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, wrapToolPolicy, attestAskUnresolvableCause } from "../tool-policy.js";
10
- import { judgeRuleToolName } from "../tool-name-aliases.js";
10
+ import { judgeRuleToolName, mountedToolNames } from "../tool-name-aliases.js";
11
11
  import { NAMESPACED_NAME_SHAPES } from "../protocol-table.js";
12
12
  import { emitTrace } from "../trace.js";
13
+ import { skillGrantBindingMembers } from "../skill-grant.js";
14
+ import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
13
15
  import { autoStrippedRule, cardDisclosureOf } from "../allow-entry.js";
14
16
  import { arbitrateByMode, askClassMembersOf, askMandateMember } from "../ask-origin.js";
15
17
  import { createModeGatePolicy, mountedToolDeclaresReadOnly } from "../mode-policy.js";
16
18
  const ancestorFacts = (ask) => ({ ...(ask ?? {}), ancestorResolved: true });
17
- import { headlessDenyAtFold, headlessDenyAtRecheck, INHERITED_STATION_FACTS, inheritedAskCarry, inheritedAskFallbackMessage, judgeInheritedClassifier, lateAskSettlementObserver, settleDenialLimitFallback } from "./denial-limit-arms.js";
19
+ import { headlessDenyAtFold, headlessDenyAtRecheck, INHERITED_STATION_FACTS, inheritedAskCarry, inheritedAskFallbackMessage, judgeInheritedClassifier, lateAskSettlementObserver, resolveInheritedAsk, settleDenialLimitFallback } from "./denial-limit-arms.js";
18
20
  import { settleReadRootGrant } from "./read-root-grant.js";
19
21
  import { askGrantShapeOf } from "./inherited-ask-grants.js";
20
22
  import { createPermissionRuleLanes, inheritedAskRuleEvidence, createRuleOffersOf } from "./permission-rule-lanes.js";
@@ -22,7 +24,7 @@ import { createLiveSessionRules, createSessionRulePolicy } from "./session-rule-
22
24
  import { thrownText } from "../thrown-value.js";
23
25
  import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
24
26
  export async function preparePolicyChain(input) {
25
- const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
27
+ const { spec, deps, internals, sessionId, runId, hostTaskId, taskRootFinal, additionalRootsCanonical, executionEnv, lockedPreflight, tools, harnessTools, toolEffects, egressTools, irreversibilityTier, axisExplicitNegatives, shellGatedTools, toolMarkSources, envHandToolNames, handsCwdRef, readDenyMatcher, announceRuleStoreSkipped, questionToolMounted, runtimeCaps, inheritedAncestorRules, ancestorSessionRulesRef, inheritedParentConstraints, seedInheritedGate, skillGrants, skillSpecs, allowedToolTables, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, autoModeDecider, permissionMode, scratchpadDir, toolRosterDeltas, stopForDenialLimit, delegation, abortController, now, ownSessionRulesRef } = input;
26
28
  const ancestorArbitrations = new Map();
27
29
  const arbitrateInherited = (ask, toolCallId, judged) => {
28
30
  const verdict = arbitrateByMode(permissionMode.current, judged.classification, { origin: judged.origin, ...askMandateMember(ancestorFacts(judged.ask.action === "ask" ? judged.ask : undefined)) });
@@ -37,7 +39,7 @@ export async function preparePolicyChain(input) {
37
39
  const auditPolicyNames = (auditee) => {
38
40
  const nameGroups = toolPolicyNameSets(auditee);
39
41
  if (nameGroups.length > 0) {
40
- const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
42
+ const known = mountedToolNames(tools);
41
43
  for (const t of harnessTools)
42
44
  known.add(t.name);
43
45
  const unmatched = new Set();
@@ -46,9 +48,24 @@ export async function preparePolicyChain(input) {
46
48
  for (const n of list ?? []) {
47
49
  const verdict = judgeRuleToolName(n, known);
48
50
  if (verdict.kind !== "current") {
49
- const err = new Error(verdict.kind === "retired"
50
- ? `tool policy ${kind}-list entry "${n}" is a RETIRED tool name (${verdict.guidance}) and matches nothing in this run's roster — legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Update the deployment's rule to the current name.`
51
- : `tool policy ${kind}-list entry "${n}" carries the "__" namespace separator but no protocol prefix, and matches nothing in this run's roster — protocol tools are named ${NAMESPACED_NAME_SHAPES}, and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with the owning protocol's marker.`);
51
+ const entry = `tool policy ${kind}-list entry "${n}"`;
52
+ let message;
53
+ switch (verdict.kind) {
54
+ case "retired":
55
+ message = `${entry} is a RETIRED tool name (${verdict.guidance}) and matches nothing in this run's roster — legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Update the deployment's rule to the current name.`;
56
+ break;
57
+ case "unprefixed_namespace":
58
+ message = `${entry} carries the "__" namespace separator but no protocol prefix, and matches nothing in this run's roster — protocol tools are named ${NAMESPACED_NAME_SHAPES}, and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with the owning protocol's marker.`;
59
+ break;
60
+ case "unmintable_segment":
61
+ message = `${entry} can match no tool, ever (${verdict.guidance}), so it would silently guard nothing.${verdict.currentName !== undefined ? ` Write "${verdict.currentName}".` : ""}`;
62
+ break;
63
+ default: {
64
+ const exhaustive = verdict;
65
+ return exhaustive;
66
+ }
67
+ }
68
+ const err = new Error(message);
52
69
  err.code = "config.legacy_tool_name";
53
70
  throw err;
54
71
  }
@@ -285,6 +302,7 @@ export async function preparePolicyChain(input) {
285
302
  return {};
286
303
  return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
287
304
  };
305
+ const grantBindingOf = (call, args) => skillGrantBindingMembers(call, args, skillSpecs, SKILL_TOOL_NAME);
288
306
  const inheritedStationFacts = (call) => {
289
307
  const seat = seatMarksOf(call);
290
308
  return {
@@ -374,7 +392,7 @@ export async function preparePolicyChain(input) {
374
392
  }
375
393
  if (judged.kind === "deny")
376
394
  return vetoed(judged.result);
377
- const { ask: editAsk, fallback: editFallback, mintedHere: editMintedHere } = judged;
395
+ const { ask: editAsk } = judged;
378
396
  re = editAsk;
379
397
  const editArbitrated = arbitrateInherited(re, creq.toolCallId, judged);
380
398
  if (editArbitrated !== undefined) {
@@ -382,21 +400,22 @@ export async function preparePolicyChain(input) {
382
400
  carryPrecedingSettlement(settledBy, out);
383
401
  return out;
384
402
  }
385
- const rr = await resolveAsk({
403
+ const { resolved: rr, judged: settled } = await resolveInheritedAsk(judged, (j) => resolveAsk({
386
404
  toolName: creq.toolName,
387
405
  toolCallId: creq.toolCallId,
388
406
  args: editArgs,
389
- ...ruleOffersOf(creq, editArgs, ancestorFacts(re)),
390
- ...cardDisclosureOf(re, inheritedAskFallbackMessage(creq.toolName)),
407
+ ...ruleOffersOf(creq, editArgs, ancestorFacts(j.ask)),
408
+ ...cardDisclosureOf(j.ask, inheritedAskFallbackMessage(creq.toolName)),
391
409
  ...askSourceIdentity(),
392
410
  ...riskAxesOf(creq),
393
- ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
394
- ...askMandateMember(ancestorFacts(re)),
395
- ...inheritedAskCarry(judged, isLiveApproverSeat(onAskOf), ancestorTracker),
411
+ ...grantBindingOf(creq, editArgs),
412
+ ...(j.ask.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
413
+ ...askMandateMember(ancestorFacts(j.ask)),
414
+ ...inheritedAskCarry(j, isLiveApproverSeat(onAskOf), ancestorTracker),
396
415
  ruleEvidence: inheritedAskEvidence,
397
- }, onAskOf, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError }));
416
+ }, onAskOf, csignal ?? abortController.signal, lateAskSettlementObserver({ toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice, onError: deps.onError })), ancestorTracker, creq.toolName, inheritedStationFacts(creq));
398
417
  void settleReadRootGrant(rr.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
399
- settleDenialLimitFallback({ fallback: editFallback, mintedHere: editMintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
418
+ settleDenialLimitFallback({ fallback: settled.fallback, mintedHere: settled.mintedHere, tracker: ancestorTracker, resolved: rr, headless: headlessDenyAtRecheck, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
400
419
  if (rr.action !== "allow")
401
420
  return rr;
402
421
  settledBy = rr;
@@ -451,7 +470,7 @@ export async function preparePolicyChain(input) {
451
470
  return judged.result;
452
471
  if (judged.kind === "deny")
453
472
  return judged.result;
454
- const { ask: inheritedAsk, fallback, mintedHere } = judged;
473
+ const { ask: inheritedAsk, fallback } = judged;
455
474
  if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) {
456
475
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
457
476
  return { action: "allow", decisionReason: "sandbox" };
@@ -461,33 +480,34 @@ export async function preparePolicyChain(input) {
461
480
  return arbitrated;
462
481
  const presentedArgs = creq.args;
463
482
  const askT0 = now();
464
- const resolved = await resolveAsk({
483
+ const { resolved, judged: settled } = await resolveInheritedAsk(judged, (j) => resolveAsk({
465
484
  toolName: creq.toolName,
466
485
  toolCallId: creq.toolCallId,
467
486
  args: presentedArgs,
468
- ...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
469
- ...cardDisclosureOf(inheritedAsk, inheritedAskFallbackMessage(creq.toolName)),
487
+ ...ruleOffersOf(creq, presentedArgs, ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)),
488
+ ...cardDisclosureOf(j.ask, inheritedAskFallbackMessage(creq.toolName)),
470
489
  ...askSourceIdentity(),
471
490
  ...riskAxesOf(creq),
472
- ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
473
- ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
474
- ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
491
+ ...grantBindingOf(creq, presentedArgs),
492
+ ...(j.ask.action === "ask" && j.ask.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
493
+ ...askMandateMember(ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)),
494
+ ...inheritedAskCarry(j, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
475
495
  ruleEvidence: inheritedAskEvidence,
476
- }, 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 }));
496
+ }, 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 })), pc.autoMode?.denialTracking, creq.toolName, inheritedStationFacts(creq));
477
497
  void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
478
498
  const askWaitMs = Math.max(0, now() - askT0);
479
499
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
480
500
  if (markInheritedUnavailable(creq.toolCallId))
481
- return inheritedAsk;
501
+ return settled.ask;
482
502
  return markerChannelFullDeny(creq);
483
503
  }
484
- settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
504
+ settleDenialLimitFallback({ fallback: settled.fallback, mintedHere: settled.mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
485
505
  if (resolved.action !== "allow")
486
506
  return resolved;
487
507
  if (resolved.updatedInput !== undefined) {
488
508
  return recheckApprovedEdit(pc.policy, pc.onAsk, creq, resolved.updatedInput, resolved, csignal, pc.autoMode);
489
509
  }
490
- recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved, askClassMembersOf(judged.classification).classes);
510
+ recordInheritedAskGrant(creq.toolCallId, pc.onAsk, resolved.presentedInput, askWaitMs, resolved, askClassMembersOf(settled.classification).classes);
491
511
  return { action: "allow", decisionReason: "mode" };
492
512
  },
493
513
  }
@@ -524,7 +544,7 @@ export async function preparePolicyChain(input) {
524
544
  return judged.result;
525
545
  if (judged.kind === "deny")
526
546
  return judged.result;
527
- const { ask: inheritedAsk, fallback, mintedHere } = judged;
547
+ const { ask: inheritedAsk, fallback } = judged;
528
548
  if (fallback === undefined && sandboxAdmissionArmed && policyAskClassOf(pc.policy) === "sandbox_local" && !sandboxBoundaryCapable(creq) && judged.classification.row.sandboxAdmission) {
529
549
  recordAncestorSandboxAdmission(creq.toolCallId, creq.toolName);
530
550
  return decision.updatedInput !== undefined ? { action: "allow", updatedInput: decision.updatedInput, decisionReason: "sandbox" } : { action: "allow", decisionReason: "sandbox" };
@@ -533,27 +553,28 @@ export async function preparePolicyChain(input) {
533
553
  if (arbitrated !== undefined)
534
554
  return arbitrated;
535
555
  const askT0 = now();
536
- const resolved = await resolveAsk({
556
+ const { resolved, judged: settled } = await resolveInheritedAsk(judged, (j) => resolveAsk({
537
557
  toolName: creq.toolName,
538
558
  toolCallId: creq.toolCallId,
539
559
  args: presentedArgs,
540
- ...ruleOffersOf(creq, presentedArgs, ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
541
- ...cardDisclosureOf(inheritedAsk, inheritedAskFallbackMessage(creq.toolName)),
560
+ ...ruleOffersOf(creq, presentedArgs, ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)),
561
+ ...cardDisclosureOf(j.ask, inheritedAskFallbackMessage(creq.toolName)),
542
562
  ...askSourceIdentity(),
543
563
  ...riskAxesOf(creq),
544
- ...(inheritedAsk.action === "ask" && inheritedAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
545
- ...askMandateMember(ancestorFacts(inheritedAsk.action === "ask" ? inheritedAsk : undefined)),
546
- ...inheritedAskCarry(judged, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
564
+ ...grantBindingOf(creq, presentedArgs),
565
+ ...(j.ask.action === "ask" && j.ask.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
566
+ ...askMandateMember(ancestorFacts(j.ask.action === "ask" ? j.ask : undefined)),
567
+ ...inheritedAskCarry(j, isLiveApproverSeat(pc.onAsk), pc.autoMode?.denialTracking),
547
568
  ruleEvidence: inheritedAskEvidence,
548
- }, 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 }));
569
+ }, 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 })), pc.autoMode?.denialTracking, creq.toolName, inheritedStationFacts(creq));
549
570
  void settleReadRootGrant(resolved.readRootGrant, undefined, { toolName: creq.toolName, toolCallId: creq.toolCallId, sessionId, runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), onNotice: deps.onNotice });
550
571
  const askWaitMs = Math.max(0, now() - askT0);
551
572
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
552
573
  if (markInheritedUnavailable(creq.toolCallId))
553
- return inheritedAsk;
574
+ return settled.ask;
554
575
  return markerChannelFullDeny(creq);
555
576
  }
556
- settleDenialLimitFallback({ fallback, mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
577
+ settleDenialLimitFallback({ fallback: settled.fallback, mintedHere: settled.mintedHere, tracker: pc.autoMode?.denialTracking, resolved, headless: headlessDenyAtFold, stop: stopForDenialLimit, toolName: creq.toolName, toolCallId: creq.toolCallId });
557
578
  if (resolved.action !== "allow")
558
579
  return resolved;
559
580
  if (resolved.updatedInput !== undefined) {
@@ -667,7 +688,7 @@ export async function preparePolicyChain(input) {
667
688
  return decision;
668
689
  })
669
690
  : foldedPolicy;
670
- return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
691
+ return { hooks, hookTimeoutMs, preToolUseObservational, ownGatePreToolUse, basePolicyForResumeEdit, denyNarrowingPolicy, skillGrantLane, allowEntryLanes, policyLayers, effectivePolicy, inheritedUnavailableAsks, inheritedAskGrants, askSourceIdentity, riskAxesOf, ruleOffersOf, grantBindingOf, permissionRuleLane, permissionRuleOrgLane, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, foldAskClasses, ancestorSandboxAdmissions, ancestorArbitrations };
671
692
  }
672
693
  const advisedPolicyNames = new WeakMap();
673
694
  const ADVISED_KEYS_CAP = 64;