@sema-agent/server 7.122.0 → 7.124.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 (256) hide show
  1. package/MIGRATION.md +209 -0
  2. package/README.md +7 -2
  3. package/README.zh-CN.md +5 -2
  4. package/USAGE.md +76 -56
  5. package/dist/approval-ask-audit-store.d.ts +27 -32
  6. package/dist/approval-ask-audit-store.js +10 -9
  7. package/dist/approval-crash-convergence.d.ts +2 -2
  8. package/dist/approval-crash-convergence.js +5 -5
  9. package/dist/approval-deny-reasons.d.ts +163 -51
  10. package/dist/approval-deny-reasons.js +79 -8
  11. package/dist/approval-policy-settlement.d.ts +4 -1
  12. package/dist/approval-policy-settlement.js +3 -2
  13. package/dist/approval-reconciler.d.ts +3 -3
  14. package/dist/approval-reconciler.js +10 -10
  15. package/dist/approval.d.ts +1 -1
  16. package/dist/approval.js +1 -1
  17. package/dist/audit.d.ts +1 -1
  18. package/dist/audit.js +53 -23
  19. package/dist/auth-bridge.d.ts +8 -8
  20. package/dist/auth-bridge.js +6 -6
  21. package/dist/auth-keys.d.ts +2 -2
  22. package/dist/auto-mode-face.d.ts +13 -9
  23. package/dist/auto-mode-face.js +8 -7
  24. package/dist/boot/budget-tracing.js +1 -1
  25. package/dist/boot/execution-env.js +1 -0
  26. package/dist/boot/leader.d.ts +1 -1
  27. package/dist/boot/memory-consolidation.d.ts +4 -4
  28. package/dist/boot/org-memory.d.ts +3 -3
  29. package/dist/boot/org-memory.js +5 -5
  30. package/dist/boot/resolve-spec.js +7 -7
  31. package/dist/boot/runner-deps.d.ts +1 -1
  32. package/dist/boot/runtime-caps.d.ts +1 -1
  33. package/dist/boot/runtime-caps.js +10 -10
  34. package/dist/boot/shutdown.d.ts +2 -2
  35. package/dist/boot/shutdown.js +2 -2
  36. package/dist/boot/side-query-lane.d.ts +3 -3
  37. package/dist/boot/{stage-02-registry-pull.d.ts → stage-02-web-admin-pull.d.ts} +2 -2
  38. package/dist/boot/{stage-02-registry-pull.js → stage-02-web-admin-pull.js} +4 -4
  39. package/dist/boot/stage-03-store-backend.d.ts +2 -2
  40. package/dist/boot/stage-04-stores.d.ts +1 -1
  41. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  42. package/dist/boot/stage-06-runners.d.ts +2 -2
  43. package/dist/boot/stage-06-runners.js +6 -6
  44. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  45. package/dist/boot/stage-07-capability-layer.js +6 -5
  46. package/dist/boot/stage-08-reapers.d.ts +3 -3
  47. package/dist/boot/stage-08-reapers.js +3 -3
  48. package/dist/boot/stage-09-leader.d.ts +3 -3
  49. package/dist/boot/stage-09-leader.js +7 -7
  50. package/dist/boot/stage-10-http-server.d.ts +3 -3
  51. package/dist/boot/stage-10-http-server.js +19 -19
  52. package/dist/boot/unpriced-ceiling-verdict.d.ts +1 -1
  53. package/dist/boot/{config-center.d.ts → web-admin.d.ts} +11 -11
  54. package/dist/boot/{config-center.js → web-admin.js} +39 -20
  55. package/dist/boot/webfetch-summarize-lane.d.ts +2 -2
  56. package/dist/brain.d.ts +1 -1
  57. package/dist/brain.js +1 -1
  58. package/dist/budget.d.ts +3 -3
  59. package/dist/canonical-json.d.ts +2 -2
  60. package/dist/capabilities/center-plugins.d.ts +1 -1
  61. package/dist/capabilities/center-wiring.d.ts +12 -5
  62. package/dist/capabilities/center-wiring.js +11 -2
  63. package/dist/capabilities/composite-members.d.ts +1 -1
  64. package/dist/capabilities/composite-members.js +1 -1
  65. package/dist/capabilities/scenarios.js +2 -2
  66. package/dist/capabilities/team.d.ts +1 -1
  67. package/dist/capabilities/tool-defer.d.ts +1 -1
  68. package/dist/center-credential-renewer.d.ts +1 -1
  69. package/dist/center-credential-renewer.js +7 -7
  70. package/dist/center-credential.d.ts +9 -9
  71. package/dist/center-credential.js +6 -6
  72. package/dist/config-catalog.d.ts +7 -10
  73. package/dist/config-catalog.js +16 -16
  74. package/dist/config-d-migration.d.ts +32 -0
  75. package/dist/config-d-migration.js +59 -0
  76. package/dist/config-invariants.d.ts +14 -4
  77. package/dist/config-invariants.js +31 -2
  78. package/dist/config-lkg.d.ts +34 -11
  79. package/dist/config-lkg.js +76 -48
  80. package/dist/config-provider.d.ts +26 -21
  81. package/dist/config-provider.js +25 -27
  82. package/dist/config-types.d.ts +59 -50
  83. package/dist/config.d.ts +17 -54
  84. package/dist/config.js +99 -181
  85. package/dist/execution-lane-caps.d.ts +1 -1
  86. package/dist/fleet/drain-entry.d.ts +1 -1
  87. package/dist/fleet/engine-signal.d.ts +61 -30
  88. package/dist/fleet/engine-signal.js +29 -5
  89. package/dist/fleet/fleet-bus.d.ts +12 -3
  90. package/dist/fleet/fleet-bus.js +13 -9
  91. package/dist/fleet/serving-lease.d.ts +3 -3
  92. package/dist/fleet/serving-lease.js +1 -1
  93. package/dist/fleet-client.d.ts +15 -13
  94. package/dist/fleet-client.js +9 -7
  95. package/dist/fleet-lease.d.ts +3 -3
  96. package/dist/fleet-lease.js +2 -2
  97. package/dist/host-lsp-manager.d.ts +1 -1
  98. package/dist/hosted-posture.d.ts +4 -4
  99. package/dist/http/admission.js +4 -4
  100. package/dist/http/checkpoint-ctx.d.ts +20 -3
  101. package/dist/http/checkpoint-ctx.js +13 -5
  102. package/dist/http/dispatch.js +7 -16
  103. package/dist/http/durable-run-leg.js +2 -1
  104. package/dist/http/health-self-report.d.ts +7 -4
  105. package/dist/http/health-self-report.js +3 -3
  106. package/dist/http/idempotency.d.ts +1 -1
  107. package/dist/http/resume-legs.d.ts +4 -3
  108. package/dist/http/resume-legs.js +31 -18
  109. package/dist/http/route-ctx.d.ts +8 -0
  110. package/dist/http/routes/admin-config-refresh.d.ts +1 -1
  111. package/dist/http/routes/admin-config-refresh.js +1 -1
  112. package/dist/http/routes/admin-drain.d.ts +1 -1
  113. package/dist/http/routes/admin-drain.js +5 -4
  114. package/dist/http/routes/approvals-assistant.js +4 -4
  115. package/dist/http/routes/capabilities.js +6 -5
  116. package/dist/http/routes/config-catalog.js +2 -3
  117. package/dist/http/routes/diagnostics.js +1 -1
  118. package/dist/http/routes/runs.js +8 -4
  119. package/dist/http/routes/sessions-list.js +2 -1
  120. package/dist/http/routes/sessions.js +2 -5
  121. package/dist/http/routes/side-query.js +3 -3
  122. package/dist/http/routes/tasks.js +3 -2
  123. package/dist/http/run-meta.d.ts +1 -1
  124. package/dist/http/run-meta.js +2 -2
  125. package/dist/http/server.d.ts +7 -7
  126. package/dist/http/server.js +1 -1
  127. package/dist/http/stored-body-replay.d.ts +4 -3
  128. package/dist/http/stored-body-replay.js +5 -5
  129. package/dist/index.d.ts +2 -1
  130. package/dist/index.js +1 -1
  131. package/dist/key-resolver.d.ts +1 -1
  132. package/dist/leader/leader.d.ts +7 -6
  133. package/dist/leader/leader.js +3 -1
  134. package/dist/leader/run-entry.d.ts +2 -2
  135. package/dist/leader/run-entry.js +3 -3
  136. package/dist/leader/wire.d.ts +4 -4
  137. package/dist/leader/wire.js +2 -1
  138. package/dist/main.js +1 -1
  139. package/dist/mcp-tool-faces.d.ts +2 -2
  140. package/dist/model-entry-refusal.d.ts +20 -11
  141. package/dist/model-entry-refusal.js +22 -17
  142. package/dist/model-provider.d.ts +2 -2
  143. package/dist/model-route-endpoint.d.ts +22 -2
  144. package/dist/model-route-endpoint.js +9 -3
  145. package/dist/model-route-gap.d.ts +103 -0
  146. package/dist/model-route-gap.js +80 -0
  147. package/dist/model-route-url.d.ts +1 -1
  148. package/dist/model-route-url.js +2 -1
  149. package/dist/model-select.d.ts +24 -21
  150. package/dist/model-select.js +22 -17
  151. package/dist/namespaced-rule-name.d.ts +16 -30
  152. package/dist/namespaced-rule-name.js +2 -20
  153. package/dist/observability/fail-open.d.ts +0 -4
  154. package/dist/observability/fail-open.js +0 -4
  155. package/dist/observability/metrics.d.ts +1 -1
  156. package/dist/org-memory-admission.d.ts +1 -1
  157. package/dist/plugins/approval-ask-store-file.d.ts +4 -2
  158. package/dist/plugins/approval-ask-store-file.js +3 -3
  159. package/dist/plugins/approval-ask-store-memory.d.ts +4 -3
  160. package/dist/plugins/approval-ask-store-memory.js +10 -4
  161. package/dist/plugins/approval-ask-store-sql.d.ts +16 -2
  162. package/dist/plugins/approval-ask-store-sql.js +20 -13
  163. package/dist/plugins/caching-session-store.d.ts +0 -10
  164. package/dist/plugins/caching-session-store.js +0 -3
  165. package/dist/plugins/file-run-store.js +9 -3
  166. package/dist/plugins/leader-run-store-sql.d.ts +3 -2
  167. package/dist/plugins/leader-run-store-sql.js +3 -2
  168. package/dist/plugins/local-session-store.d.ts +20 -27
  169. package/dist/plugins/local-session-store.js +19 -17
  170. package/dist/plugins/memory-run-store.js +9 -3
  171. package/dist/plugins/one-time-migrations.js +51 -0
  172. package/dist/plugins/scheduler-support.d.ts +26 -7
  173. package/dist/plugins/scheduler-support.js +96 -53
  174. package/dist/plugins/session-policy-store-sql.js +1 -2
  175. package/dist/plugins/store-backend.js +1 -1
  176. package/dist/plugins/store-contracts.d.ts +20 -4
  177. package/dist/plugins/web-search.d.ts +1 -1
  178. package/dist/plugins/web-search.js +2 -1
  179. package/dist/plugins/workflow-run-store-sql.d.ts +4 -2
  180. package/dist/posture-source.d.ts +1 -1
  181. package/dist/read-face-posture.d.ts +3 -3
  182. package/dist/read-face-posture.js +1 -1
  183. package/dist/rule-entry-list.d.ts +3 -13
  184. package/dist/rule-entry-list.js +1 -12
  185. package/dist/run-local.js +10 -7
  186. package/dist/runtime-caps-resolver.d.ts +4 -4
  187. package/dist/runtime-caps-resolver.js +2 -2
  188. package/dist/runtime-governance.d.ts +36 -13
  189. package/dist/runtime-governance.js +7 -2
  190. package/dist/sealed-key.d.ts +4 -4
  191. package/dist/sealed-key.js +3 -2
  192. package/dist/security.d.ts +3 -3
  193. package/dist/service-version.d.ts +1 -1
  194. package/dist/service-version.js +5 -0
  195. package/dist/session-policy-wire.d.ts +47 -49
  196. package/dist/session-policy-wire.js +41 -36
  197. package/dist/session-sync.d.ts +5 -1
  198. package/dist/session-sync.js +2 -1
  199. package/dist/session-titler.d.ts +0 -11
  200. package/dist/session-titler.js +3 -5
  201. package/dist/shared-memory-scope-authorizer.d.ts +4 -4
  202. package/dist/spec-fields.d.ts +4 -4
  203. package/dist/spec-fields.js +1 -4
  204. package/dist/task-a2a.d.ts +1 -1
  205. package/dist/task-settings.d.ts +4 -5
  206. package/dist/task-settings.js +5 -8
  207. package/dist/tool-approval.d.ts +43 -4
  208. package/dist/tool-approval.js +147 -98
  209. package/dist/trace/core-keyset-guard.d.ts +1 -1
  210. package/dist/trace/project.d.ts +3 -3
  211. package/dist/trace/redact.d.ts +2 -23
  212. package/dist/trace/redact.js +1 -6
  213. package/dist/trace/self-minted-wire.d.ts +147 -3
  214. package/dist/trace/self-minted-wire.js +50 -9
  215. package/dist/unattended-permission-mode.d.ts +3 -3
  216. package/dist/unattended-permission-mode.js +2 -2
  217. package/dist/url-refusal.d.ts +35 -0
  218. package/dist/url-refusal.js +15 -0
  219. package/dist/user-authored-text.d.ts +31 -0
  220. package/dist/user-authored-text.js +18 -0
  221. package/dist/{config-center → web-admin-client}/apply-effective.d.ts +13 -70
  222. package/dist/{config-center → web-admin-client}/apply-effective.js +104 -121
  223. package/dist/{config-center → web-admin-client}/apply-ledger.d.ts +2 -2
  224. package/dist/{config-center → web-admin-client}/center-request.d.ts +7 -7
  225. package/dist/{config-center → web-admin-client}/center-request.js +5 -4
  226. package/dist/{config-center → web-admin-client}/effective-keys.d.ts +1 -3
  227. package/dist/{config-center → web-admin-client}/effective-keys.js +0 -9
  228. package/dist/{config-center → web-admin-client}/facade.d.ts +6 -6
  229. package/dist/{config-center → web-admin-client}/facade.js +2 -2
  230. package/dist/{config-center → web-admin-client}/http-client.d.ts +6 -10
  231. package/dist/{config-center → web-admin-client}/http-client.js +15 -15
  232. package/dist/{config-center → web-admin-client}/pull-state.d.ts +1 -1
  233. package/dist/{config-center → web-admin-client}/read-face.js +4 -4
  234. package/dist/web-admin-client/read-warnings.d.ts +137 -0
  235. package/dist/web-admin-client/read-warnings.js +168 -0
  236. package/dist/{config-center → web-admin-client}/restart-signal.d.ts +5 -3
  237. package/dist/{config-center → web-admin-client}/restart-signal.js +3 -4
  238. package/dist/{config-center → web-admin-client}/skills-mcp.d.ts +1 -1
  239. package/dist/{config-center → web-admin-client}/skills-mcp.js +13 -13
  240. package/dist/{config-center → web-admin-client}/stage-limits.d.ts +1 -7
  241. package/dist/{config-center → web-admin-client}/stage-limits.js +6 -7
  242. package/dist/{config-center → web-admin-client}/types.d.ts +19 -47
  243. package/package.json +3 -3
  244. package/skills/find-skills.md +2 -2
  245. package/dist/config-center/read-warnings.d.ts +0 -109
  246. package/dist/config-center/read-warnings.js +0 -110
  247. package/dist/url-shape.d.ts +0 -31
  248. package/dist/url-shape.js +0 -41
  249. /package/dist/{config-center → web-admin-client}/apply-ledger.js +0 -0
  250. /package/dist/{config-center → web-admin-client}/hot-keys-registry.d.ts +0 -0
  251. /package/dist/{config-center → web-admin-client}/hot-keys-registry.js +0 -0
  252. /package/dist/{config-center → web-admin-client}/mcp-revocation.d.ts +0 -0
  253. /package/dist/{config-center → web-admin-client}/mcp-revocation.js +0 -0
  254. /package/dist/{config-center → web-admin-client}/pull-state.js +0 -0
  255. /package/dist/{config-center → web-admin-client}/read-face.d.ts +0 -0
  256. /package/dist/{config-center → web-admin-client}/types.js +0 -0
@@ -11,6 +11,7 @@ import { recordFailOpen } from "./observability/fail-open.js";
11
11
  import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
12
12
  import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
13
13
  import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
14
+ import { APPROVAL_ENDING_CAUSES, SETTLE_OUTCOME, causeOfAskRow } from "./approval-deny-reasons.js";
14
15
  import { settleByPolicy } from "./approval-policy-settlement.js";
15
16
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
16
17
  import { governanceOriginOf } from "./runtime-governance.js";
@@ -31,7 +32,6 @@ const SUSPENDED_POLL_BUDGET_MS = 5_000;
31
32
  const EDITED_DECISION_MARK_CAP_MS = 10_000;
32
33
  const DEFAULT_WINDOW_MARGIN_MS = 10_000;
33
34
  const SHUTDOWN_DRAIN_POLL_MS = 50;
34
- const SHUTDOWN_BEFORE_PARK = "shutdown_before_park";
35
35
  const MAX_ALLOW_SESSIONS = 4096;
36
36
  const DEFAULT_ADMIT_MAX_PER_TASK = 32;
37
37
  const DEFAULT_ADMIT_MAX_PER_OWNER = 256;
@@ -59,6 +59,7 @@ function durableRuleMaterial(material) {
59
59
  }
60
60
  return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
61
61
  }
62
+ export const NOT_PENDING_UNKNOWN_CAUSE = "unknown_or_other_replica";
62
63
  export const ASK_DECISION_CONSUMER_ABSENT = "absent";
63
64
  const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
64
65
  const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
@@ -366,6 +367,14 @@ function settleArgsOfDecided(outcome) {
366
367
  ...(outcome.settledBy !== undefined ? { settledBy: outcome.settledBy } : {}),
367
368
  };
368
369
  }
370
+ function verdictOfDecided(args) {
371
+ return {
372
+ by: "human",
373
+ allowed: args.allowed,
374
+ ...(args.updatedInput !== undefined ? { updatedInput: args.updatedInput } : {}),
375
+ ...(args.settledBy !== undefined ? { hostSettledBy: args.settledBy } : {}),
376
+ };
377
+ }
369
378
  function settleArgsOf(outcome) {
370
379
  return outcome === "unavailable" ? "unavailable" : settleArgsOfDecided(outcome);
371
380
  }
@@ -499,16 +508,16 @@ export class ToolApprovalCoordinator {
499
508
  return outcome;
500
509
  const dispatch = (res, where) => {
501
510
  if (res.claimed) {
502
- this.settleSameAskIdParkRoute(row.askId);
511
+ this.settleSameAskIdParkRoute(row.askId, APPROVAL_ENDING_CAUSES.UNDELIVERABLE);
503
512
  return undefined;
504
513
  }
505
514
  const read = this.classifyClaimLoss(row.askId, res.current, where);
506
- this.dispatchClaimTruth(row.askId, read);
515
+ this.dispatchClaimTruth(row.askId, read, APPROVAL_ENDING_CAUSES.UNDELIVERABLE);
507
516
  return read;
508
517
  };
509
518
  let res;
510
519
  try {
511
- res = await this.withStoreDeadline(settleByPolicy(store, row, this.unattendedPolicy), "claimTerminal(unattended-fallback)", DURABLE_CALL_TIMEOUT_MS, (late) => {
520
+ res = await this.withStoreDeadline(settleByPolicy(store, row, this.unattendedPolicy, APPROVAL_ENDING_CAUSES.UNDELIVERABLE), "claimTerminal(unattended-fallback)", DURABLE_CALL_TIMEOUT_MS, (late) => {
512
521
  dispatch(late, "late:unattended-fallback");
513
522
  });
514
523
  }
@@ -707,7 +716,7 @@ export class ToolApprovalCoordinator {
707
716
  let n = 0;
708
717
  for (const e of this.pending.values()) {
709
718
  if (e.ctxTaskId === wireTaskId && e.settledOutcome === undefined) {
710
- e.forceParkNow();
719
+ e.forceParkNow(APPROVAL_ENDING_CAUSES.STREAM_DISCONNECTED);
711
720
  n++;
712
721
  }
713
722
  }
@@ -717,7 +726,7 @@ export class ToolApprovalCoordinator {
717
726
  this.shutdownDraining = true;
718
727
  for (const e of [...this.pending.values()])
719
728
  if (e.settledOutcome === undefined)
720
- e.forceParkNow();
729
+ e.forceParkNow(APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
721
730
  }
722
731
  async drainForShutdown(deadlineMs) {
723
732
  this.beginDrain();
@@ -789,13 +798,13 @@ export class ToolApprovalCoordinator {
789
798
  storeErrorCount() {
790
799
  return this.storeErrorTally;
791
800
  }
792
- settleVoidedSiblings(askIds) {
801
+ settleVoidedSiblings(askIds, cause) {
793
802
  for (const askId of askIds) {
794
803
  const entries = this.pendingByAskId.get(askId);
795
804
  if (!entries)
796
805
  continue;
797
806
  for (const entry of [...entries])
798
- entry.settle(false, "expired");
807
+ entry.settle({ by: "system", cause });
799
808
  }
800
809
  }
801
810
  governanceForcedOf(origin, req) {
@@ -884,9 +893,13 @@ export class ToolApprovalCoordinator {
884
893
  classifyClaimLoss(askId, current, _where) {
885
894
  if (current.state === "absent")
886
895
  return { kind: "absent" };
896
+ const rowCause = current.cause;
897
+ if (rowCause?.kind === "ending")
898
+ return { kind: "ended", cause: rowCause.cause };
887
899
  const replayedOutcome = replayTerminalAskRow(current);
888
- if (replayedOutcome === "unavailable")
889
- return { kind: "parked" };
900
+ if (replayedOutcome === "unavailable") {
901
+ return rowCause?.kind === "park_trigger" ? { kind: "parked", cause: rowCause.cause } : { kind: "parked" };
902
+ }
890
903
  const replayed = settleArgsOfDecided(replayedOutcome);
891
904
  if (!replayed.allowed)
892
905
  return { kind: "decided", outcome: replayedOutcome };
@@ -905,20 +918,23 @@ export class ToolApprovalCoordinator {
905
918
  }
906
919
  return false;
907
920
  }
908
- settleSameAskIdParkRoute(askId) {
921
+ settleSameAskIdParkRoute(askId, cause) {
909
922
  const entries = this.pendingByAskId.get(askId);
910
923
  if (!entries)
911
924
  return;
912
925
  for (const entry of [...entries])
913
- entry.settleParkRoute();
926
+ entry.settleParkRoute(cause);
914
927
  }
915
- dispatchClaimTruth(askId, read) {
928
+ dispatchClaimTruth(askId, read, parkedCause) {
916
929
  switch (read.kind) {
917
930
  case "decided":
918
931
  this.notifyExternalDecision(askId, read.outcome);
919
932
  return;
933
+ case "ended":
934
+ this.settleVoidedSiblings([askId], read.cause);
935
+ return;
920
936
  case "parked":
921
- this.settleSameAskIdParkRoute(askId);
937
+ this.settleSameAskIdParkRoute(askId, read.cause ?? parkedCause);
922
938
  return;
923
939
  case "absent":
924
940
  case "edit-in-flight":
@@ -1006,7 +1022,7 @@ export class ToolApprovalCoordinator {
1006
1022
  const args = settleArgsOfDecided(outcome);
1007
1023
  let settled = 0;
1008
1024
  for (const entry of [...entries]) {
1009
- entry.settle(args.allowed, args.allowed ? "allowed" : "denied", args.updatedInput, args.settledBy);
1025
+ entry.settle(verdictOfDecided(args));
1010
1026
  settled += 1;
1011
1027
  }
1012
1028
  return { settled };
@@ -1058,7 +1074,7 @@ export class ToolApprovalCoordinator {
1058
1074
  this.streams.delete(key);
1059
1075
  for (const [, p] of [...this.pending]) {
1060
1076
  if (p.targetCtxs?.has(ctx))
1061
- p.onTargetGone?.(ctx);
1077
+ p.onTargetGone?.(ctx, APPROVAL_ENDING_CAUSES.RUN_EXITED);
1062
1078
  }
1063
1079
  this.settleParksAwaitingCommit((p) => p.carrierLeg === ctx, "leg-closed");
1064
1080
  this.askAudit?.noteLegClosed(ctx.taskId);
@@ -1240,7 +1256,7 @@ export class ToolApprovalCoordinator {
1240
1256
  }), "ensureAsk", DURABLE_CALL_TIMEOUT_MS, (late) => {
1241
1257
  if (late.row.state !== "STREAM_PENDING") {
1242
1258
  lateTerminalRow = late.row;
1243
- this.dispatchClaimTruth(late.row.askId, this.classifyClaimLoss(late.row.askId, projectAskTerminalClaimTruth(late.row), "late:ensureAsk"));
1259
+ this.dispatchClaimTruth(late.row.askId, this.classifyClaimLoss(late.row.askId, projectAskTerminalClaimTruth(late.row), "late:ensureAsk"), APPROVAL_ENDING_CAUSES.UNDELIVERABLE);
1244
1260
  return;
1245
1261
  }
1246
1262
  lateEnsuredRow = late.inserted ? { askId: late.row.askId, batchId: late.row.batchId } : undefined;
@@ -1351,7 +1367,8 @@ export class ToolApprovalCoordinator {
1351
1367
  let timer;
1352
1368
  let resolveAllowed;
1353
1369
  const allowedP = new Promise((resolve) => { resolveAllowed = resolve; });
1354
- let lastOutcome = "expired";
1370
+ let lastOutcome = SETTLE_OUTCOME.EXPIRED;
1371
+ let lastCause;
1355
1372
  const abortListeners = [];
1356
1373
  let selfEntry;
1357
1374
  let hostSettled;
@@ -1383,12 +1400,17 @@ export class ToolApprovalCoordinator {
1383
1400
  }
1384
1401
  return shapeDeny(false);
1385
1402
  };
1386
- const settle = (allowed, outcome, updatedInput, hostSettledBy, denyReason, readRootGrant) => {
1403
+ const settle = (verdict) => {
1387
1404
  if (done)
1388
1405
  return;
1389
1406
  done = true;
1407
+ const allowed = verdict.by === "human" && verdict.allowed;
1408
+ const outcome = verdict.by === "system" ? SETTLE_OUTCOME.EXPIRED : verdict.allowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED;
1409
+ const hostSettledBy = verdict.hostSettledBy;
1410
+ const updatedInput = verdict.by === "human" ? verdict.updatedInput : undefined;
1411
+ const readRootGrant = verdict.by === "human" ? verdict.readRootGrant : undefined;
1390
1412
  hostSettled = hostSettledBy;
1391
- denyReasonSettled = allowed ? undefined : denyReason;
1413
+ denyReasonSettled = verdict.by === "human" && !verdict.allowed ? verdict.denyReason : undefined;
1392
1414
  if (timer)
1393
1415
  clearTimeout(timer);
1394
1416
  for (const [sig, fn] of abortListeners)
@@ -1407,19 +1429,23 @@ export class ToolApprovalCoordinator {
1407
1429
  }
1408
1430
  }
1409
1431
  lastOutcome = outcome;
1432
+ lastCause = verdict.by === "system" ? verdict.cause : undefined;
1410
1433
  releaseAdmission();
1411
1434
  if (selfEntry)
1412
1435
  selfEntry.settledOutcome = outcome;
1413
- const shutdownEnded = this.shutdownDraining && outcome === "expired";
1414
- const settleRecord = () => ({
1415
- approvalId: wireApprovalId,
1416
- outcome,
1417
- ...(windowRouteUnavailable ? { parkRouted: true } : {}),
1418
- ...(hostSettledBy !== undefined ? { settledBy: hostSettledBy } : {}),
1419
- ...(shutdownEnded ? { cause: SHUTDOWN_BEFORE_PARK } : {}),
1420
- settledAtMs: Date.now(),
1421
- });
1422
- if (shutdownEnded && windowRouteUnavailable && this.unattendedRoutesToPark && this.parkCommits !== undefined) {
1436
+ const endedWhileDraining = this.shutdownDraining && verdict.by === "system";
1437
+ const settleRecord = () => {
1438
+ const common = {
1439
+ approvalId: wireApprovalId,
1440
+ ...(windowRouteUnavailable ? { parkRouted: true } : {}),
1441
+ ...(hostSettledBy !== undefined ? { settledBy: hostSettledBy } : {}),
1442
+ settledAtMs: Date.now(),
1443
+ };
1444
+ return verdict.by === "system"
1445
+ ? { ...common, outcome: SETTLE_OUTCOME.EXPIRED, cause: verdict.cause }
1446
+ : { ...common, outcome: verdict.allowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED };
1447
+ };
1448
+ if (endedWhileDraining && windowRouteUnavailable && this.unattendedRoutesToPark && this.parkCommits !== undefined) {
1423
1449
  this.parksAwaitingCommit.add({
1424
1450
  approvalId: wireApprovalId,
1425
1451
  taskId: origin.taskId,
@@ -1434,23 +1460,26 @@ export class ToolApprovalCoordinator {
1434
1460
  }
1435
1461
  resolveAllowed({ allowed, ...(allowed && updatedInput !== undefined ? { updatedInput } : {}), ...(allowed && readRootGrant !== undefined ? { readRootGrant } : {}) });
1436
1462
  };
1437
- const broadcastClaimTruth = (read) => {
1463
+ const broadcastClaimTruth = (read, armCause) => {
1438
1464
  if (askId === undefined)
1439
1465
  return;
1440
- this.dispatchClaimTruth(askId, read);
1466
+ this.dispatchClaimTruth(askId, read, armCause);
1441
1467
  };
1442
- const settleFromClaimLoss = (read) => {
1468
+ const settleFromClaimLoss = (read, armCause) => {
1443
1469
  switch (read.kind) {
1444
1470
  case "decided": {
1445
- const args = settleArgsOfDecided(read.outcome);
1446
- settle(args.allowed, args.allowed ? "allowed" : "denied", args.updatedInput, args.settledBy);
1447
- broadcastClaimTruth(read);
1471
+ settle(verdictOfDecided(settleArgsOfDecided(read.outcome)));
1472
+ broadcastClaimTruth(read, armCause);
1448
1473
  return true;
1449
1474
  }
1475
+ case "ended":
1476
+ settle({ by: "system", cause: read.cause });
1477
+ broadcastClaimTruth(read, armCause);
1478
+ return true;
1450
1479
  case "parked":
1451
1480
  windowRouteUnavailable = true;
1452
- settle(false, "expired");
1453
- broadcastClaimTruth(read);
1481
+ settle({ by: "system", cause: read.cause ?? armCause });
1482
+ broadcastClaimTruth(read, armCause);
1454
1483
  return true;
1455
1484
  case "absent":
1456
1485
  case "edit-in-flight":
@@ -1461,15 +1490,15 @@ export class ToolApprovalCoordinator {
1461
1490
  }
1462
1491
  }
1463
1492
  };
1464
- const lateClaimLossSettle = (current, intent) => {
1493
+ const lateClaimLossSettle = (current, intent, armCause) => {
1465
1494
  if (askId === undefined)
1466
1495
  return;
1467
1496
  const read = this.classifyClaimLoss(askId, current, `late:${intent}`);
1468
1497
  if (done) {
1469
- broadcastClaimTruth(read);
1498
+ broadcastClaimTruth(read, armCause);
1470
1499
  return;
1471
1500
  }
1472
- settleFromClaimLoss(read);
1501
+ settleFromClaimLoss(read, armCause);
1473
1502
  };
1474
1503
  const boundedClaim = (mk, label, timeoutMs, late) => {
1475
1504
  const release = this.admitTerminalClaim(askId ?? wireApprovalId);
@@ -1510,7 +1539,7 @@ export class ToolApprovalCoordinator {
1510
1539
  }
1511
1540
  if (probed.kind === "row") {
1512
1541
  const read = this.classifyClaimLoss(pinnedAskId, probed.current, `yield-probe:${arm.intent}`);
1513
- if (settleFromClaimLoss(read))
1542
+ if (settleFromClaimLoss(read, arm.cause))
1514
1543
  return;
1515
1544
  if (read.kind === "edit-in-flight")
1516
1545
  lastLoss = probed.current;
@@ -1547,10 +1576,10 @@ export class ToolApprovalCoordinator {
1547
1576
  arm.onLostTruth?.(outcome.current);
1548
1577
  const read = this.classifyClaimLoss(pinnedAskId, outcome.current, `claim:${arm.intent}`);
1549
1578
  if (done) {
1550
- broadcastClaimTruth(read);
1579
+ broadcastClaimTruth(read, arm.cause);
1551
1580
  return;
1552
1581
  }
1553
- if (settleFromClaimLoss(read))
1582
+ if (settleFromClaimLoss(read, arm.cause))
1554
1583
  return;
1555
1584
  if (read.kind === "absent") {
1556
1585
  arm.settleByIntent();
@@ -1564,24 +1593,24 @@ export class ToolApprovalCoordinator {
1564
1593
  return;
1565
1594
  if (this.editedDecisionInFlight(pinnedAskId)) {
1566
1595
  windowRouteUnavailable = true;
1567
- settle(false, "expired");
1596
+ settle({ by: "system", cause: arm.cause });
1568
1597
  return;
1569
1598
  }
1570
- if (lastLoss !== undefined && settleFromClaimLoss(this.classifyClaimLoss(pinnedAskId, lastLoss, `claim-tail:${arm.intent}`)))
1599
+ if (lastLoss !== undefined && settleFromClaimLoss(this.classifyClaimLoss(pinnedAskId, lastLoss, `claim-tail:${arm.intent}`), arm.cause))
1571
1600
  return;
1572
1601
  recordFailOpen(ambiguousCasFailOpenTag(arm.intent), `askId=${pinnedAskId} attempts=${attempt} claims=${claimsIssued} rejects=${rejects} elapsedMs=${Date.now() - ladderStartedAt}`);
1573
1602
  arm.settleByIntent();
1574
1603
  };
1575
- const settleParkRouteIfUnsettled = (hostSettledBy) => {
1604
+ const settleParkRouteIfUnsettled = (hostSettledBy, cause) => {
1576
1605
  if (done)
1577
1606
  return;
1578
1607
  windowRouteUnavailable = true;
1579
- settle(false, "expired", undefined, hostSettledBy);
1608
+ settle({ by: "system", cause, ...(hostSettledBy !== undefined ? { hostSettledBy } : {}) });
1580
1609
  };
1581
- const windowExpired = (hostSelfReport) => {
1610
+ const windowExpired = (hostSelfReport, cause) => {
1582
1611
  void (async () => {
1583
1612
  if (!this.askStore || askId === undefined || batchId === undefined) {
1584
- settleParkRouteIfUnsettled(hostSelfReport);
1613
+ settleParkRouteIfUnsettled(hostSelfReport, cause);
1585
1614
  return;
1586
1615
  }
1587
1616
  const store = this.askStore;
@@ -1589,45 +1618,46 @@ export class ToolApprovalCoordinator {
1589
1618
  const pinnedBatchId = batchId;
1590
1619
  await resolveLocalOutcome({
1591
1620
  intent: "expire",
1621
+ cause,
1592
1622
  storeLabel: "claimTerminal(window)",
1593
- claim: (timeoutMs) => boundedClaim(() => settleByPolicy(store, { askId: pinnedAskId, batchId: pinnedBatchId }, this.unattendedPolicy), "claimTerminal(window)", timeoutMs, (late) => {
1623
+ claim: (timeoutMs) => boundedClaim(() => settleByPolicy(store, { askId: pinnedAskId, batchId: pinnedBatchId }, this.unattendedPolicy, cause), "claimTerminal(window)", timeoutMs, (late) => {
1594
1624
  if (!late.claimed) {
1595
- lateClaimLossSettle(late.current, "expire");
1625
+ lateClaimLossSettle(late.current, "expire", cause);
1596
1626
  return;
1597
1627
  }
1598
- this.settleSameAskIdParkRoute(pinnedAskId);
1599
- this.settleVoidedSiblings(late.voidedSiblings);
1628
+ this.settleSameAskIdParkRoute(pinnedAskId, cause);
1629
+ this.settleVoidedSiblings(late.voidedSiblings, APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN);
1600
1630
  this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, late.voidedSiblings, "superseded_by_park", Date.now(), origin.taskId));
1601
1631
  }),
1602
1632
  onWon: (res) => {
1603
1633
  windowRouteUnavailable = true;
1604
- settle(false, "expired");
1605
- this.settleSameAskIdParkRoute(pinnedAskId);
1606
- this.settleVoidedSiblings(res.voidedSiblings);
1634
+ settle({ by: "system", cause });
1635
+ this.settleSameAskIdParkRoute(pinnedAskId, cause);
1636
+ this.settleVoidedSiblings(res.voidedSiblings, APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN);
1607
1637
  this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, res.voidedSiblings, "superseded_by_park", Date.now(), origin.taskId));
1608
1638
  },
1609
- settleByIntent: () => settleParkRouteIfUnsettled(hostSelfReport),
1639
+ settleByIntent: () => settleParkRouteIfUnsettled(hostSelfReport, cause),
1610
1640
  });
1611
1641
  })();
1612
1642
  };
1613
- const forceParkNow = () => {
1643
+ const forceParkNow = (cause) => {
1614
1644
  if (done)
1615
1645
  return;
1616
1646
  if (!this.askStore || askId === undefined || batchId === undefined) {
1617
- settleParkRouteIfUnsettled(undefined);
1647
+ settleParkRouteIfUnsettled(undefined, cause);
1618
1648
  return;
1619
1649
  }
1620
- windowExpired(undefined);
1650
+ windowExpired(undefined, cause);
1621
1651
  };
1622
- timer = setTimeout(() => windowExpired("timeout"), this.askStore ? Math.max(0, persistedExpiresAtMs - Date.now()) : effectiveWindowMs);
1652
+ timer = setTimeout(() => windowExpired("timeout", APPROVAL_ENDING_CAUSES.TTL_EXPIRED), this.askStore ? Math.max(0, persistedExpiresAtMs - Date.now()) : effectiveWindowMs);
1623
1653
  timer.unref?.();
1624
- const runCancel = async () => {
1654
+ const runCancel = async (cause) => {
1625
1655
  if (!this.askStore || askId === undefined || batchId === undefined) {
1626
- settle(false, "expired");
1656
+ settle({ by: "system", cause });
1627
1657
  return;
1628
1658
  }
1629
1659
  if (this.hasOtherSuspendedOwner(askId, selfEntry)) {
1630
- settle(false, "expired");
1660
+ settle({ by: "system", cause });
1631
1661
  return;
1632
1662
  }
1633
1663
  const store = this.askStore;
@@ -1640,52 +1670,53 @@ export class ToolApprovalCoordinator {
1640
1670
  };
1641
1671
  await resolveLocalOutcome({
1642
1672
  intent: "cancel",
1673
+ cause,
1643
1674
  storeLabel: "claimTerminal(cancel)",
1644
- claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel"), "claimTerminal(cancel)", timeoutMs, (late) => {
1675
+ claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel", { cause }), "claimTerminal(cancel)", timeoutMs, (late) => {
1645
1676
  if (!late.claimed) {
1646
1677
  cancelLostTruth(late.current);
1647
- lateClaimLossSettle(late.current, "cancel");
1678
+ lateClaimLossSettle(late.current, "cancel", cause);
1648
1679
  return;
1649
1680
  }
1650
- this.settleVoidedSiblings([pinnedAskId]);
1681
+ this.settleVoidedSiblings([pinnedAskId], cause);
1651
1682
  this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
1652
1683
  this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
1653
1684
  }),
1654
1685
  onWon: () => {
1655
- settle(false, "expired");
1656
- this.settleVoidedSiblings([pinnedAskId]);
1686
+ settle({ by: "system", cause });
1687
+ this.settleVoidedSiblings([pinnedAskId], cause);
1657
1688
  this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
1658
1689
  this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
1659
1690
  },
1660
1691
  onLostTruth: cancelLostTruth,
1661
- settleByIntent: () => settle(false, "expired"),
1692
+ settleByIntent: () => settle({ by: "system", cause }),
1662
1693
  });
1663
1694
  };
1664
1695
  if (signal) {
1665
- const onTaskAbort = () => void runCancel();
1696
+ const onTaskAbort = () => void runCancel(APPROVAL_ENDING_CAUSES.RUN_CANCELLED);
1666
1697
  signal.addEventListener("abort", onTaskAbort, { once: true });
1667
1698
  abortListeners.push([signal, onTaskAbort]);
1668
1699
  }
1669
1700
  let liveConnections = aliveCtxs.length;
1670
1701
  const goneCtxs = new Set();
1671
- const onTargetGone = (c) => {
1702
+ const onTargetGone = (c, cause) => {
1672
1703
  if (goneCtxs.has(c))
1673
1704
  return;
1674
1705
  goneCtxs.add(c);
1675
1706
  liveConnections--;
1676
1707
  if (liveConnections <= 0)
1677
- void runCancel();
1708
+ void runCancel(cause);
1678
1709
  };
1679
1710
  for (const c of aliveCtxs) {
1680
1711
  if (!c.abortSignal)
1681
1712
  continue;
1682
- const onCtxAbort = () => onTargetGone(c);
1713
+ const onCtxAbort = () => onTargetGone(c, APPROVAL_ENDING_CAUSES.RUN_CANCELLED);
1683
1714
  c.abortSignal.addEventListener("abort", onCtxAbort, { once: true });
1684
1715
  abortListeners.push([c.abortSignal, onCtxAbort]);
1685
1716
  }
1686
1717
  const earlyAbortDuringEnsure = signal?.aborted === true || aliveCtxs.some((c) => c.abortSignal?.aborted === true);
1687
1718
  if (earlyAbortDuringEnsure) {
1688
- await runCancel();
1719
+ await runCancel(APPROVAL_ENDING_CAUSES.RUN_CANCELLED);
1689
1720
  if (done) {
1690
1721
  return shapeOutcome(await allowedP);
1691
1722
  }
@@ -1712,7 +1743,7 @@ export class ToolApprovalCoordinator {
1712
1743
  const pendingEntry = {
1713
1744
  settle,
1714
1745
  forceParkNow,
1715
- settleParkRoute: () => settleParkRouteIfUnsettled(),
1746
+ settleParkRoute: (cause) => settleParkRouteIfUnsettled(undefined, cause),
1716
1747
  owner: origin.owner,
1717
1748
  ...(primary ? { ctxTaskId: primary.taskId, originCtx: primary } : {}),
1718
1749
  ...(reachability === "unreached" ? { suspended: true } : {}),
@@ -1756,7 +1787,7 @@ export class ToolApprovalCoordinator {
1756
1787
  mintedAtMs: Date.now(),
1757
1788
  });
1758
1789
  if (this.shutdownDraining)
1759
- forceParkNow();
1790
+ forceParkNow(APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
1760
1791
  const cardFrame = askId !== undefined && cardForFrame !== undefined
1761
1792
  ? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now() })
1762
1793
  : undefined;
@@ -1799,30 +1830,31 @@ export class ToolApprovalCoordinator {
1799
1830
  if (done)
1800
1831
  return;
1801
1832
  emitFailed = true;
1802
- settle(false, "expired");
1833
+ settle({ by: "system", cause: APPROVAL_ENDING_CAUSES.BROADCAST_LOST });
1803
1834
  };
1804
1835
  await resolveLocalOutcome({
1805
1836
  intent: "unreachable",
1837
+ cause: APPROVAL_ENDING_CAUSES.BROADCAST_LOST,
1806
1838
  storeLabel: "claimTerminal(emit-failed)",
1807
- claim: (timeoutMs) => boundedClaim(() => settleByPolicy(store, { askId: pinnedAskId, batchId: pinnedBatchId }, this.unattendedPolicy), "claimTerminal(emit-failed)", timeoutMs, (late) => {
1839
+ claim: (timeoutMs) => boundedClaim(() => settleByPolicy(store, { askId: pinnedAskId, batchId: pinnedBatchId }, this.unattendedPolicy, APPROVAL_ENDING_CAUSES.BROADCAST_LOST), "claimTerminal(emit-failed)", timeoutMs, (late) => {
1808
1840
  if (!late.claimed) {
1809
- lateClaimLossSettle(late.current, "unreachable");
1841
+ lateClaimLossSettle(late.current, "unreachable", APPROVAL_ENDING_CAUSES.BROADCAST_LOST);
1810
1842
  return;
1811
1843
  }
1812
- this.settleSameAskIdParkRoute(pinnedAskId);
1813
- this.settleVoidedSiblings(late.voidedSiblings);
1844
+ this.settleSameAskIdParkRoute(pinnedAskId, APPROVAL_ENDING_CAUSES.BROADCAST_LOST);
1845
+ this.settleVoidedSiblings(late.voidedSiblings, APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN);
1814
1846
  }),
1815
1847
  onWon: (res) => {
1816
1848
  settleUnreachable();
1817
- this.settleSameAskIdParkRoute(pinnedAskId);
1818
- this.settleVoidedSiblings(res.voidedSiblings);
1849
+ this.settleSameAskIdParkRoute(pinnedAskId, APPROVAL_ENDING_CAUSES.BROADCAST_LOST);
1850
+ this.settleVoidedSiblings(res.voidedSiblings, APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN);
1819
1851
  },
1820
1852
  settleByIntent: settleUnreachable,
1821
1853
  });
1822
1854
  }
1823
1855
  else if (!done) {
1824
1856
  emitFailed = true;
1825
- settle(false, "expired");
1857
+ settle({ by: "system", cause: APPROVAL_ENDING_CAUSES.BROADCAST_LOST });
1826
1858
  }
1827
1859
  }
1828
1860
  })();
@@ -1836,6 +1868,7 @@ export class ToolApprovalCoordinator {
1836
1868
  type: "tool_approval_complete",
1837
1869
  approvalId: wireApprovalId,
1838
1870
  outcome: lastOutcome,
1871
+ ...(lastCause !== undefined ? { cause: lastCause } : {}),
1839
1872
  ...(parkRouted ? { parked: true } : {}),
1840
1873
  })
1841
1874
  : undefined)
@@ -1845,7 +1878,7 @@ export class ToolApprovalCoordinator {
1845
1878
  return this.unattendedOutcome({ arm: "emit_failed", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1846
1879
  if (windowRouteUnavailable)
1847
1880
  return shapeOutcome(settled);
1848
- if (!emitSettled && lastOutcome === "expired")
1881
+ if (!emitSettled && lastOutcome === SETTLE_OUTCOME.EXPIRED)
1849
1882
  return this.unattendedOutcome({ arm: "emit_unsettled", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1850
1883
  return shapeOutcome(settled);
1851
1884
  }
@@ -1941,7 +1974,7 @@ export class ToolApprovalCoordinator {
1941
1974
  return this.notPending();
1942
1975
  const tomb = this.lookupParkTombstone(id);
1943
1976
  if (tomb === undefined || (tomb.owner !== null && tomb.owner !== principal)) {
1944
- return this.notPending("unknown_or_other_replica");
1977
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
1945
1978
  }
1946
1979
  return this.dispatchLateByRow(store, tomb, id, parsed, httpReq, principal);
1947
1980
  }
@@ -1952,13 +1985,13 @@ export class ToolApprovalCoordinator {
1952
1985
  }
1953
1986
  catch (err) {
1954
1987
  this.noteStoreError(err, "getAsk(respond-late)");
1955
- return this.notPending("unknown_or_other_replica");
1988
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
1956
1989
  }
1957
1990
  if (row === null)
1958
- return this.notPending("unknown_or_other_replica");
1991
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
1959
1992
  if (row.owner !== null && row.owner !== principal) {
1960
1993
  defaultLogger.warn("tool approval late decision rejected: the ask row changed owner since it parked", { askId: row.askId });
1961
- return this.notPending("unknown_or_other_replica");
1994
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
1962
1995
  }
1963
1996
  if (tomb.redeemed !== undefined)
1964
1997
  return this.replayRedeemReceipt(id, tomb, row, tomb.redeemed, parsed);
@@ -1985,11 +2018,12 @@ export class ToolApprovalCoordinator {
1985
2018
  };
1986
2019
  }
1987
2020
  case "DENIED":
1988
- return this.notPending("denied");
1989
- case "VOID":
1990
- return this.notPending("voided");
2021
+ case "VOID": {
2022
+ const rowCause = causeOfAskRow(row);
2023
+ return this.notPending(rowCause?.kind === "ending" ? rowCause.cause : undefined);
2024
+ }
1991
2025
  case "STREAM_PENDING":
1992
- return this.notPending("unknown_or_other_replica");
2026
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
1993
2027
  default: {
1994
2028
  const unhandled = row.state;
1995
2029
  defaultLogger.error("tool approval respond hit an unknown ask state", { askId: tomb.askId, state: unhandled });
@@ -2326,7 +2360,7 @@ export class ToolApprovalCoordinator {
2326
2360
  if (this.pending.get(id) !== entry) {
2327
2361
  if (winnerRow !== undefined)
2328
2362
  this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow));
2329
- const converged = decidedWon && entry.settledOutcome === (wantAllowed ? "allowed" : "denied");
2363
+ const converged = decidedWon && entry.settledOutcome === (wantAllowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED);
2330
2364
  if (!converged) {
2331
2365
  return this.notPending();
2332
2366
  }
@@ -2373,7 +2407,14 @@ export class ToolApprovalCoordinator {
2373
2407
  const allowed = parsed.value !== "deny";
2374
2408
  const settleArgs = opts?.settleOutcome !== undefined ? settleArgsOfDecided(opts.settleOutcome) : { allowed, updatedInput: parsed.updatedInput, settledBy: liveSettledBy(parsed.decidedBy) };
2375
2409
  const readRootGrant = opts?.settleOutcome === undefined || opts.settleOutcomeIsThisRequest === true ? parsed.readRootGrant : undefined;
2376
- entry.settle(settleArgs.allowed, settleArgs.allowed ? "allowed" : "denied", settleArgs.updatedInput, settleArgs.settledBy, settleArgs.allowed ? undefined : parsed.note, readRootGrant);
2410
+ entry.settle({
2411
+ by: "human",
2412
+ allowed: settleArgs.allowed,
2413
+ updatedInput: settleArgs.updatedInput,
2414
+ ...(settleArgs.settledBy !== undefined ? { hostSettledBy: settleArgs.settledBy } : {}),
2415
+ ...(!settleArgs.allowed && parsed.note !== undefined ? { denyReason: parsed.note } : {}),
2416
+ ...(readRootGrant !== undefined ? { readRootGrant } : {}),
2417
+ });
2377
2418
  const updatedInputDelivered = opts?.updatedInputDelivered ?? true;
2378
2419
  const noteRecorded = opts?.noteRecorded ?? false;
2379
2420
  return {
@@ -2456,10 +2497,18 @@ export class ToolApprovalCoordinator {
2456
2497
  continue;
2457
2498
  if (this.editedDecisionInFlight(askId))
2458
2499
  continue;
2500
+ const rowCause = causeOfAskRow(row);
2501
+ if (rowCause?.kind === "ending") {
2502
+ const before = this.pendingByAskId.get(askId)?.size ?? 0;
2503
+ this.settleVoidedSiblings([askId], rowCause.cause);
2504
+ settled += before;
2505
+ continue;
2506
+ }
2459
2507
  const outcome = replayTerminalAskRow(row);
2460
2508
  if (outcome === "unavailable") {
2461
2509
  const before = this.pendingByAskId.get(askId)?.size ?? 0;
2462
- this.settleSameAskIdParkRoute(askId);
2510
+ const parkCause = (rowCause?.kind === "park_trigger" ? rowCause.cause : undefined) ?? (Date.now() >= row.expiresAtMs ? APPROVAL_ENDING_CAUSES.TTL_EXPIRED : APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
2511
+ this.settleSameAskIdParkRoute(askId, parkCause);
2463
2512
  settled += before;
2464
2513
  continue;
2465
2514
  }
@@ -47,7 +47,7 @@ type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTask
47
47
  type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "hasBidiControls" | "previewWithheld" | "execCwd" | "authoredBy";
48
48
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
49
49
  type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
50
- type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "inputHasBidi" | "rememberRefused";
50
+ type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "cause" | "inputHasBidi" | "rememberRefused";
51
51
  type _GuardAskFrame = AssertAllKeysHandled<Exclude<keyof ToolApprovalFrame, AskProjected | AskFrameServerMinted>>;
52
52
  type _GuardAskFrameReverse = AssertAllKeysHandled<Exclude<AskProjected | AskFrameServerMinted, keyof ToolApprovalFrame>>;
53
53
  type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end" | "tool_roster_delta" | "tool_progress" | "tool_disclosure";
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Task Trace projection (S1): the durable run event log → sema-registry's `TraceTurn[]` contract.
2
+ * Task Trace projection (S1): the durable run event log → web-admin's `TraceTurn[]` contract.
3
3
  *
4
4
  * The event log (src/runs.ts) is a flat per-task seq stream: reasoning / text / tool_start / tool_end /
5
- * turn_end / compacted / done / suspended / failed. sema-registry renders an ORDERED `blocks[]` transcript
5
+ * turn_end / compacted / done / suspended / failed. web-admin renders an ORDERED `blocks[]` transcript
6
6
  * (CC-style thinking↔tool interleave), grouped into turns. This is pure presentation/projection = the
7
7
  * SERVICE layer (core [R57] cross-review: do NOT push this shape into core — different consumer, different shape).
8
8
  *
@@ -1031,7 +1031,7 @@ export declare function taskSummary(run: RunRecord, latestEventType?: string): T
1031
1031
  * 🔴 刻意**只**认这一个键,不做成映射表:帧名归一是历史包袱的一次性偿还,不是一条可以长胖的兼容层。
1032
1032
  * 再有第二个名字对不齐,应当在源头修,而不是往这里加一行。 */
1033
1033
  export declare function normalizeRunEventType(type: string): string;
1034
- /** Map one durable event → a sema-registry SSE trace event (contract 2.3). null = not a client event. The
1034
+ /** Map one durable event → a web-admin SSE trace event (contract 2.3). null = not a client event. The
1035
1035
  * durable log is block-grained, so each block ships as one "delta" the client appends (stream mode = "delta"). */
1036
1036
  export declare function mapTraceEvent(type: string, seq: number, data: Record<string, unknown>): {
1037
1037
  event: string;