@sema-agent/core 7.6.0 → 7.6.2

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 (78) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +2 -3
  4. package/dist/agents/repair-loop.d.ts +2 -2
  5. package/dist/agents/retain-ledger.d.ts +2 -3
  6. package/dist/agents/send-message-tool.d.ts +2 -2
  7. package/dist/agents/session-util.d.ts +2 -2
  8. package/dist/agents/subagent.d.ts +3 -4
  9. package/dist/agents/teacher.d.ts +2 -2
  10. package/dist/agents/team.d.ts +2 -2
  11. package/dist/agents/verify.d.ts +5 -6
  12. package/dist/core/agent-definition.d.ts +172 -0
  13. package/dist/core/agent-definition.js +1 -0
  14. package/dist/core/checkpoint-store.d.ts +8 -4
  15. package/dist/core/delegation-frames.d.ts +298 -0
  16. package/dist/core/delegation-frames.js +21 -0
  17. package/dist/core/engine-notice.d.ts +555 -0
  18. package/dist/core/engine-notice.js +55 -0
  19. package/dist/core/gate-fold.d.ts +12 -0
  20. package/dist/core/gate-fold.js +158 -0
  21. package/dist/core/gate-lanes.d.ts +93 -0
  22. package/dist/core/gate-lanes.js +626 -0
  23. package/dist/core/hands-band.d.ts +134 -0
  24. package/dist/core/hands-band.js +1 -0
  25. package/dist/core/hooks.d.ts +20 -101
  26. package/dist/core/hooks.js +53 -854
  27. package/dist/core/mcp-failure.d.ts +43 -5
  28. package/dist/core/mcp-failure.js +31 -14
  29. package/dist/core/mcp-server-spec.d.ts +217 -0
  30. package/dist/core/mcp-server-spec.js +1 -0
  31. package/dist/core/model-seat.d.ts +99 -0
  32. package/dist/core/model-seat.js +1 -0
  33. package/dist/core/reminder-mint.d.ts +10 -0
  34. package/dist/core/reminder-mint.js +3 -0
  35. package/dist/core/runner/contracts.d.ts +382 -6
  36. package/dist/core/runner/gate-exit.d.ts +177 -9
  37. package/dist/core/runner/gate-exit.js +70 -1
  38. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  39. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  40. package/dist/core/runner/prepare-run-refs.d.ts +12 -0
  41. package/dist/core/runner/prepare-run-refs.js +5 -0
  42. package/dist/core/runner/prepare-task.d.ts +2 -2
  43. package/dist/core/runner/runtask.d.ts +4 -71
  44. package/dist/core/runner/runtask.js +18 -6
  45. package/dist/core/runner-deps.d.ts +1416 -0
  46. package/dist/core/runner-deps.js +1 -0
  47. package/dist/core/runtime-caps.d.ts +164 -0
  48. package/dist/core/runtime-caps.js +1 -0
  49. package/dist/core/task-event.d.ts +910 -0
  50. package/dist/core/task-event.js +1 -0
  51. package/dist/core/task-limits.d.ts +110 -0
  52. package/dist/core/task-limits.js +1 -0
  53. package/dist/core/task-result.d.ts +809 -0
  54. package/dist/core/task-result.js +1 -0
  55. package/dist/core/task-spec.d.ts +1370 -0
  56. package/dist/core/task-spec.js +1 -0
  57. package/dist/core/task-stream.d.ts +382 -0
  58. package/dist/core/task-stream.js +1 -0
  59. package/dist/core/tool-spec.d.ts +1174 -0
  60. package/dist/core/tool-spec.js +1 -0
  61. package/dist/core/types.d.ts +26 -7691
  62. package/dist/core/types.js +2 -76
  63. package/dist/core/warm-resume.d.ts +2 -2
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +1 -1
  66. package/dist/orchestration/goal.d.ts +2 -2
  67. package/dist/orchestration/run-spec.d.ts +2 -2
  68. package/dist/orchestration/run-workflow-tool.d.ts +3 -3
  69. package/dist/orchestration/workflow.d.ts +4 -4
  70. package/dist/scenarios/scenario-registry.d.ts +3 -3
  71. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  72. package/dist/server/http.d.ts +2 -2
  73. package/dist/stores/file/fs-atomic.d.ts +88 -12
  74. package/dist/stores/file/fs-atomic.js +184 -55
  75. package/dist/stores/file/index.d.ts +1 -0
  76. package/dist/stores/file/index.js +1 -0
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +9 -1
@@ -1,14 +1,14 @@
1
- import { decisionText, describeThrown, refuseOutOfContractDecision } from "./tool-policy.js";
2
- import { createSettlementLedger, directDeny, mintGateOutcome } from "./runner/gate-exit.js";
1
+ import { decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
2
+ import { createSettlementLedger, engineFailClosedExit, hookDenyExit, traceHookCrash } from "./runner/gate-exit.js";
3
+ import { runGateFold } from "./gate-fold.js";
4
+ import { runGateLanes } from "./gate-lanes.js";
5
+ export { normalizeOrgGateVerdict, normalizePersistedRuleHit, persistedRuleMandateOf } from "./gate-lanes.js";
6
+ export { cloneObserverInput } from "./runner/gate-exit.js";
3
7
  import { brandPolicyAskClass } from "./ask-class.js";
4
- import { askOriginOf, classifierMayAnswer, isAskOrigin } from "./ask-origin.js";
5
- import { denialLimitFallbackMessage, unarmedWindow } from "./auto-mode.js";
8
+ import { isAskOrigin } from "./ask-origin.js";
6
9
  import { inlineUntrusted } from "./untrusted-text.js";
7
- import { mintSystemReminder } from "./reminder-mint.js";
8
- import { PROBE_REASON_MAX, normalizeProbeCause } from "./checkpoint-store.js";
9
- import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
10
+ export { formatHookFeedback } from "./reminder-mint.js";
10
11
  import { createSafeNotifier } from "./safe-notify.js";
11
- import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
12
12
  export const DEFAULT_HOOK_TIMEOUT_MS = 600_000;
13
13
  export const MAX_HOOK_TIMEOUT_MS = 2_147_483_647;
14
14
  const POST_ABORT_GRACE_MS = 1_000;
@@ -95,18 +95,6 @@ export function hookSeatExpiredError(seat, timeoutMs, cause, consequence) {
95
95
  ? `the deployment's ${seat} hook did not answer within its ${timeoutMs}ms bound (Hooks.timeoutMs) — ${consequence}`
96
96
  : `the deployment's ${seat} hook had not answered when the task was cancelled — ${consequence}`);
97
97
  }
98
- export function cloneObserverInput(input) {
99
- try {
100
- return structuredClone(input);
101
- }
102
- catch {
103
- if (Array.isArray(input))
104
- return [...input];
105
- if (input !== null && typeof input === "object")
106
- return { ...input };
107
- return input;
108
- }
109
- }
110
98
  const reflectApply = Reflect.apply;
111
99
  const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
112
100
  const getPrototypeOf = Object.getPrototypeOf;
@@ -180,146 +168,6 @@ export function mintHookInvocationIdentity(facts) {
180
168
  ...(facts.parentToolCallId !== undefined ? { parentToolCallId: facts.parentToolCallId } : {}),
181
169
  }));
182
170
  }
183
- export function formatHookFeedback(text, mark) {
184
- return mintSystemReminder(text, mark);
185
- }
186
- const PARK_FAILURE_CAUSE_MAX = 600;
187
- function withParkFailureCause(reason, parkFailed) {
188
- if (parkFailed === undefined)
189
- return reason;
190
- return (`${reason} — note: a durable approval park was attempted for this call FIRST and could not be minted ` +
191
- `(${inlineUntrusted(parkFailed, PARK_FAILURE_CAUSE_MAX)}), so the refusal above is what the fallback had ` +
192
- `left to say, not the reason the call stopped.`);
193
- }
194
- function isPlainOwnRecord(x) {
195
- try {
196
- if (x === null || typeof x !== "object" || Array.isArray(x))
197
- return false;
198
- const p = Object.getPrototypeOf(x);
199
- return p === Object.prototype || p === null;
200
- }
201
- catch {
202
- return false;
203
- }
204
- }
205
- function ownDataValue(obj, key) {
206
- try {
207
- const d = Object.getOwnPropertyDescriptor(obj, key);
208
- return d && "value" in d ? d.value : undefined;
209
- }
210
- catch {
211
- return undefined;
212
- }
213
- }
214
- export function normalizeOrgGateVerdict(answer, unreadable) {
215
- const clean = (v) => Object.assign(Object.create(null), v);
216
- const readable = (x) => {
217
- try {
218
- return x !== null && (typeof x === "object" || typeof x === "function") && !Array.isArray(x);
219
- }
220
- catch {
221
- return false;
222
- }
223
- };
224
- if (!readable(answer))
225
- return clean({ status: "unavailable", disclosures: [unreadable] });
226
- const status = ownDataValue(answer, "status");
227
- if (status === "unavailable") {
228
- const disclosures = ownDataValue(answer, "disclosures");
229
- let lines = [];
230
- if (Array.isArray(disclosures)) {
231
- try {
232
- for (let i = 0; i < disclosures.length; i++) {
233
- const d = disclosures[i];
234
- if (typeof d === "string")
235
- lines.push(d);
236
- }
237
- }
238
- catch {
239
- lines = [];
240
- }
241
- }
242
- return clean({ status: "unavailable", disclosures: lines.length > 0 ? lines : [unreadable] });
243
- }
244
- if (status !== "available")
245
- return clean({ status: "unavailable", disclosures: [unreadable] });
246
- const revision = ownDataValue(answer, "revision");
247
- const revisionCell = typeof revision === "number" && Number.isFinite(revision) ? { revision } : {};
248
- const verdict = ownDataValue(answer, "verdict");
249
- if (verdict === undefined)
250
- return clean({ status: "available", ...revisionCell });
251
- if (!readable(verdict))
252
- return clean({ status: "unavailable", disclosures: [unreadable] });
253
- const behavior = ownDataValue(verdict, "behavior");
254
- const rule = ownDataValue(verdict, "rule");
255
- if ((behavior !== "deny" && behavior !== "ask") || typeof rule !== "string")
256
- return clean({ status: "unavailable", disclosures: [unreadable] });
257
- return clean({ status: "available", verdict: clean({ behavior: behavior, rule }), ...revisionCell });
258
- }
259
- function copiedUsableDots(dots) {
260
- let copied;
261
- try {
262
- if (Array.isArray(dots)) {
263
- const walked = [];
264
- for (let i = 0; i < dots.length; i++) {
265
- const d = dots[i];
266
- walked.push(isPlainOwnRecord(d) ? { actor: ownDataValue(d, "actor"), counter: ownDataValue(d, "counter") } : { actor: undefined, counter: undefined });
267
- }
268
- copied = walked;
269
- }
270
- }
271
- catch {
272
- copied = undefined;
273
- }
274
- const usable = copied?.every((d) => typeof d.actor === "string" && typeof d.counter === "number" && Number.isFinite(d.counter)) === true && copied.length > 0;
275
- return usable ? { dots: copied } : {};
276
- }
277
- export function normalizePersistedRuleHit(hit) {
278
- if (typeof hit === "string")
279
- return { hit: { rules: [{ rule: hit }] } };
280
- if (!isPlainOwnRecord(hit))
281
- return {};
282
- if (ownDataValue(hit, "unreadable") === true)
283
- return { unreadable: true };
284
- const rulesRaw = ownDataValue(hit, "rules");
285
- if (rulesRaw === undefined) {
286
- const coverageRaw = ownDataValue(hit, "segmentCoverage");
287
- let rows;
288
- try {
289
- if (Array.isArray(coverageRaw)) {
290
- const walked = [];
291
- for (let i = 0; i < coverageRaw.length; i++) {
292
- const r = coverageRaw[i];
293
- walked.push(isPlainOwnRecord(r) ? { segment: ownDataValue(r, "segment"), covered: ownDataValue(r, "covered") } : { segment: undefined, covered: undefined });
294
- }
295
- rows = walked.every((r) => typeof r.segment === "string" && typeof r.covered === "boolean") ? walked : undefined;
296
- }
297
- }
298
- catch {
299
- rows = undefined;
300
- }
301
- return rows !== undefined && rows.length > 0 ? { coverage: rows.map((r) => ({ segment: r.segment, covered: r.covered })) } : {};
302
- }
303
- let members;
304
- try {
305
- if (Array.isArray(rulesRaw)) {
306
- const walked = [];
307
- for (let i = 0; i < rulesRaw.length; i++) {
308
- const m = rulesRaw[i];
309
- walked.push(isPlainOwnRecord(m) ? { rule: ownDataValue(m, "rule"), dots: ownDataValue(m, "dots") } : { rule: undefined, dots: undefined });
310
- }
311
- members = walked;
312
- }
313
- }
314
- catch {
315
- members = undefined;
316
- }
317
- if (members === undefined || members.length === 0)
318
- return {};
319
- if (!members.every((m) => typeof m.rule === "string"))
320
- return {};
321
- return { hit: { rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } };
322
- }
323
171
  function preToolUseCrashReason(subject, err) {
324
172
  const raw = err instanceof Error ? err.message.trim() || err.name : String(err);
325
173
  const cause = inlineUntrusted(raw, 200);
@@ -383,49 +231,6 @@ export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash, timeo
383
231
  },
384
232
  }, "hook");
385
233
  }
386
- function traceHookCrash(input, err, notifier) {
387
- notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
388
- }
389
- function withProbeTimeout(p, ms, signal) {
390
- if (ms === undefined && signal === undefined)
391
- return p;
392
- return new Promise((resolve, reject) => {
393
- let settled = false;
394
- const done = (fn, v) => {
395
- if (settled)
396
- return;
397
- settled = true;
398
- if (timer !== undefined)
399
- clearTimeout(timer);
400
- signal?.removeEventListener("abort", onAbort);
401
- fn(v);
402
- };
403
- const onAbort = () => done(reject, new Error("reversibilityProbe aborted (task ended)"));
404
- const timer = ms !== undefined
405
- ? setTimeout(() => done(reject, new Error(`reversibilityProbe timed out after ${ms}ms`)), ms)
406
- : undefined;
407
- if (signal?.aborted) {
408
- onAbort();
409
- return;
410
- }
411
- signal?.addEventListener("abort", onAbort);
412
- p.then((v) => done(resolve, v), (err) => done(reject, err));
413
- });
414
- }
415
- const DEFAULT_PROBE_TIMEOUT_MS = 30_000;
416
- export function persistedRuleMandateOf(marks) {
417
- return marks.probeMandated === true
418
- ? "probe_mandate"
419
- : marks.egress === true
420
- ? "tool_marks"
421
- : marks.shellGated === true
422
- ? marks.irreversibility === "always"
423
- ? "operator_always"
424
- : undefined
425
- : marks.irreversibility === "always" || marks.irreversibility === "maybe"
426
- ? "tool_marks"
427
- : undefined;
428
- }
429
234
  export function askCarryRowMembers(carry) {
430
235
  if (carry === undefined)
431
236
  return {};
@@ -434,9 +239,6 @@ export function askCarryRowMembers(carry) {
434
239
  ...(isAskOrigin(carry.origin) ? { origin: carry.origin } : {}),
435
240
  };
436
241
  }
437
- function parkWith(suspendAsk, parkArgs, carry) {
438
- return suspendAsk(...parkArgs, carry);
439
- }
440
242
  export async function runToolGate(input) {
441
243
  const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
442
244
  const { toolCallId, toolName } = event;
@@ -452,12 +254,7 @@ export async function runToolGate(input) {
452
254
  ...(input.hookEnv !== undefined ? { env: input.hookEnv } : {}),
453
255
  ...(input.identity !== undefined ? { identity: input.identity } : {}),
454
256
  });
455
- let currentInput = event.input;
456
- const preToolContext = [];
457
- let hookAsk;
458
- let parkFailed;
459
- let deniedBy;
460
- const ledger = createSettlementLedger({ toolCallId, toolName });
257
+ const modelInput = event.input;
461
258
  const notifier = createSafeNotifier(input.onNotifyError !== undefined ? { onError: input.onNotifyError } : undefined);
462
259
  const hookSeatMs = resolveHookTimeoutMs(input.hookTimeoutMs, (err) => traceHookCrash(input, err, notifier));
463
260
  const seatBound = (abortEnds) => ({
@@ -481,669 +278,71 @@ export async function runToolGate(input) {
481
278
  throw hookSeatExpiredError("permissionDenied", hookSeatMs, seat.cause, "the deny observation was abandoned; the deny itself is unchanged");
482
279
  }, "toolGate.permissionDenied");
483
280
  };
484
- if (preToolUse) {
281
+ const screening = preToolUse !== undefined
282
+ ? {
283
+ screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, args, hookCtx(sig))),
284
+ screened: screenPreToolUseResult,
285
+ crashReason: preToolUseCrashReason,
286
+ unansweredReason: preToolUseUnansweredBlock,
287
+ }
288
+ : undefined;
289
+ const pass = {
290
+ input,
291
+ event,
292
+ adjudicate,
293
+ resolveAsk,
294
+ suspendAsk,
295
+ toolName,
296
+ toolCallId,
297
+ callSignal,
298
+ notifier,
299
+ ledger: createSettlementLedger({ toolCallId, toolName }),
300
+ notifyPermissionDenied: notifyPermissionDeniedSeat,
301
+ screening,
302
+ afterFold: runGateLanes,
303
+ currentInput: modelInput,
304
+ req: { toolName, args: modelInput, toolCallId },
305
+ preToolContext: [],
306
+ hookAsk: undefined,
307
+ parkFailed: undefined,
308
+ deniedBy: undefined,
309
+ decision: { action: "allow" },
310
+ tightenedBy: undefined,
311
+ policyRewrite: undefined,
312
+ };
313
+ if (screening !== undefined) {
485
314
  let r;
486
315
  let unanswered;
487
316
  try {
488
- const seat = await runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, currentInput, hookCtx(sig)));
317
+ const seat = await screening.screen(pass.currentInput);
489
318
  if (seat.expired)
490
319
  unanswered = seat.cause;
491
320
  else
492
- r = screenPreToolUseResult(seat.value);
321
+ r = screening.screened(seat.value);
493
322
  }
494
323
  catch (err) {
495
- const reason = preToolUseCrashReason(`this call to "${toolName}"`, err);
324
+ const reason = screening.crashReason(`this call to "${toolName}"`, err);
496
325
  traceHookCrash(input, err, notifier);
497
- const gate = directDeny("hook");
498
- await notifyPermissionDeniedSeat({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
499
- return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, preToolContext };
326
+ return await engineFailClosedExit(pass, reason);
500
327
  }
501
328
  if (unanswered !== undefined) {
502
- const reason = await preToolUseUnansweredBlock(`this call to "${toolName}"`, unanswered);
503
- const gate = directDeny("hook");
504
- await notifyPermissionDeniedSeat({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
505
- return { block: true, reason: formatHookFeedback(reason, input.reminderMark), gate, preToolContext };
329
+ const reason = await screening.unansweredReason(`this call to "${toolName}"`, unanswered);
330
+ return await engineFailClosedExit(pass, reason);
506
331
  }
507
332
  if (r) {
508
333
  if (r.additionalContext) {
509
- preToolContext.push(r.additionalContext);
334
+ pass.preToolContext.push(r.additionalContext);
510
335
  }
511
336
  if (r.action === "deny") {
512
- return {
513
- block: true,
514
- reason: formatHookFeedback(decisionText(r) ?? `tool "${toolName}" blocked by a PreToolUse hook`, input.reminderMark),
515
- gate: directDeny("hook"),
516
- preToolContext,
517
- };
337
+ return hookDenyExit(pass, r);
518
338
  }
519
339
  if (r.updatedInput !== undefined) {
520
- currentInput = r.updatedInput;
340
+ pass.currentInput = r.updatedInput;
521
341
  }
522
342
  if (r.action === "ask") {
523
- hookAsk = r;
343
+ pass.hookAsk = r;
524
344
  }
525
345
  }
526
346
  }
527
- const req = { toolName, args: currentInput, toolCallId };
528
- let decision = adjudicate ? await (callSignal !== undefined ? adjudicate(req, callSignal) : adjudicate(req)) : { action: "allow" };
529
- {
530
- const refusedBy = ledger.fromDecision(decision);
531
- if (refusedBy !== undefined)
532
- deniedBy = refusedBy;
533
- }
534
- let tightenedBy;
535
- let policyRewrite;
536
- if ((decision.action === "allow" || decision.action === "ask") && decision.updatedInput !== undefined) {
537
- policyRewrite = decision.updatedInput;
538
- }
539
- else if (hookAsk?.updatedInput !== undefined) {
540
- policyRewrite = hookAsk.updatedInput;
541
- }
542
- if (decision.action === "allow" && hookAsk) {
543
- decision = {
544
- action: "ask",
545
- ...(hookAsk.message !== undefined ? { message: hookAsk.message } : {}),
546
- decisionReason: "hook",
547
- ...(hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
548
- ...(hookAsk.updatedInput !== undefined ? { updatedInput: hookAsk.updatedInput } : {}),
549
- };
550
- }
551
- else if (decision.action === "ask" && hookAsk) {
552
- const hookMessage = hookAsk.message;
553
- const foldedMessage = hookMessage !== undefined && hookMessage !== decision.message
554
- ? `${decision.message !== undefined ? `${decision.message} ` : ""}(a PreToolUse hook also asks: ${hookMessage})`
555
- : decision.message;
556
- decision = {
557
- ...decision,
558
- decisionReason: "hook",
559
- ...(foldedMessage !== undefined ? { message: foldedMessage } : {}),
560
- ...(hookAsk.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
561
- };
562
- }
563
- if (input.egress && decision.action === "allow") {
564
- decision = {
565
- action: "ask",
566
- message: `tool "${toolName}" performs an external write (egress) — explicit approval required`,
567
- decisionReason: "safety",
568
- };
569
- tightenedBy = "safety";
570
- }
571
- const alreadyAsking = decision.action === "ask";
572
- if ((decision.action === "allow" || alreadyAsking) && (input.irreversibility === "always" || input.irreversibility === "maybe")) {
573
- let tighten = !alreadyAsking;
574
- let probeReason;
575
- let probeCause;
576
- let probeMandated;
577
- if (input.irreversibility === "maybe" && input.reversibilityProbe) {
578
- let reversible = false;
579
- const probeArgs = policyRewrite !== undefined ? policyRewrite : currentInput;
580
- const suppliedProbeMs = input.approvalTimeoutMs;
581
- let probeTimeoutMs;
582
- if (suppliedProbeMs === undefined) {
583
- probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS;
584
- }
585
- else if (Number.isFinite(suppliedProbeMs) && suppliedProbeMs >= 0 && suppliedProbeMs <= 2_147_483_647) {
586
- probeTimeoutMs = suppliedProbeMs;
587
- }
588
- else {
589
- probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS;
590
- traceHookCrash(input, new Error(`approvalTimeoutMs must be a non-negative finite number no greater than 2147483647 (got ${String(suppliedProbeMs)}) — the reversibilityProbe deadline falls back to the ${DEFAULT_PROBE_TIMEOUT_MS}ms default`), notifier);
591
- }
592
- try {
593
- const verdict = await withProbeTimeout(Promise.resolve(input.reversibilityProbe(probeArgs)), probeTimeoutMs, input.abortSignal);
594
- reversible = verdict?.reversible === true;
595
- if (!reversible) {
596
- const raw = verdict?.reason;
597
- if (typeof raw === "string" && raw.trim().length > 0)
598
- probeReason = inlineUntrusted(raw, PROBE_REASON_MAX);
599
- probeCause = normalizeProbeCause(verdict?.cause);
600
- if (verdict?.mandated === true)
601
- probeMandated = true;
602
- }
603
- }
604
- catch (err) {
605
- reversible = false;
606
- if (input.abortSignal?.aborted !== true)
607
- traceHookCrash(input, err, notifier);
608
- }
609
- tighten = !alreadyAsking && !reversible;
610
- }
611
- if (tighten) {
612
- decision = {
613
- action: "ask",
614
- message: `tool "${toolName}" performs an irreversible action — explicit approval required`,
615
- decisionReason: "safety",
616
- ...(probeReason !== undefined ? { probeReason } : {}),
617
- ...(probeCause !== undefined ? { probeCause } : {}),
618
- ...(probeMandated === true ? { probeMandated: true } : {}),
619
- };
620
- tightenedBy = input.shellGated === true ? "shell_gate" : "safety";
621
- }
622
- else if (alreadyAsking && probeMandated === true && decision.action === "ask") {
623
- decision = { ...decision, probeMandated: true };
624
- }
625
- }
626
- const breakerKnownOpen = (d) => {
627
- try {
628
- return d.breakerOpen();
629
- }
630
- catch {
631
- return false;
632
- }
633
- };
634
- if (input.peerMessage === true && decision.action === "allow" && input.autoMode !== undefined && !breakerKnownOpen(input.autoMode.decider)) {
635
- decision = {
636
- action: "ask",
637
- message: `tool "${toolName}" sends a message to another agent — routed for classifier review in auto mode`,
638
- decisionReason: "safety",
639
- };
640
- tightenedBy = "safety";
641
- }
642
- if (decision.action === "allow" && input.writeProtectionCheck !== undefined) {
643
- const writeProtectedHit = input.writeProtectionCheck(toolName, policyRewrite !== undefined ? policyRewrite : currentInput);
644
- if (writeProtectedHit !== null) {
645
- decision = {
646
- action: "ask",
647
- message: `tool "${toolName}" writes to a write-protected path (table entry "${writeProtectedHit.name}") — explicit approval required`,
648
- decisionReason: "safety",
649
- };
650
- tightenedBy = "safety";
651
- }
652
- }
653
- let orgRealApprovalRequired = false;
654
- const askOriginFacts = (org) => ({
655
- contentQuestion: toolName === ASK_USER_QUESTION_TOOL_NAME,
656
- markedUnresolvable: input.isMarkedUnresolvable?.(input.event.toolCallId) === true,
657
- org,
658
- tightened: tightenedBy,
659
- });
660
- let orgTightenCount = 0;
661
- let orgEvidence = (() => {
662
- const w = input.orgRules === undefined ? "not_wired" : "not_adjudicated";
663
- return { revisionAbsent: w, ruleAbsent: w };
664
- })();
665
- const mintRuleEvidence = (personal) => ({
666
- ...(orgEvidence.revision !== undefined ? { orgRevision: orgEvidence.revision } : { orgRevisionAbsent: orgEvidence.revisionAbsent ?? "not_reported" }),
667
- ...(orgEvidence.rule !== undefined ? { orgRule: orgEvidence.rule } : { orgRuleAbsent: orgEvidence.ruleAbsent ?? "not_reported" }),
668
- ...(personal.dots !== undefined ? { personalRuleDots: personal.dots } : { personalRuleDotsAbsent: personal.dotsAbsent ?? "not_reported" }),
669
- });
670
- const applyOrgLayer = async (current, args) => {
671
- if (input.orgRules === undefined)
672
- return { decision: current, org: undefined };
673
- if (toolName === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
674
- return { decision: current, org: undefined };
675
- let decided = current;
676
- let pending;
677
- try {
678
- pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId }));
679
- }
680
- catch (err) {
681
- pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] });
682
- }
683
- const raw = await settleOrgVerdictWithin(pending.catch((err) => ({
684
- status: "unavailable",
685
- disclosures: [`the org adjudication face threw: ${describeThrown(err)}`],
686
- })), {
687
- status: "unavailable",
688
- disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`],
689
- }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
690
- const answer = normalizeOrgGateVerdict(raw, "the org adjudication face answered something this gate cannot read as a verdict — treated as unreadable governance, never as an empty one");
691
- if (answer.status === "unavailable") {
692
- orgEvidence = { revisionAbsent: "unavailable", ruleAbsent: "unavailable" };
693
- orgRealApprovalRequired = true;
694
- await notifier.notifyAsync(() => input.orgRules?.onUnavailable?.({ toolName, toolCallId, message: answer.disclosures.join("; ") }), "toolGate.orgSnapshotUnavailable");
695
- if (decided.action === "allow") {
696
- decided = {
697
- action: "ask",
698
- message: `tool "${toolName}" needs approval: this deployment is org-governed and cannot currently adjudicate ` +
699
- `against an organization policy snapshot — every allow tightens to a real approval until it can`,
700
- decisionReason: ORG_UNAVAILABLE_DECISION_REASON,
701
- requiresRealApproval: true,
702
- };
703
- orgTightenCount += 1;
704
- }
705
- else if (decided.action === "ask" && decided.requiresRealApproval !== true) {
706
- decided = { ...decided, requiresRealApproval: true };
707
- orgTightenCount += 1;
708
- }
709
- return { decision: decided, org: "unavailable" };
710
- }
711
- const revisionCell = answer.revision !== undefined ? { revision: answer.revision } : { revisionAbsent: "not_reported" };
712
- if (answer.verdict === undefined) {
713
- orgEvidence = { ...revisionCell, ruleAbsent: "no_match" };
714
- return { decision: decided, org: undefined };
715
- }
716
- const { behavior, rule } = answer.verdict;
717
- orgEvidence = { ...revisionCell, rule };
718
- if (behavior === "deny") {
719
- orgTightenCount += 1;
720
- deniedBy = "org";
721
- return { decision: { action: "deny", message: `an organization policy rule (${rule}) denies this call`, decisionReason: ORG_RULE_DECISION_REASON }, org: undefined };
722
- }
723
- if (decided.action === "deny")
724
- return { decision: decided, org: undefined };
725
- orgTightenCount += 1;
726
- orgRealApprovalRequired = true;
727
- return {
728
- decision: decided.action === "ask"
729
- ? { ...decided, requiresRealApproval: true }
730
- : {
731
- action: "ask",
732
- message: `an organization policy rule (${rule}) requires approval for this call`,
733
- decisionReason: ORG_RULE_DECISION_REASON,
734
- requiresRealApproval: true,
735
- },
736
- org: "rule",
737
- };
738
- };
739
- const orgAnswer = await applyOrgLayer(decision, policyRewrite !== undefined ? policyRewrite : currentInput);
740
- decision = orgAnswer.decision;
741
- if (decision.action === "ask") {
742
- if (policyRewrite !== undefined) {
743
- currentInput = policyRewrite;
744
- req.args = policyRewrite;
745
- }
746
- const persistedRuleMandate = persistedRuleMandateOf({
747
- egress: input.egress,
748
- shellGated: input.shellGated,
749
- irreversibility: input.irreversibility,
750
- probeMandated: decision.action === "ask" && decision.probeMandated === true,
751
- });
752
- let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
753
- let laneCoverage;
754
- if (input.persistedRules &&
755
- !orgRealApprovalRequired &&
756
- decision.action === "ask" &&
757
- decision.requiresRealApproval !== true &&
758
- decision.decisionReason !== "hook" &&
759
- req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
760
- input.isMarkedUnresolvable?.(input.event.toolCallId) !== true) {
761
- const answer = await (async () => {
762
- let pendingHit;
763
- try {
764
- pendingHit = Promise.resolve(input.persistedRules?.admits(req));
765
- }
766
- catch {
767
- return { unreadable: true };
768
- }
769
- return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
770
- })();
771
- const hitRules = answer.hit?.rules;
772
- laneCoverage = answer.coverage;
773
- personalEvidence = { dotsAbsent: answer.unreadable === true ? "unavailable" : "no_match" };
774
- const disclosedRuleSet = (rules) => {
775
- const shown = rules.slice(0, 5).map((r) => inlineUntrusted(r.rule, 200));
776
- const rest = rules.length - shown.length;
777
- return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
778
- };
779
- const unionDotsOf = (rules) => {
780
- const seen = new Set();
781
- const union = [];
782
- for (const r of rules) {
783
- if (r.dots === undefined || r.dots.length === 0)
784
- return { dotsAbsent: "not_reported" };
785
- for (const d of r.dots) {
786
- const key = JSON.stringify([d.actor, d.counter]);
787
- if (!seen.has(key)) {
788
- seen.add(key);
789
- union.push(d);
790
- }
791
- }
792
- }
793
- return { dots: union };
794
- };
795
- if (hitRules !== undefined && persistedRuleMandate === undefined && decision.matchedAskRule === undefined) {
796
- decision = {
797
- action: "allow",
798
- message: hitRules.length === 1
799
- ? `a persisted allow rule (${disclosedRuleSet(hitRules)}) covers this call`
800
- : `persisted allow rules (${disclosedRuleSet(hitRules)}) cover every segment of this call`,
801
- decisionReason: "persisted_rule",
802
- ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
803
- };
804
- await notifier.notifyAsync(() => input.persistedRules?.onResolved?.({ toolName: req.toolName, toolCallId, rules: hitRules.map((r) => r.rule) }), "toolGate.persistedRuleResolved");
805
- }
806
- else if (hitRules !== undefined) {
807
- personalEvidence = unionDotsOf(hitRules);
808
- const shownRule = disclosedRuleSet(hitRules);
809
- const mandateNoun = persistedRuleMandate === "probe_mandate"
810
- ? "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule"
811
- : persistedRuleMandate === "operator_always"
812
- ? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
813
- : persistedRuleMandate !== undefined
814
- ? "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)"
815
- : "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)";
816
- decision = {
817
- ...decision,
818
- persistedRuleShadowed: shownRule,
819
- message: hitRules.length === 1
820
- ? `${decision.message !== undefined ? `${decision.message} ` : ""}(a persisted allow rule (${shownRule}) matches this call but does not clear the ask — ${mandateNoun})`
821
- : `${decision.message !== undefined ? `${decision.message} ` : ""}(persisted allow rules (${shownRule}) match this call but do not clear the ask — ${mandateNoun})`,
822
- };
823
- }
824
- }
825
- const originFacts = askOriginFacts(orgAnswer.org);
826
- if (decision.action === "ask") {
827
- const stamped = { ...decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
828
- decision = { ...stamped, origin: askOriginOf(stamped, originFacts) };
829
- }
830
- if (decision.action === "ask")
831
- ledger.supersede();
832
- if (input.autoMode && decision.action === "ask" && decision.origin !== undefined && classifierMayAnswer(decision.origin)) {
833
- const verdict = await input.autoMode.decider
834
- .decide({ req, askMessage: decisionText(decision) }, input.abortSignal)
835
- .catch(() => ({ kind: "unavailable", cause: "error" }));
836
- if (verdict.kind === "allow") {
837
- input.autoMode.denialTracking?.recordAllow();
838
- decision = {
839
- action: "allow",
840
- message: "auto-mode classifier allowed this call",
841
- decisionReason: "classifier",
842
- ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
843
- };
844
- }
845
- else if (verdict.kind === "block") {
846
- const reason = verdict.reason ? inlineUntrusted(verdict.reason) : "";
847
- const category = verdict.category ? inlineUntrusted(verdict.category) : "";
848
- const tracked = input.autoMode.denialTracking?.recordBlock();
849
- if (tracked?.limitReached === true) {
850
- const fallbackAsk = {
851
- ...decision,
852
- message: denialLimitFallbackMessage(tracked.fallback, reason || category || toolName),
853
- decisionReason: "classifier",
854
- requiresRealApproval: true,
855
- denialLimitFallback: tracked.fallback,
856
- };
857
- decision = { ...fallbackAsk, origin: askOriginOf(fallbackAsk, originFacts) };
858
- }
859
- else {
860
- decision = {
861
- action: "deny",
862
- message: `auto-mode classifier blocked this call${reason ? `: ${reason}` : category ? `: [${category}]` : ""}`,
863
- decisionReason: "classifier",
864
- };
865
- deniedBy = "classifier";
866
- }
867
- }
868
- }
869
- if (input.sandboxAdmission !== undefined &&
870
- decision.action === "ask" &&
871
- req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
872
- input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
873
- decision.requiresRealApproval !== true &&
874
- decision.decisionReason !== "hook" &&
875
- hookAsk === undefined &&
876
- input.egress !== true &&
877
- input.peerMessage !== true &&
878
- input.sandboxAdmission.boundaryCapable !== true) {
879
- const recorded = input.sandboxAdmission.askClassesOf(toolCallId);
880
- const admissible = recorded !== undefined && recorded.length > 0 && recorded.every((r) => r.cls === "sandbox_local");
881
- if (admissible) {
882
- const askClasses = recorded;
883
- input.sandboxAdmission.onAdmitted({ toolCallId, toolName, askClasses, boundary: "sandbox_internal" });
884
- preToolContext.push(`permission.sandbox_admitted: this call's pending approval was auto-admitted because the deployment declares an isolated sandbox execution env and the request stays inside it. Recorded for audit.`);
885
- decision = {
886
- action: "allow",
887
- message: "admitted inside the deployment-declared sandbox (permission.sandbox_admitted)",
888
- decisionReason: "sandbox",
889
- ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
890
- };
891
- }
892
- }
893
- const egressTool = input.egress === true;
894
- const irreversibleTool = input.irreversibility === "always" || input.irreversibility === "maybe";
895
- const safety = egressTool || irreversibleTool ? { egress: egressTool, irreversible: irreversibleTool } : undefined;
896
- const realApprovalOf = (d) => {
897
- if (d.action !== "ask" || d.requiresRealApproval !== true)
898
- return undefined;
899
- if (d.origin === undefined)
900
- throw new Error(`a real-approval ask for "${toolName}" reached a park site with no origin word — the origin stamp precedes every park`);
901
- return { origin: d.origin, ...(originFacts.org !== undefined ? { org: originFacts.org } : {}) };
902
- };
903
- const carryOf = (d) => ({
904
- ...(callSignal !== undefined ? { signal: callSignal } : {}),
905
- ...(d.action === "ask" && d.denialLimitFallback !== undefined ? { denialLimitFallback: unarmedWindow(d.denialLimitFallback) } : {}),
906
- ...(d.action === "ask" && d.origin !== undefined ? { origin: d.origin } : {}),
907
- });
908
- if (suspendAsk && decision.action === "ask") {
909
- const parkArgs = [req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined];
910
- const suspended = await parkWith(suspendAsk, parkArgs, carryOf(decision));
911
- if (suspended) {
912
- if ("parkFailed" in suspended)
913
- parkFailed = suspended.parkFailed;
914
- else
915
- return { suspend: suspended, preToolContext };
916
- }
917
- }
918
- if (decision.action === "ask" && req.toolName === ASK_USER_QUESTION_TOOL_NAME) {
919
- const outcome = input.resolveContentAsk !== undefined ? await (callSignal !== undefined ? input.resolveContentAsk(req, callSignal) : input.resolveContentAsk(req)) : { kind: "unavailable", parkDeclined: false };
920
- if (outcome.kind === "answered") {
921
- decision = { action: "allow", updatedInput: outcome.presentedInput };
922
- }
923
- else if (outcome.kind === "unavailable") {
924
- if (outcome.presentedInput !== undefined) {
925
- currentInput = outcome.presentedInput;
926
- req.args = outcome.presentedInput;
927
- }
928
- if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
929
- const parkArgs = [req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined, decision.action === "ask" ? decision.matchedAskRule : undefined, decision.action === "ask" ? decision.probeMandated : undefined];
930
- const suspended = await parkWith(suspendAsk, parkArgs, carryOf(decision));
931
- if (suspended) {
932
- if ("parkFailed" in suspended)
933
- parkFailed = suspended.parkFailed;
934
- else
935
- return { suspend: suspended, preToolContext };
936
- }
937
- }
938
- if (decision.origin === undefined)
939
- throw new Error(`the content ask for "${toolName}" reached its refusal without an origin word`);
940
- ledger.set({ settlement: { kind: input.resolveContentAsk !== undefined ? "approver_unavailable" : "no_approver", who: { party: "none" }, when: Date.now() }, origin: decision.origin });
941
- deniedBy = "ask_resolution";
942
- decision = {
943
- action: "deny",
944
- message: "AskUserQuestion could not be parked for a durable human answer on this path (no checkpoint " +
945
- "facility, or the suspend failed) and a synchronous permission approver cannot carry an answer — " +
946
- "the question was NOT shown to anyone. Proceed with your best judgment and state the assumption " +
947
- "you made.",
948
- };
949
- }
950
- else {
951
- if (outcome.presentedInput !== undefined)
952
- decision = { action: "allow", updatedInput: outcome.presentedInput };
953
- else
954
- decision = { action: "allow" };
955
- }
956
- }
957
- if (decision.action === "ask") {
958
- const askBeforeResolve = decision;
959
- const resolved = await (callSignal !== undefined ? resolveAsk(decision, req, callSignal) : resolveAsk(decision, req));
960
- ledger.fromResolved(resolved, askBeforeResolve, toolName);
961
- if (resolved.action === "deny")
962
- deniedBy = "ask_resolution";
963
- decision = resolved;
964
- if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
965
- const parkArgs = [req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.matchedAskRule : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeMandated : undefined];
966
- const suspended = await parkWith(suspendAsk, parkArgs, carryOf(askBeforeResolve));
967
- if (suspended) {
968
- if ("parkFailed" in suspended)
969
- parkFailed = suspended.parkFailed;
970
- else
971
- return { suspend: suspended, preToolContext };
972
- }
973
- }
974
- if (askBeforeResolve.action === "ask" && askBeforeResolve.denialLimitFallback !== undefined) {
975
- if (decision.action === "allow") {
976
- input.autoMode?.denialTracking?.recordAllow();
977
- }
978
- else if ((resolved.action === "deny" &&
979
- (resolved.settlement.kind === "no_approver" || resolved.settlement.kind === "blanket_allow_refused" || resolved.settlement.kind === "approver_unavailable")) ||
980
- resolved.approverUnavailable === true) {
981
- try {
982
- input.onHeadlessDenialLimit?.({ toolName, toolCallId, fallback: unarmedWindow(askBeforeResolve.denialLimitFallback) });
983
- }
984
- catch {
985
- }
986
- }
987
- }
988
- if (decision.action === "allow" && decision.updatedInput !== undefined) {
989
- let editArgs = decision.updatedInput;
990
- let editDenied;
991
- let editRewrittenSinceHuman = false;
992
- for (let round = 0;; round++) {
993
- if (round >= 3) {
994
- editDenied = {
995
- action: "deny",
996
- message: `the approval-edit chain for "${toolName}" exceeded the re-approval cap — denied fail-closed; re-submit the edited action directly`,
997
- };
998
- deniedBy = "policy";
999
- break;
1000
- }
1001
- if (preToolUse) {
1002
- let hr;
1003
- let hrUnanswered;
1004
- try {
1005
- const seat = await runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, editArgs, hookCtx(sig)));
1006
- if (seat.expired)
1007
- hrUnanswered = seat.cause;
1008
- else
1009
- hr = screenPreToolUseResult(seat.value);
1010
- }
1011
- catch (err) {
1012
- traceHookCrash(input, err, notifier);
1013
- editDenied = { action: "deny", message: preToolUseCrashReason(`the approved edit for "${toolName}"`, err) };
1014
- deniedBy = "hook";
1015
- break;
1016
- }
1017
- if (hrUnanswered !== undefined) {
1018
- editDenied = { action: "deny", message: await preToolUseUnansweredBlock(`the approved edit for "${toolName}"`, hrUnanswered) };
1019
- deniedBy = "hook";
1020
- break;
1021
- }
1022
- if (hr) {
1023
- if (hr.additionalContext)
1024
- preToolContext.push(hr.additionalContext);
1025
- if (hr.action === "deny") {
1026
- editDenied = { action: "deny", message: decisionText(hr) ?? `the approved edit for "${toolName}" was blocked by a PreToolUse hook` };
1027
- deniedBy = "hook";
1028
- break;
1029
- }
1030
- if (hr.action === "ask") {
1031
- editDenied = {
1032
- action: "deny",
1033
- message: `the approved edit for "${toolName}" raises a new PreToolUse approval requirement the original approval does not cover — denied fail-closed; re-submit the edited action directly`,
1034
- };
1035
- deniedBy = "hook";
1036
- break;
1037
- }
1038
- if (hr.updatedInput !== undefined) {
1039
- editArgs = hr.updatedInput;
1040
- editRewrittenSinceHuman = true;
1041
- }
1042
- }
1043
- }
1044
- if (!adjudicate && input.orgRules === undefined)
1045
- break;
1046
- let recheck = { action: "allow" };
1047
- if (adjudicate) {
1048
- try {
1049
- recheck = await (callSignal !== undefined ? adjudicate({ toolName, args: editArgs, toolCallId }, callSignal) : adjudicate({ toolName, args: editArgs, toolCallId }));
1050
- }
1051
- catch (err) {
1052
- recheck = {
1053
- action: "deny",
1054
- message: `policy re-check of the approved edit for "${toolName}" errored: ${err instanceof Error ? err.message : String(err)}`,
1055
- };
1056
- }
1057
- }
1058
- {
1059
- const refusedBy = ledger.fromDecision(recheck);
1060
- if (recheck.action === "deny" && refusedBy !== undefined) {
1061
- editDenied = recheck;
1062
- deniedBy = refusedBy;
1063
- break;
1064
- }
1065
- }
1066
- if (recheck.updatedInput !== undefined) {
1067
- editArgs = recheck.updatedInput;
1068
- editRewrittenSinceHuman = true;
1069
- }
1070
- const editOrg = await applyOrgLayer(recheck, editArgs);
1071
- recheck = editOrg.decision;
1072
- if (recheck.action === "deny") {
1073
- editDenied = recheck;
1074
- break;
1075
- }
1076
- if (recheck.action === "allow") {
1077
- if (recheck.updatedInput !== undefined) {
1078
- editArgs = recheck.updatedInput;
1079
- editRewrittenSinceHuman = true;
1080
- }
1081
- break;
1082
- }
1083
- if (recheck.updatedInput !== undefined) {
1084
- editArgs = recheck.updatedInput;
1085
- editRewrittenSinceHuman = true;
1086
- }
1087
- const editAskSnapshot = { ...recheck, ruleEvidence: mintRuleEvidence({ dotsAbsent: "not_adjudicated" }) };
1088
- const editAskDecision = { ...editAskSnapshot, origin: askOriginOf(editAskSnapshot, askOriginFacts(editOrg.org)) };
1089
- const editAskReq = { toolName, args: editArgs, toolCallId };
1090
- const rr = await (callSignal !== undefined ? resolveAsk(editAskDecision, editAskReq, callSignal) : resolveAsk(editAskDecision, editAskReq));
1091
- ledger.fromResolved(rr, editAskDecision, toolName);
1092
- if (rr.action !== "allow") {
1093
- editDenied = rr;
1094
- deniedBy = "ask_resolution";
1095
- break;
1096
- }
1097
- if (rr.updatedInput === undefined) {
1098
- if (rr.presentedInput !== undefined)
1099
- editArgs = rr.presentedInput;
1100
- editRewrittenSinceHuman = false;
1101
- break;
1102
- }
1103
- editArgs = rr.updatedInput;
1104
- editRewrittenSinceHuman = false;
1105
- }
1106
- if (editDenied === undefined && editRewrittenSinceHuman && input.writeProtectionCheck !== undefined) {
1107
- const editHit = input.writeProtectionCheck(toolName, editArgs);
1108
- if (editHit !== null) {
1109
- editDenied = {
1110
- action: "deny",
1111
- message: `the approved edit for "${toolName}" was rewritten by the restriction chain onto a write-protected path (table entry "${editHit.name}") that no approval covers — denied fail-closed; re-submit the edited action directly`,
1112
- };
1113
- deniedBy = "write_protection";
1114
- }
1115
- }
1116
- decision = editDenied ?? { ...decision, updatedInput: editArgs };
1117
- }
1118
- else if (decision.action === "allow" && decision.updatedInput === undefined && resolved.presentedInput !== undefined) {
1119
- decision = { ...decision, updatedInput: resolved.presentedInput };
1120
- }
1121
- }
1122
- }
1123
- if (decision.action === "deny") {
1124
- const denyReason = withParkFailureCause(decisionText(decision) ?? `tool "${toolName}" denied by policy`, parkFailed);
1125
- if (decision.updatedInput !== undefined) {
1126
- currentInput = decision.updatedInput;
1127
- }
1128
- if (deniedBy === undefined)
1129
- throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
1130
- const gate = mintGateOutcome({ deniedBy, ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}) });
1131
- await notifyPermissionDeniedSeat({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, gate, ...(input.identity !== undefined ? { identity: input.identity } : {}) });
1132
- return {
1133
- block: true,
1134
- reason: formatHookFeedback(denyReason, input.reminderMark),
1135
- gate,
1136
- preToolContext,
1137
- };
1138
- }
1139
- if (decision.action === "allow") {
1140
- const rw = decision.updatedInput !== undefined ? decision.updatedInput : policyRewrite;
1141
- if (rw !== undefined)
1142
- currentInput = rw;
1143
- }
1144
- return {
1145
- updatedInput: currentInput === event.input ? undefined : currentInput,
1146
- gate: mintGateOutcome(ledger.settled !== undefined ? { settled: ledger.settled } : {}),
1147
- preToolContext,
1148
- };
347
+ return await runGateFold(pass);
1149
348
  }