@sema-agent/core 7.5.2 → 7.6.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 (85) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/agents/cascade.d.ts +2 -2
  3. package/dist/agents/cascade.js +12 -10
  4. package/dist/agents/repair-loop.d.ts +5 -3
  5. package/dist/agents/repair-loop.js +13 -15
  6. package/dist/agents/subagent.d.ts +24 -42
  7. package/dist/agents/subagent.js +119 -105
  8. package/dist/agents/suspend-guard.d.ts +31 -19
  9. package/dist/agents/suspend-guard.js +14 -8
  10. package/dist/agents/teacher.js +9 -9
  11. package/dist/agents/team.d.ts +4 -3
  12. package/dist/agents/team.js +10 -8
  13. package/dist/agents/verify.d.ts +3 -3
  14. package/dist/agents/verify.js +17 -17
  15. package/dist/core/a2a.js +2 -1
  16. package/dist/core/ask-origin.d.ts +60 -7
  17. package/dist/core/ask-origin.js +26 -1
  18. package/dist/core/checkpoint-store.d.ts +78 -76
  19. package/dist/core/checkpoint-store.js +17 -1
  20. package/dist/core/gate-outcome.d.ts +189 -0
  21. package/dist/core/gate-outcome.js +70 -0
  22. package/dist/core/hooks.d.ts +18 -92
  23. package/dist/core/hooks.js +88 -85
  24. package/dist/core/mcp-failure.d.ts +104 -0
  25. package/dist/core/mcp-failure.js +128 -0
  26. package/dist/core/mcp.d.ts +21 -77
  27. package/dist/core/mcp.js +76 -150
  28. package/dist/core/pause-registry.d.ts +131 -0
  29. package/dist/core/pause-registry.js +27 -0
  30. package/dist/core/runner/assemble-result.d.ts +32 -41
  31. package/dist/core/runner/assemble-result.js +55 -74
  32. package/dist/core/runner/contracts.d.ts +46 -64
  33. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  34. package/dist/core/runner/denial-limit-arms.js +3 -3
  35. package/dist/core/runner/gate-exit.d.ts +74 -0
  36. package/dist/core/runner/gate-exit.js +55 -0
  37. package/dist/core/runner/park-commit.d.ts +17 -23
  38. package/dist/core/runner/park-commit.js +14 -15
  39. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  40. package/dist/core/runner/prepare-ask-lane.js +3 -5
  41. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  42. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  43. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  44. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  45. package/dist/core/runner/prepare-gate-stations.js +29 -54
  46. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  47. package/dist/core/runner/prepare-memory.d.ts +44 -26
  48. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  49. package/dist/core/runner/prepare-park-ask.js +5 -5
  50. package/dist/core/runner/prepare-task.js +8 -9
  51. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  52. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  53. package/dist/core/runner/runtask.d.ts +16 -31
  54. package/dist/core/runner/runtask.js +109 -120
  55. package/dist/core/runner/terminal-projection.d.ts +22 -0
  56. package/dist/core/runner/terminal-projection.js +28 -0
  57. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  58. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  59. package/dist/core/terminal-cause.d.ts +137 -0
  60. package/dist/core/terminal-cause.js +9 -0
  61. package/dist/core/tool-policy.d.ts +43 -139
  62. package/dist/core/tool-policy.js +79 -112
  63. package/dist/core/types.d.ts +67 -164
  64. package/dist/core/wiring-manifest.d.ts +6 -3
  65. package/dist/core/workflow-journal-store.js +3 -4
  66. package/dist/engine/harness/agent-harness.d.ts +1 -1
  67. package/dist/index.d.ts +10 -7
  68. package/dist/index.js +8 -5
  69. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  70. package/dist/orchestration/builtin-workflows.js +1 -1
  71. package/dist/orchestration/goal.js +8 -7
  72. package/dist/orchestration/run-spec.js +5 -3
  73. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  74. package/dist/orchestration/run-workflow-tool.js +4 -4
  75. package/dist/orchestration/workflow-governance.d.ts +4 -4
  76. package/dist/orchestration/workflow-governance.js +4 -2
  77. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  78. package/dist/orchestration/workflow-primitives.js +1 -1
  79. package/dist/orchestration/workflow.d.ts +11 -0
  80. package/dist/orchestration/workflow.js +64 -39
  81. package/dist/prompts/supervisor.d.ts +1 -1
  82. package/dist/prompts/supervisor.js +3 -3
  83. package/dist/scenarios/scenario-registry.js +1 -1
  84. package/package.json +3 -1
  85. package/test/export-surface.snapshot.json +74 -22
@@ -11,9 +11,31 @@ import { protocolOf } from "./protocol-table.js";
11
11
  import { isAbsolutePathForm, isWinFormPath, writeTargetPath } from "../tools/fs/safety.js";
12
12
  const DECISION_REASONS = ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable"];
13
13
  const DECISION_REASON_SET = new Set(DECISION_REASONS);
14
- export const APPROVAL_SETTLED_BY_VALUES = ["human", "timeout", "aborted"];
15
- export function isApprovalSettledBy(v) {
16
- return typeof v === "string" && APPROVAL_SETTLED_BY_VALUES.includes(v);
14
+ function settledByNobody(kind) {
15
+ return { kind, who: { party: "none" }, when: Date.now() };
16
+ }
17
+ function settledByPerson(kind, approver, note) {
18
+ return { kind, who: { party: "person", ...(approver !== undefined ? { approver } : {}) }, when: Date.now(), ...(note !== undefined ? { note } : {}) };
19
+ }
20
+ const ENGINE_SETTLEMENTS = new WeakMap();
21
+ function attestEngineSettlement(d, record, call) {
22
+ const s = record.settlement;
23
+ const settlement = Object.freeze({ ...s, who: Object.freeze({ ...s.who }) });
24
+ ENGINE_SETTLEMENTS.set(d, { settlement, origin: record.origin, toolCallId: call.toolCallId, toolName: call.toolName });
25
+ return d;
26
+ }
27
+ function transferEngineSettlement(from, to) {
28
+ const v = ENGINE_SETTLEMENTS.get(from);
29
+ if (v !== undefined)
30
+ ENGINE_SETTLEMENTS.set(to, v);
31
+ }
32
+ export function engineSettlementOf(d, call) {
33
+ if (typeof d !== "object" || d === null)
34
+ return undefined;
35
+ const v = ENGINE_SETTLEMENTS.get(d);
36
+ if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
37
+ return undefined;
38
+ return { settlement: v.settlement, origin: v.origin };
17
39
  }
18
40
  export const APPROVER_ATTRIBUTION_MAX_CHARS = 256;
19
41
  const APPROVER_REFUSED_CHARS_RE = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/u;
@@ -321,38 +343,34 @@ export function createApprovalPolicy(opts) {
321
343
  return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
322
344
  }
323
345
  if (need.has(toolName) || namespacedCoveringHit(needCovering, toolName)) {
346
+ const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
324
347
  if (signal?.aborted) {
325
- return withCoreMintedResolution({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, settledBy: "aborted" }, "task_aborted", req);
348
+ return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
326
349
  }
327
350
  let ok;
328
351
  try {
329
352
  ok = await withTimeout(Promise.resolve(opts.approve(req, signal)), opts.approvalTimeoutMs, () => DEADLINE_ELAPSED);
330
353
  }
331
354
  catch (err) {
332
- return withCoreMintedResolution({
333
- action: "deny",
334
- message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
335
- settledBy: "aborted",
336
- }, "approver_error", req);
355
+ return attest({ action: "deny", message: `approval errored for "${req.toolName}": ${describeThrown(err)}` }, settledByNobody("approver_error"));
337
356
  }
338
357
  if (ok === DEADLINE_ELAPSED) {
339
- return withCoreMintedResolution({
358
+ return attest({
340
359
  action: "deny",
341
360
  message: `no one answered the approval request for "${req.toolName}" — the approval window elapsed ` +
342
361
  `(${opts.approvalTimeoutMs}ms) with no answer; denied fail-closed`,
343
- settledBy: "timeout",
344
- }, "window_expired", req);
362
+ }, { kind: "approval_window_expired", who: { party: "engine", window: "approval_factory" }, when: Date.now() });
345
363
  }
346
364
  if (signal?.aborted) {
347
- return withCoreMintedResolution({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, settledBy: "aborted" }, "task_aborted", req);
365
+ return attest({ action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)` }, settledByNobody("task_aborted"));
348
366
  }
349
367
  const okRaw = ok;
350
368
  if (okRaw === true)
351
- return { action: "allow", settledBy: "human" };
369
+ return attest({ action: "allow" }, settledByPerson("human_allowed", undefined));
352
370
  if (okRaw !== false) {
353
- return withCoreMintedResolution({ action: "deny", message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (this policy's \`approve\` returns a boolean: return true or false)`, settledBy: "aborted" }, "approver_contract", req);
371
+ return attest({ action: "deny", message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (this policy's \`approve\` returns a boolean: return true or false)` }, settledByNobody("approver_contract"));
354
372
  }
355
- return withCoreMintedResolution({ action: "deny", message: `approval denied for "${req.toolName}"`, settledBy: "human" }, "human_refused", req);
373
+ return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByPerson("human_refused", undefined));
356
374
  }
357
375
  if (opts.denyByDefault && !auto.has(toolName) && !namespacedCoveringHit(autoCovering, toolName)) {
358
376
  return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
@@ -387,31 +405,30 @@ export function combinePolicies(...policies) {
387
405
  let asked;
388
406
  let current = req;
389
407
  let rewrite;
390
- let settled;
408
+ let settledAllow;
391
409
  let ruleAskText;
392
410
  let probeMandateSeen = false;
393
411
  let fallbackSeen;
394
412
  for (const p of policies) {
395
413
  const d = refuseOutOfContractDecision(await p.check(current, signal));
396
414
  if (d.action === "deny") {
397
- if (rewrite?.updatedInput !== undefined) {
398
- const out = { ...d, updatedInput: rewrite.updatedInput };
399
- const attested = coreMintedResolutions.get(d);
400
- if (attested !== undefined)
401
- coreMintedResolutions.set(out, attested);
402
- return out;
415
+ const out = rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
416
+ if (out !== d) {
417
+ transferEngineSettlement(d, out);
418
+ }
419
+ if (settledAllow !== undefined && ENGINE_SETTLEMENTS.get(out) === undefined) {
420
+ const owned = out === d ? { ...d } : out;
421
+ transferEngineSettlement(settledAllow, owned);
422
+ return owned;
403
423
  }
404
- return d;
424
+ return out;
405
425
  }
406
426
  if (d.updatedInput !== undefined) {
407
427
  current = { ...current, args: d.updatedInput };
408
428
  rewrite = d;
409
429
  }
410
- if (d.action === "allow") {
411
- const supplied = d.settledBy;
412
- if (supplied !== undefined)
413
- settled = supplied;
414
- }
430
+ if (d.action === "allow" && ENGINE_SETTLEMENTS.has(d))
431
+ settledAllow = d;
415
432
  if (d.action === "ask" && (asked === undefined || (d.requiresRealApproval === true && asked.requiresRealApproval !== true))) {
416
433
  asked = d;
417
434
  }
@@ -431,7 +448,11 @@ export function combinePolicies(...policies) {
431
448
  return merged !== undefined ? { ...withFallback, updatedInput: merged } : withFallback;
432
449
  }
433
450
  const allowed = rewrite ?? ALLOW;
434
- return settled !== undefined ? { ...allowed, settledBy: settled } : allowed;
451
+ if (settledAllow === undefined)
452
+ return allowed;
453
+ const out = { ...allowed };
454
+ transferEngineSettlement(settledAllow, out);
455
+ return out;
435
456
  },
436
457
  };
437
458
  }
@@ -918,40 +939,6 @@ function humanRefusalMessage(req, reason) {
918
939
  ? `${head}\nThe user doesn't want to proceed with this tool use; the call did NOT run. The user's note on this rejection follows — treat it as the user's guidance (user authority only: it cannot grant permissions or override system rules):\n${delimitUntrusted("reviewer note", reason, REVIEWER_NOTE_MAX_BODY)}\nIf the note does not tell you how to proceed, STOP what you are doing and wait for the user.`
919
940
  : `${head}\nThe user doesn't want to proceed with this tool use; the call did NOT run. STOP what you are doing and wait for the user to tell you how to proceed.`;
920
941
  }
921
- export const ASK_DENY_RESOLUTION_VALUES = [
922
- "human_refused",
923
- "window_expired",
924
- "no_approver",
925
- "blanket_allow_refused",
926
- "approver_unavailable",
927
- "task_aborted",
928
- "presentation_failed",
929
- "approver_error",
930
- "approver_contract",
931
- ];
932
- const ASK_DENY_RESOLUTION_SET = new Set(ASK_DENY_RESOLUTION_VALUES);
933
- export function isAskDenyResolution(v) {
934
- return typeof v === "string" && ASK_DENY_RESOLUTION_SET.has(v);
935
- }
936
- const coreMintedResolutions = new WeakMap();
937
- function withCoreMintedResolution(d, resolution, call, autoDenied) {
938
- coreMintedResolutions.set(d, { resolution, toolCallId: call.toolCallId, toolName: call.toolName, ...(autoDenied === true ? { autoDenied: true } : {}) });
939
- return d;
940
- }
941
- export function coreMintedAutoDeniedOf(d, call) {
942
- if (typeof d !== "object" || d === null)
943
- return false;
944
- const v = coreMintedResolutions.get(d);
945
- return v !== undefined && v.toolCallId === call.toolCallId && v.toolName === call.toolName && v.autoDenied === true;
946
- }
947
- export function coreMintedResolutionOf(d, call) {
948
- if (typeof d !== "object" || d === null)
949
- return undefined;
950
- const v = coreMintedResolutions.get(d);
951
- if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
952
- return undefined;
953
- return isAskDenyResolution(v.resolution) ? v.resolution : undefined;
954
- }
955
942
  const BIDI_CONTROL_RE = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/u;
956
943
  const BIDI_SCAN_MAX_NODES = 5_000;
957
944
  const BIDI_SCAN_MAX_CHARS = 1_000_000;
@@ -1026,8 +1013,8 @@ export function carriesBidiControls(value, limits) {
1026
1013
  }
1027
1014
  export async function resolveAsk(req, onAsk, signal, onLateSettlement) {
1028
1015
  const r = await resolveAskArms(req, onAsk, signal, onLateSettlement);
1029
- if (r.action === "deny" && isAskDenyResolution(r.resolution))
1030
- return withCoreMintedResolution(r, r.resolution, req, r.autoDenied === true ? true : undefined);
1016
+ if (r.settlement !== undefined && req.origin !== undefined)
1017
+ attestEngineSettlement(r, { settlement: r.settlement, origin: req.origin }, req);
1031
1018
  return r;
1032
1019
  }
1033
1020
  function raceAskWaitAgainstSignal(wait, signal) {
@@ -1084,7 +1071,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1084
1071
  `judgment was applied) — this decision requires an actual auto-mode classifier verdict or a ` +
1085
1072
  `real approval callback, neither of which a blanket bypass can provide: ${req.message}`,
1086
1073
  decisionReason: "mode",
1087
- resolution: "blanket_allow_refused",
1074
+ settlement: settledByNobody("blanket_allow_refused"),
1088
1075
  };
1089
1076
  }
1090
1077
  return { action: "allow", decisionReason: "mode" };
@@ -1094,12 +1081,11 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1094
1081
  action: "deny",
1095
1082
  message: `approval required for "${req.toolName}" but no approver is wired (headless auto-deny): ${req.message}`,
1096
1083
  decisionReason: "mode",
1097
- resolution: "no_approver",
1084
+ settlement: settledByNobody("no_approver"),
1098
1085
  };
1099
1086
  }
1100
1087
  if (signal?.aborted) {
1101
- return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode",
1102
- resolution: "task_aborted", settledBy: "aborted" };
1088
+ return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode", settlement: settledByNobody("task_aborted") };
1103
1089
  }
1104
1090
  const presented = tryCloneArgs(req.args);
1105
1091
  if (!presented.ok) {
@@ -1107,8 +1093,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1107
1093
  action: "deny",
1108
1094
  message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${presented.reason}) — denied fail-closed`,
1109
1095
  decisionReason: "mode",
1110
- resolution: "presentation_failed",
1111
- settledBy: "aborted",
1096
+ settlement: settledByNobody("presentation_failed"),
1112
1097
  };
1113
1098
  }
1114
1099
  let ok;
@@ -1119,8 +1104,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1119
1104
  action: "deny",
1120
1105
  message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${approverView.reason}) — denied fail-closed`,
1121
1106
  decisionReason: "mode",
1122
- resolution: "presentation_failed",
1123
- settledBy: "aborted",
1107
+ settlement: settledByNobody("presentation_failed"),
1124
1108
  };
1125
1109
  }
1126
1110
  const bidi = carriesBidiControls(presented.value) || carriesBidiControls(req.preview);
@@ -1140,15 +1124,12 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1140
1124
  message: `no one answered the classifier denial-limit approval for "${req.toolName}" — the ${String(windowMs)}ms window ` +
1141
1125
  `elapsed with no answer; auto-denied: ${req.message}`,
1142
1126
  decisionReason: "mode",
1143
- resolution: "window_expired",
1144
- settledBy: "timeout",
1145
- autoDenied: true,
1127
+ settlement: { kind: "denial_limit_window_expired", who: { party: "engine", window: "denial_limit" }, when: Date.now() },
1146
1128
  };
1147
1129
  }
1148
1130
  if (raced.tag === "aborted") {
1149
1131
  detachLateAskWait(wait, onLateSettlement);
1150
- return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode",
1151
- resolution: "task_aborted", settledBy: "aborted" };
1132
+ return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode", settlement: settledByNobody("task_aborted") };
1152
1133
  }
1153
1134
  if (raced.tag === "threw")
1154
1135
  throw raced.error;
@@ -1159,13 +1140,11 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1159
1140
  action: "deny",
1160
1141
  message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
1161
1142
  decisionReason: "mode",
1162
- resolution: "approver_error",
1163
- settledBy: "aborted",
1143
+ settlement: settledByNobody("approver_error"),
1164
1144
  };
1165
1145
  }
1166
1146
  if (signal?.aborted) {
1167
- return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode",
1168
- resolution: "task_aborted", settledBy: "aborted" };
1147
+ return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode", settlement: settledByNobody("task_aborted") };
1169
1148
  }
1170
1149
  if (ok === "unavailable") {
1171
1150
  return {
@@ -1173,8 +1152,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1173
1152
  message: `approval required for "${req.toolName}" but the approver reported unavailable (no reachable ` +
1174
1153
  `operator for this ask) and no durable approval gate is armed — denied fail-closed: ${req.message}`,
1175
1154
  decisionReason: "mode",
1176
- resolution: "approver_unavailable",
1177
- settledBy: "aborted",
1155
+ settlement: settledByNobody("approver_unavailable"),
1178
1156
  approverUnavailable: true,
1179
1157
  };
1180
1158
  }
@@ -1194,8 +1172,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1194
1172
  action: "deny",
1195
1173
  message: `the approver's outcome for "${req.toolName}" could not be read (${describeThrown(err)}) — denied fail-closed`,
1196
1174
  decisionReason: "mode",
1197
- resolution: "approver_contract",
1198
- settledBy: "aborted",
1175
+ settlement: settledByNobody("approver_contract"),
1199
1176
  };
1200
1177
  }
1201
1178
  if (typeof allowed !== "boolean") {
@@ -1204,8 +1181,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1204
1181
  message: `the approver for "${req.toolName}" returned an object whose allow is not a boolean ` +
1205
1182
  `(got ${allowed === null ? "null" : typeof allowed}) — a verdict is exactly true or false; denied fail-closed`,
1206
1183
  decisionReason: "mode",
1207
- resolution: "approver_contract",
1208
- settledBy: "aborted",
1184
+ settlement: settledByNobody("approver_contract"),
1209
1185
  };
1210
1186
  }
1211
1187
  if (supplied !== undefined && supplied !== "human" && supplied !== "timeout") {
@@ -1214,8 +1190,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1214
1190
  message: `the approver for "${req.toolName}" reported settledBy "${typeof supplied === "string" ? containThrownText(supplied) : supplied === null ? "null" : typeof supplied}", which is outside what a synchronous ` +
1215
1191
  `approver may self-report — it is exactly "human" or "timeout" (or omitted); denied fail-closed`,
1216
1192
  decisionReason: "mode",
1217
- resolution: "approver_contract",
1218
- settledBy: "aborted",
1193
+ settlement: settledByNobody("approver_contract"),
1219
1194
  };
1220
1195
  }
1221
1196
  const attribution = screenApproverAttribution(suppliedApprover);
@@ -1224,19 +1199,16 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1224
1199
  action: "deny",
1225
1200
  message: `the approver for "${req.toolName}" reported an attribution this seam refuses: ${attribution.defect}; denied fail-closed`,
1226
1201
  decisionReason: "mode",
1227
- resolution: "approver_contract",
1228
- settledBy: "aborted",
1202
+ settlement: settledByNobody("approver_contract"),
1229
1203
  };
1230
1204
  }
1231
- const attributionCell = attribution.approver !== undefined ? { approver: attribution.approver } : {};
1232
1205
  if (supplied === "timeout" && allowed === true) {
1233
1206
  return {
1234
1207
  action: "deny",
1235
1208
  message: `the approver for "${req.toolName}" returned an allow settled by "timeout" — an elapsed approval window cannot be ` +
1236
1209
  `what approved a call; denied fail-closed (report timeout with allow:false, or allow with settledBy "human"/omitted)`,
1237
1210
  decisionReason: "mode",
1238
- resolution: "approver_contract",
1239
- settledBy: "timeout",
1211
+ settlement: settledByNobody("approver_contract"),
1240
1212
  };
1241
1213
  }
1242
1214
  if (allowed !== true) {
@@ -1249,8 +1221,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1249
1221
  message: `the approver for "${req.toolName}" attached a reason that is not a plain string ` +
1250
1222
  `(got ${suppliedReason === null ? "null" : typeof suppliedReason}) — a deny note is the decider's plain text; denied fail-closed`,
1251
1223
  decisionReason: "mode",
1252
- resolution: "approver_contract",
1253
- settledBy: "aborted",
1224
+ settlement: settledByNobody("approver_contract"),
1254
1225
  };
1255
1226
  }
1256
1227
  reasonText = typeof suppliedReason === "string" && suppliedReason !== "" ? suppliedReason : undefined;
@@ -1260,8 +1231,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1260
1231
  action: "deny",
1261
1232
  message: `the approver for "${req.toolName}" attached a reason that could not be read (${describeThrown(err)}) — denied fail-closed`,
1262
1233
  decisionReason: "mode",
1263
- resolution: "approver_contract",
1264
- settledBy: "aborted",
1234
+ settlement: settledByNobody("approver_contract"),
1265
1235
  };
1266
1236
  }
1267
1237
  return {
@@ -1270,37 +1240,34 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
1270
1240
  ? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${reasonText !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", reasonText, REVIEWER_NOTE_MAX_BODY)}` : ""}`
1271
1241
  : humanRefusalMessage(req, reasonText),
1272
1242
  decisionReason: "mode",
1273
- resolution: supplied === "timeout" ? "window_expired" : "human_refused",
1274
- settledBy: supplied === "timeout" ? "timeout" : "human",
1275
- ...(supplied !== "timeout" && reasonText !== undefined ? { humanRefusalNote: true } : {}),
1276
- ...attributionCell,
1243
+ settlement: supplied === "timeout"
1244
+ ? { kind: "approval_window_expired", who: { party: "host" }, when: Date.now() }
1245
+ : settledByPerson("human_refused", attribution.approver, reasonText),
1277
1246
  };
1278
1247
  }
1279
1248
  if (suppliedEdit === undefined)
1280
- return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settledBy: "human", ...attributionCell };
1249
+ return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settlement: settledByPerson("human_allowed", attribution.approver) };
1281
1250
  const edit = tryCloneArgs(suppliedEdit);
1282
1251
  if (!edit.ok) {
1283
1252
  return {
1284
1253
  action: "deny",
1285
1254
  message: `the approved edit for "${req.toolName}" is not safely clonable (${edit.reason}) — denied fail-closed`,
1286
1255
  decisionReason: "mode",
1287
- resolution: "presentation_failed",
1288
- settledBy: "aborted",
1256
+ settlement: settledByNobody("presentation_failed"),
1289
1257
  };
1290
1258
  }
1291
- return { action: "allow", updatedInput: edit.value, decisionReason: "mode", settledBy: "human", ...attributionCell };
1259
+ return { action: "allow", updatedInput: edit.value, decisionReason: "mode", settlement: settledByPerson("human_allowed", attribution.approver) };
1292
1260
  }
1293
1261
  const okRaw = ok;
1294
1262
  if (okRaw === true)
1295
- return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settledBy: "human" };
1263
+ return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settlement: settledByPerson("human_allowed", undefined) };
1296
1264
  if (okRaw !== false) {
1297
1265
  return {
1298
1266
  action: "deny",
1299
1267
  message: `the approver for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (return true, false, "unavailable", or the {allow} object)`,
1300
1268
  decisionReason: "mode",
1301
- resolution: "approver_contract",
1302
- settledBy: "aborted",
1269
+ settlement: settledByNobody("approver_contract"),
1303
1270
  };
1304
1271
  }
1305
- return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", resolution: "human_refused", settledBy: "human" };
1272
+ return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", settlement: settledByPerson("human_refused", undefined) };
1306
1273
  }