@sema-agent/server 7.131.0 → 7.133.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 (509) hide show
  1. package/MIGRATION.md +575 -0
  2. package/README.md +12 -9
  3. package/README.zh-CN.md +9 -6
  4. package/USAGE.md +326 -98
  5. package/deploy/sema-up/README.md +19 -1
  6. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
  7. package/deploy/sema-up/chart/templates/server.yaml +5 -2
  8. package/deploy/sema-up/stack/compose.yaml +3 -0
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  10. package/dist/adoption/plan.d.ts +3 -2
  11. package/dist/adoption/plan.js +7 -5
  12. package/dist/adoption/runner.js +3 -10
  13. package/dist/adoption/sql.d.ts +3 -2
  14. package/dist/adoption/sql.js +3 -5
  15. package/dist/adoption/wire.d.ts +4 -1
  16. package/dist/adoption/wire.js +1 -0
  17. package/dist/approval-ask-audit-store.d.ts +31 -13
  18. package/dist/approval-ask-audit-store.js +272 -74
  19. package/dist/approval-ask-machine.d.ts +15 -0
  20. package/dist/approval-ask-machine.js +6 -0
  21. package/dist/approval-ask-retention.d.ts +136 -0
  22. package/dist/approval-ask-retention.js +102 -0
  23. package/dist/approval-card.d.ts +82 -20
  24. package/dist/approval-card.js +46 -16
  25. package/dist/approval-deny-reasons.d.ts +1 -1
  26. package/dist/approval.d.ts +9 -0
  27. package/dist/approval.js +24 -3
  28. package/dist/audit.d.ts +33 -19
  29. package/dist/audit.js +12 -16
  30. package/dist/auth-bridge.d.ts +4 -0
  31. package/dist/auth-bridge.js +10 -11
  32. package/dist/bake-runner/main.js +3 -2
  33. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  34. package/dist/boot/deployment-mods.d.ts +31 -0
  35. package/dist/boot/deployment-mods.js +26 -0
  36. package/dist/boot/execution-env.d.ts +1 -1
  37. package/dist/boot/execution-env.js +27 -12
  38. package/dist/boot/execution-lane.js +11 -0
  39. package/dist/boot/leader.js +2 -0
  40. package/dist/boot/memory-boundary.d.ts +2 -2
  41. package/dist/boot/memory-boundary.js +2 -2
  42. package/dist/boot/memory-consolidation.d.ts +5 -4
  43. package/dist/boot/memory-consolidation.js +7 -6
  44. package/dist/boot/org-delivery.d.ts +28 -0
  45. package/dist/boot/org-delivery.js +20 -0
  46. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  47. package/dist/boot/read-deny-anchor-audit.js +36 -0
  48. package/dist/boot/reapers.d.ts +3 -0
  49. package/dist/boot/reapers.js +21 -1
  50. package/dist/boot/resolve-spec.d.ts +4 -5
  51. package/dist/boot/resolve-spec.js +74 -123
  52. package/dist/boot/runner-deps.d.ts +19 -8
  53. package/dist/boot/runner-deps.js +15 -2
  54. package/dist/boot/session-faces.d.ts +2 -7
  55. package/dist/boot/session-faces.js +4 -2
  56. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  57. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  58. package/dist/boot/stage-03-store-backend.js +2 -2
  59. package/dist/boot/stage-04-stores.d.ts +1 -1
  60. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  61. package/dist/boot/stage-05-execution-env.js +7 -10
  62. package/dist/boot/stage-06-runners.d.ts +4 -2
  63. package/dist/boot/stage-06-runners.js +11 -7
  64. package/dist/boot/stage-07-capability-layer.d.ts +4 -2
  65. package/dist/boot/stage-07-capability-layer.js +27 -6
  66. package/dist/boot/stage-08-reapers.d.ts +4 -2
  67. package/dist/boot/stage-08-reapers.js +2 -2
  68. package/dist/boot/stage-09-leader.d.ts +6 -9
  69. package/dist/boot/stage-10-http-server.d.ts +6 -9
  70. package/dist/boot/stage-10-http-server.js +7 -9
  71. package/dist/boot/stores.d.ts +1 -1
  72. package/dist/boot/stores.js +7 -8
  73. package/dist/boot/web-admin.d.ts +4 -3
  74. package/dist/boot/web-admin.js +186 -98
  75. package/dist/boot/workflow-orchestration.d.ts +2 -0
  76. package/dist/boot/workflow-orchestration.js +2 -1
  77. package/dist/brain.js +25 -25
  78. package/dist/breaker-key.d.ts +6 -0
  79. package/dist/breaker-key.js +29 -0
  80. package/dist/budget.js +5 -4
  81. package/dist/caller-text-echo.d.ts +23 -0
  82. package/dist/caller-text-echo.js +21 -0
  83. package/dist/capabilities/brand-identity.d.ts +26 -0
  84. package/dist/capabilities/brand-identity.js +27 -0
  85. package/dist/capabilities/center-plugins.d.ts +7 -0
  86. package/dist/capabilities/center-plugins.js +1 -1
  87. package/dist/capabilities/center-prompts.d.ts +32 -59
  88. package/dist/capabilities/center-prompts.js +18 -32
  89. package/dist/capabilities/composite-members.d.ts +1 -1
  90. package/dist/capabilities/composite-members.js +1 -1
  91. package/dist/capabilities/engine-capabilities.js +2 -1
  92. package/dist/capabilities/execution-lane.js +2 -2
  93. package/dist/capabilities/hands-lane.d.ts +30 -6
  94. package/dist/capabilities/hands-lane.js +11 -1
  95. package/dist/capabilities/mcp-probe.d.ts +48 -3
  96. package/dist/capabilities/mcp-probe.js +47 -135
  97. package/dist/capabilities/scenarios.d.ts +95 -45
  98. package/dist/capabilities/scenarios.js +53 -38
  99. package/dist/capabilities/skills.d.ts +19 -4
  100. package/dist/capabilities/skills.js +30 -10
  101. package/dist/capability-facts.d.ts +34 -4
  102. package/dist/capability-facts.js +28 -2
  103. package/dist/center-config-tier.d.ts +69 -0
  104. package/dist/center-config-tier.js +42 -0
  105. package/dist/checkpoint-identity.d.ts +16 -0
  106. package/dist/checkpoint-identity.js +19 -0
  107. package/dist/config-catalog.d.ts +17 -10
  108. package/dist/config-catalog.js +47 -32
  109. package/dist/config-d-migration.d.ts +3 -1
  110. package/dist/config-d-migration.js +2 -2
  111. package/dist/config-dir-trust.d.ts +114 -0
  112. package/dist/config-dir-trust.js +388 -0
  113. package/dist/config-invariants.d.ts +18 -5
  114. package/dist/config-invariants.js +35 -3
  115. package/dist/config-lkg.d.ts +9 -9
  116. package/dist/config-lkg.js +20 -4
  117. package/dist/config-provider.d.ts +9 -2
  118. package/dist/config-provider.js +18 -6
  119. package/dist/config-types.d.ts +75 -20
  120. package/dist/config-types.js +8 -0
  121. package/dist/config.d.ts +13 -0
  122. package/dist/config.js +137 -62
  123. package/dist/delegated-session-owner.d.ts +185 -0
  124. package/dist/delegated-session-owner.js +119 -0
  125. package/dist/device-protocol.d.ts +5 -3
  126. package/dist/device-protocol.js +1 -1
  127. package/dist/device-ws-hub.js +21 -2
  128. package/dist/device-ws-protocol.d.ts +70 -6
  129. package/dist/device-ws-protocol.js +50 -22
  130. package/dist/elicitation.js +3 -2
  131. package/dist/entry-problems.d.ts +34 -0
  132. package/dist/entry-problems.js +14 -0
  133. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  134. package/dist/env-name-allowlist-knobs.js +5 -4
  135. package/dist/exec-bounds.d.ts +15 -3
  136. package/dist/exec-bounds.js +4 -0
  137. package/dist/execution-lane-caps.d.ts +56 -10
  138. package/dist/execution-lane-caps.js +50 -0
  139. package/dist/field-conflict.d.ts +43 -0
  140. package/dist/field-conflict.js +20 -0
  141. package/dist/fleet/fleet-bus.d.ts +80 -6
  142. package/dist/fleet/fleet-bus.js +41 -9
  143. package/dist/hooks/classic-stations.d.ts +91 -0
  144. package/dist/hooks/classic-stations.js +27 -0
  145. package/dist/hooks/command-spawn.d.ts +57 -0
  146. package/dist/hooks/command-spawn.js +139 -0
  147. package/dist/hooks/hook-llm.d.ts +8 -5
  148. package/dist/hooks/hook-llm.js +79 -36
  149. package/dist/hooks/hook-runner.d.ts +349 -95
  150. package/dist/hooks/hook-runner.js +855 -568
  151. package/dist/hooks/hook-source.d.ts +44 -0
  152. package/dist/hooks/hook-source.js +40 -0
  153. package/dist/http/active-run-conflict.d.ts +5 -13
  154. package/dist/http/active-run-conflict.js +17 -27
  155. package/dist/http/admission.d.ts +8 -2
  156. package/dist/http/admission.js +188 -101
  157. package/dist/http/approval-sweeps.js +99 -21
  158. package/dist/http/caller-trust.d.ts +77 -0
  159. package/dist/http/caller-trust.js +143 -0
  160. package/dist/http/checkpoint-ctx.d.ts +41 -24
  161. package/dist/http/checkpoint-ctx.js +23 -16
  162. package/dist/http/claimed-run.d.ts +14 -0
  163. package/dist/http/claimed-run.js +15 -0
  164. package/dist/http/device-placement.d.ts +2 -4
  165. package/dist/http/device-placement.js +7 -11
  166. package/dist/http/dispatch.d.ts +0 -4
  167. package/dist/http/dispatch.js +15 -13
  168. package/dist/http/durable-run-leg.d.ts +6 -33
  169. package/dist/http/durable-run-leg.js +36 -10
  170. package/dist/http/health-self-report.d.ts +7 -5
  171. package/dist/http/health-self-report.js +4 -18
  172. package/dist/http/owner-access.d.ts +56 -0
  173. package/dist/http/owner-access.js +42 -0
  174. package/dist/http/principal-gate.d.ts +70 -28
  175. package/dist/http/principal-gate.js +61 -15
  176. package/dist/http/readiness.d.ts +11 -5
  177. package/dist/http/readiness.js +10 -3
  178. package/dist/http/refusal-body.d.ts +21 -0
  179. package/dist/http/refusal-body.js +4 -0
  180. package/dist/http/request-guards.d.ts +43 -23
  181. package/dist/http/request-guards.js +53 -17
  182. package/dist/http/resume-ctx-read.d.ts +2 -5
  183. package/dist/http/resume-ctx-read.js +7 -4
  184. package/dist/http/resume-legs.d.ts +28 -4
  185. package/dist/http/resume-legs.js +169 -91
  186. package/dist/http/retired-service-deps.d.ts +43 -0
  187. package/dist/http/retired-service-deps.js +18 -0
  188. package/dist/http/route-ctx.d.ts +44 -28
  189. package/dist/http/route-ctx.js +4 -0
  190. package/dist/http/route-table.js +2 -0
  191. package/dist/http/routes/a2a-serve.js +3 -4
  192. package/dist/http/routes/admin-config-refresh.js +1 -1
  193. package/dist/http/routes/admin-drain.js +1 -1
  194. package/dist/http/routes/admin-memory-optout.js +5 -1
  195. package/dist/http/routes/adoption.js +2 -9
  196. package/dist/http/routes/approvals-assistant.js +49 -48
  197. package/dist/http/routes/background.js +3 -2
  198. package/dist/http/routes/capabilities.js +21 -5
  199. package/dist/http/routes/config-catalog.d.ts +3 -3
  200. package/dist/http/routes/config-catalog.js +1 -1
  201. package/dist/http/routes/devices.js +3 -17
  202. package/dist/http/routes/diagnostics.js +6 -2
  203. package/dist/http/routes/images.js +6 -7
  204. package/dist/http/routes/leader.js +1 -8
  205. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  206. package/dist/http/routes/mcp-reconnect.js +3 -2
  207. package/dist/http/routes/memory-bundle.js +1 -1
  208. package/dist/http/routes/memory-compliance.js +1 -1
  209. package/dist/http/routes/memory-consolidation.js +1 -1
  210. package/dist/http/routes/memory-origin.js +1 -1
  211. package/dist/http/routes/memory-policy.d.ts +1 -1
  212. package/dist/http/routes/memory-policy.js +3 -2
  213. package/dist/http/routes/notify-wake.js +8 -21
  214. package/dist/http/routes/observability.js +2 -2
  215. package/dist/http/routes/principal-inbox.d.ts +39 -0
  216. package/dist/http/routes/principal-inbox.js +76 -0
  217. package/dist/http/routes/retention-ops.js +2 -9
  218. package/dist/http/routes/rules.js +5 -18
  219. package/dist/http/routes/runs.d.ts +5 -0
  220. package/dist/http/routes/runs.js +34 -107
  221. package/dist/http/routes/session-sync.js +15 -26
  222. package/dist/http/routes/sessions.js +30 -40
  223. package/dist/http/routes/shared-memory.js +2 -13
  224. package/dist/http/routes/side-query.js +8 -7
  225. package/dist/http/routes/tasks.js +67 -21
  226. package/dist/http/routes/workflows.d.ts +351 -0
  227. package/dist/http/routes/workflows.js +153 -119
  228. package/dist/http/send.d.ts +33 -10
  229. package/dist/http/send.js +21 -4
  230. package/dist/http/server.d.ts +27 -18
  231. package/dist/http/server.js +34 -6
  232. package/dist/http/stored-body-replay.js +17 -4
  233. package/dist/http/submit-ctx.d.ts +25 -0
  234. package/dist/http/submit-ctx.js +40 -0
  235. package/dist/http/submit-replay.d.ts +4 -3
  236. package/dist/http/submit-replay.js +5 -4
  237. package/dist/http/wire-types.d.ts +11 -8
  238. package/dist/index.d.ts +2 -2
  239. package/dist/index.js +1 -2
  240. package/dist/lane-home-probe.d.ts +112 -0
  241. package/dist/lane-home-probe.js +101 -0
  242. package/dist/leader/endpoint.d.ts +4 -1
  243. package/dist/leader/endpoint.js +11 -10
  244. package/dist/limit-refusal.d.ts +59 -0
  245. package/dist/limit-refusal.js +15 -0
  246. package/dist/main.js +1 -1
  247. package/dist/mcp-assertion-header.d.ts +32 -19
  248. package/dist/mcp-assertion-header.js +10 -9
  249. package/dist/mcp-tool-faces.d.ts +5 -0
  250. package/dist/mcp-tool-faces.js +6 -4
  251. package/dist/memory-project-layer.d.ts +37 -0
  252. package/dist/memory-project-layer.js +46 -0
  253. package/dist/memory-scope.d.ts +24 -17
  254. package/dist/memory-scope.js +24 -8
  255. package/dist/memory-sync-client.d.ts +27 -13
  256. package/dist/memory-sync-client.js +107 -18
  257. package/dist/model-entry-refusal.d.ts +9 -1
  258. package/dist/model-entry-refusal.js +6 -1
  259. package/dist/model-plane-face.d.ts +51 -0
  260. package/dist/model-plane-face.js +24 -0
  261. package/dist/model-provider.d.ts +44 -12
  262. package/dist/model-provider.js +21 -2
  263. package/dist/model-route-endpoint.d.ts +9 -12
  264. package/dist/model-route-endpoint.js +8 -11
  265. package/dist/model-route-env.d.ts +22 -0
  266. package/dist/model-route-env.js +2 -0
  267. package/dist/model-route-gap.d.ts +60 -30
  268. package/dist/model-route-gap.js +21 -15
  269. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  270. package/dist/observability/corrupt-read-seat.js +9 -0
  271. package/dist/observability/cost-taxonomy.d.ts +7 -7
  272. package/dist/observability/err-text.d.ts +6 -0
  273. package/dist/observability/err-text.js +18 -4
  274. package/dist/observability/fail-open.d.ts +76 -14
  275. package/dist/observability/fail-open.js +76 -14
  276. package/dist/observability/metrics.js +4 -1
  277. package/dist/observability/prompt-manifest.d.ts +33 -82
  278. package/dist/observability/prompt-manifest.js +122 -17
  279. package/dist/observability/run-terminal.js +14 -6
  280. package/dist/observability/secret-env-scrub.d.ts +13 -1
  281. package/dist/observability/secret-env-scrub.js +13 -1
  282. package/dist/observability/tool-trace.d.ts +16 -21
  283. package/dist/observability/tool-trace.js +58 -49
  284. package/dist/operator-ask.d.ts +10 -0
  285. package/dist/operator-ask.js +3 -0
  286. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  287. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  288. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  289. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  290. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  291. package/dist/orchestration/workflow-notify-journal.js +27 -34
  292. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  293. package/dist/orchestration/workflow-park-decision.js +118 -0
  294. package/dist/org-delivery-gate.d.ts +81 -0
  295. package/dist/org-delivery-gate.js +59 -0
  296. package/dist/panel-tool-names.d.ts +23 -0
  297. package/dist/panel-tool-names.js +8 -0
  298. package/dist/parked-decide.d.ts +39 -6
  299. package/dist/parked-decide.js +98 -51
  300. package/dist/parked-revive-surface.d.ts +9 -2
  301. package/dist/parked-revive-surface.js +11 -8
  302. package/dist/per-task-image.d.ts +7 -1
  303. package/dist/per-task-image.js +3 -3
  304. package/dist/permission-rule-list.d.ts +1 -1
  305. package/dist/permission-rule-list.js +2 -2
  306. package/dist/plain-string-record.d.ts +25 -0
  307. package/dist/plain-string-record.js +32 -0
  308. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  309. package/dist/plugins/approval-ask-store-file.js +21 -0
  310. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  311. package/dist/plugins/approval-ask-store-memory.js +34 -2
  312. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  313. package/dist/plugins/approval-ask-store-sql.js +37 -1
  314. package/dist/plugins/blob-backend.d.ts +83 -15
  315. package/dist/plugins/blob-backend.js +68 -15
  316. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  317. package/dist/plugins/breaker-state-sql.js +4 -4
  318. package/dist/plugins/caching-session-store.d.ts +3 -0
  319. package/dist/plugins/caching-session-store.js +3 -0
  320. package/dist/plugins/checkpoint-store-sql.d.ts +28 -4
  321. package/dist/plugins/checkpoint-store-sql.js +59 -28
  322. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  323. package/dist/plugins/file-history-store-sql.js +3 -1
  324. package/dist/plugins/file-run-store.d.ts +5 -13
  325. package/dist/plugins/file-run-store.js +9 -0
  326. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  327. package/dist/plugins/fork-routing-session-store.js +12 -1
  328. package/dist/plugins/host-platform.d.ts +2 -0
  329. package/dist/plugins/host-platform.js +2 -1
  330. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  331. package/dist/plugins/k8s-bg-scripts.js +22 -0
  332. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  333. package/dist/plugins/local-checkpoint-store.js +11 -3
  334. package/dist/plugins/local-session-store.d.ts +1 -1
  335. package/dist/plugins/local-session-store.js +1 -1
  336. package/dist/plugins/local-session-window.d.ts +39 -0
  337. package/dist/plugins/local-session-window.js +54 -0
  338. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  339. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  340. package/dist/plugins/mailbox-store-sql.js +77 -52
  341. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  342. package/dist/plugins/memory-run-store.d.ts +5 -13
  343. package/dist/plugins/memory-run-store.js +8 -0
  344. package/dist/plugins/one-time-migrations.d.ts +245 -93
  345. package/dist/plugins/one-time-migrations.js +668 -207
  346. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  347. package/dist/plugins/org-delivery-quota-store.js +228 -0
  348. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  349. package/dist/plugins/permission-rule-store-sql.js +4 -18
  350. package/dist/plugins/pg-pool.js +39 -7
  351. package/dist/plugins/pg-session-storage.d.ts +20 -8
  352. package/dist/plugins/pg-session-storage.js +34 -21
  353. package/dist/plugins/remote-env-adb.d.ts +12 -12
  354. package/dist/plugins/remote-env-adb.js +21 -6
  355. package/dist/plugins/remote-env-device.js +3 -0
  356. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  357. package/dist/plugins/remote-env-e2b.js +21 -9
  358. package/dist/plugins/remote-env-host.d.ts +9 -9
  359. package/dist/plugins/remote-env-host.js +10 -32
  360. package/dist/plugins/remote-env-k8s.d.ts +103 -38
  361. package/dist/plugins/remote-env-k8s.js +288 -95
  362. package/dist/plugins/remote-env-local-docker.d.ts +27 -13
  363. package/dist/plugins/remote-env-local-docker.js +82 -11
  364. package/dist/plugins/remote-env-ssh.d.ts +21 -12
  365. package/dist/plugins/remote-env-ssh.js +117 -5
  366. package/dist/plugins/remote-shell.d.ts +0 -1
  367. package/dist/plugins/remote-shell.js +1 -2
  368. package/dist/plugins/retention-store-sql.js +6 -2
  369. package/dist/plugins/run-store-sql.d.ts +16 -14
  370. package/dist/plugins/run-store-sql.js +22 -2
  371. package/dist/plugins/schema-consistency.d.ts +54 -0
  372. package/dist/plugins/schema-consistency.js +52 -0
  373. package/dist/plugins/schema-indexes.js +2 -0
  374. package/dist/plugins/send-user-file.js +2 -2
  375. package/dist/plugins/session-epoch-anchor.d.ts +39 -0
  376. package/dist/plugins/session-epoch-anchor.js +22 -0
  377. package/dist/plugins/session-store.d.ts +18 -8
  378. package/dist/plugins/session-store.js +15 -4
  379. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  380. package/dist/plugins/shared-memory-store-sql.js +5 -16
  381. package/dist/plugins/sql-driver.d.ts +205 -1
  382. package/dist/plugins/sql-driver.js +325 -13
  383. package/dist/plugins/sql-errors.d.ts +64 -0
  384. package/dist/plugins/sql-errors.js +57 -0
  385. package/dist/plugins/store-backend.d.ts +36 -1
  386. package/dist/plugins/store-backend.js +42 -15
  387. package/dist/plugins/store-contracts.d.ts +42 -0
  388. package/dist/plugins/store-contracts.js +1 -0
  389. package/dist/plugins/tidb-pool.d.ts +6 -1
  390. package/dist/plugins/tidb-pool.js +44 -10
  391. package/dist/plugins/tidb-session-storage.d.ts +8 -11
  392. package/dist/plugins/tidb-session-storage.js +6 -16
  393. package/dist/plugins/tidb-session-store.d.ts +24 -4
  394. package/dist/plugins/tidb-session-store.js +29 -5
  395. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  396. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  397. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  398. package/dist/plugins/workflow-run-store-sql.js +50 -9
  399. package/dist/posture-source.d.ts +18 -10
  400. package/dist/posture-source.js +4 -1
  401. package/dist/project-identity.d.ts +45 -6
  402. package/dist/project-identity.js +162 -8
  403. package/dist/project-layer-key.d.ts +28 -0
  404. package/dist/project-layer-key.js +29 -0
  405. package/dist/prompts-domain-validate.d.ts +17 -61
  406. package/dist/prompts-domain-validate.js +9 -119
  407. package/dist/question.js +3 -2
  408. package/dist/read-deny-entry.d.ts +25 -0
  409. package/dist/read-deny-entry.js +12 -0
  410. package/dist/read-face-posture.d.ts +37 -11
  411. package/dist/read-face-posture.js +32 -3
  412. package/dist/request-key-closure.d.ts +9 -2
  413. package/dist/request-key-closure.js +11 -5
  414. package/dist/request-value-closure.d.ts +5 -0
  415. package/dist/request-value-closure.js +11 -3
  416. package/dist/rule-entry-list.d.ts +38 -11
  417. package/dist/rule-entry-list.js +106 -30
  418. package/dist/rules-consent.js +3 -4
  419. package/dist/run-acceptance.d.ts +19 -3
  420. package/dist/run-acceptance.js +13 -1
  421. package/dist/run-local.js +54 -24
  422. package/dist/runs.d.ts +15 -8
  423. package/dist/runs.js +35 -1
  424. package/dist/runtime-caps-resolver.d.ts +16 -6
  425. package/dist/runtime-caps-resolver.js +18 -5
  426. package/dist/sandbox-pkg-source.d.ts +19 -1
  427. package/dist/sandbox-pkg-source.js +29 -7
  428. package/dist/security.d.ts +87 -9
  429. package/dist/security.js +52 -9
  430. package/dist/server-secret-env.d.ts +44 -18
  431. package/dist/server-secret-env.js +49 -8
  432. package/dist/session-error-wire.d.ts +19 -2
  433. package/dist/session-error-wire.js +5 -1
  434. package/dist/session-incarnation.d.ts +15 -5
  435. package/dist/session-incarnation.js +16 -9
  436. package/dist/session-titled-frame.d.ts +33 -0
  437. package/dist/session-titled-frame.js +4 -0
  438. package/dist/session-titler.d.ts +9 -1
  439. package/dist/session-titler.js +27 -6
  440. package/dist/spec-fields.d.ts +28 -16
  441. package/dist/spec-fields.js +66 -54
  442. package/dist/sql-engine-posture.d.ts +31 -4
  443. package/dist/sql-engine-posture.js +36 -1
  444. package/dist/stored-request-tool-axes-m19.d.ts +19 -4
  445. package/dist/stored-request-tool-axes-m19.js +30 -2
  446. package/dist/task-a2a.d.ts +9 -1
  447. package/dist/task-a2a.js +73 -47
  448. package/dist/task-cwd.d.ts +1 -1
  449. package/dist/task-mcp.d.ts +46 -58
  450. package/dist/task-mcp.js +82 -107
  451. package/dist/task-settings.d.ts +95 -108
  452. package/dist/task-settings.js +77 -98
  453. package/dist/text-bidi.d.ts +1 -48
  454. package/dist/text-bidi.js +3 -5
  455. package/dist/tighten-only-seats.d.ts +150 -0
  456. package/dist/tighten-only-seats.js +196 -0
  457. package/dist/tool-approval.d.ts +304 -88
  458. package/dist/tool-approval.js +623 -239
  459. package/dist/trace/core-keyset-guard.d.ts +122 -20
  460. package/dist/trace/engine-notice-wire.d.ts +11 -3
  461. package/dist/trace/engine-notice-wire.js +8 -1
  462. package/dist/trace/exit-seat.d.ts +28 -0
  463. package/dist/trace/exit-seat.js +76 -0
  464. package/dist/trace/ledger-events.d.ts +6 -3
  465. package/dist/trace/ledger-sink.js +4 -1
  466. package/dist/trace/model-segment.js +1 -1
  467. package/dist/trace/project.d.ts +98 -17
  468. package/dist/trace/project.js +98 -50
  469. package/dist/trace/projection-table.d.ts +100 -0
  470. package/dist/trace/projection-table.js +130 -0
  471. package/dist/trace/redact.d.ts +54 -0
  472. package/dist/trace/redact.js +778 -152
  473. package/dist/trace/self-minted-wire.d.ts +345 -1
  474. package/dist/trace/self-minted-wire.js +117 -1
  475. package/dist/trace/wire-projection-faces.d.ts +377 -3
  476. package/dist/trace/wire-projection-faces.js +164 -1
  477. package/dist/url-refusal.js +2 -0
  478. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  479. package/dist/web-admin-client/apply-effective.js +56 -5
  480. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  481. package/dist/web-admin-client/candidate-gate.js +3 -3
  482. package/dist/web-admin-client/facade.d.ts +1 -1
  483. package/dist/web-admin-client/facade.js +1 -1
  484. package/dist/web-admin-client/http-client.js +0 -6
  485. package/dist/web-admin-client/read-face.js +5 -9
  486. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  487. package/dist/web-admin-client/read-warnings.js +10 -0
  488. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  489. package/dist/web-admin-client/skills-mcp.js +11 -9
  490. package/dist/web-admin-client/types.d.ts +2 -3
  491. package/dist/workspace-cache.d.ts +35 -0
  492. package/dist/workspace-cache.js +36 -0
  493. package/dist/workspace-space.d.ts +3 -2
  494. package/dist/workspace-space.js +2 -0
  495. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +65 -10
  496. package/package.json +8 -4
  497. package/scripts/drain-check-local.mjs +34 -1
  498. package/scripts/drain-check.mysql.sql +30 -2
  499. package/scripts/drain-check.pg.sql +29 -2
  500. package/dist/capabilities/memory-notice.d.ts +0 -138
  501. package/dist/capabilities/memory-notice.js +0 -64
  502. package/dist/capabilities/prompt.d.ts +0 -17
  503. package/dist/capabilities/prompt.js +0 -6
  504. package/dist/mcp-content-origin.d.ts +0 -34
  505. package/dist/mcp-content-origin.js +0 -10
  506. package/dist/tool-axis-words.d.ts +0 -27
  507. package/dist/tool-axis-words.js +0 -9
  508. package/dist/wire-name-bound.d.ts +0 -14
  509. package/dist/wire-name-bound.js +0 -13
@@ -7,9 +7,11 @@ import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact
7
7
  import { hasBidiControls } from "./text-bidi.js";
8
8
  import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerprint.js";
9
9
  import { createLogger } from "./observability/logger.js";
10
+ import { errorKindOf } from "./observability/err-text.js";
10
11
  import { recordFailOpen } from "./observability/fail-open.js";
11
- import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
12
- import { currentParkArm, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readStoredCardEnvelope, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
+ import { ownerOfCheckpointScope } from "./security.js";
13
+ import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "./approval-ask-machine.js";
14
+ import { currentParkArm, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildReplayFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readStoredCardEnvelope, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, buildApprovalCompleteFrame, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
13
15
  import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
14
16
  import { APPROVAL_ENDING_CAUSES, SETTLE_OUTCOME, causeOfAskRow } from "./approval-deny-reasons.js";
15
17
  import { settleByPolicy } from "./approval-policy-settlement.js";
@@ -18,6 +20,7 @@ import { governanceOriginOf } from "./runtime-governance.js";
18
20
  import { callGrantBindingOf, questionGrantBindingOf } from "./standing-answer-grant.js";
19
21
  import { operatorAsk } from "./operator-ask.js";
20
22
  import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
23
+ import { refusalBody } from "./http/refusal-body.js";
21
24
  const defaultLogger = createLogger();
22
25
  export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
23
26
  export function isApprovalGateKind(k) {
@@ -32,15 +35,17 @@ const DEFAULT_APPROVAL_TTL_MS = 5 * 60_000;
32
35
  const DEFAULT_UNREACHED_ASK_TTL_MS = 60 * 60_000;
33
36
  const SUSPENDED_POLL_MAX_PER_TICK = 64;
34
37
  const SUSPENDED_POLL_BUDGET_MS = 5_000;
38
+ const ADOPT_OPEN_ASKS_MAX = 1_000;
35
39
  const EDITED_DECISION_MARK_CAP_MS = 10_000;
36
40
  const DEFAULT_WINDOW_MARGIN_MS = 10_000;
37
41
  const SHUTDOWN_DRAIN_POLL_MS = 50;
38
42
  const MAX_ALLOW_SESSIONS = 4096;
39
43
  const DEFAULT_ADMIT_MAX_PER_TASK = 32;
40
44
  const DEFAULT_ADMIT_MAX_PER_OWNER = 256;
41
- const DEFAULT_PARK_TOMBSTONE_MAX = 2000;
42
- const DEFAULT_PARK_TOMBSTONE_TTL_MS = 60 * 60_000;
43
- const DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX = 128;
45
+ const DEFAULT_PARK_REDEMPTION_MAX = 2000;
46
+ const DEFAULT_PARK_REDEMPTION_TTL_MS = 60 * 60_000;
47
+ const DEFAULT_PARK_REDEMPTION_PER_PRINCIPAL_MAX = 128;
48
+ const PARK_REDEMPTION_ADMISSION_RETRY_AFTER_SEC = 5;
44
49
  const PARKING_RETRY_AFTER_MS = 250;
45
50
  export function approvalDecidePath(sessionId) {
46
51
  return `/v1/approvals/${encodeURIComponent(sessionId)}/decide`;
@@ -63,6 +68,22 @@ function durableRuleMaterial(material) {
63
68
  return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
64
69
  }
65
70
  export const NOT_PENDING_UNKNOWN_CAUSE = "unknown_or_other_replica";
71
+ export const TOOL_APPROVAL_READ_STATES = ["pending", "decided", "parked", "void"];
72
+ function decidedRowFacts(row) {
73
+ if (row.state !== "DECIDED" || (row.decision !== "approve" && row.decision !== "deny") || row.provisional || row.decidedAtMs === null)
74
+ return undefined;
75
+ return { decision: row.decision === "approve" ? "allow" : "deny", decidedAtMs: row.decidedAtMs, ...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}) };
76
+ }
77
+ function buildAskRowReadProjection(row, nowMs) {
78
+ const frame = buildReplayFrame(row, nowMs);
79
+ return {
80
+ approvalId: row.askId,
81
+ ts: row.createdAtMs,
82
+ expiresAtMs: row.expiresAtMs,
83
+ ...(row.sessionId !== row.taskId ? { sessionId: row.sessionId } : {}),
84
+ ...(frame !== undefined ? { toolName: frame.card.toolName, frame } : {}),
85
+ };
86
+ }
66
87
  export const ASK_DECISION_CONSUMER_ABSENT = "absent";
67
88
  const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
68
89
  const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
@@ -181,11 +202,45 @@ function mandatedFromSeat(seat) {
181
202
  return false;
182
203
  }
183
204
  }
205
+ function askRowVisibleTo(row, principal) {
206
+ return row !== null && (row.owner === null || row.owner === principal);
207
+ }
208
+ function waitingLegsReached(entry, othersReached) {
209
+ return (entry.adopted === true ? 0 : 1) + othersReached;
210
+ }
211
+ function withConsumerDisclosure(result, waitingLegs) {
212
+ return waitingLegs > 0 ? result : { status: result.status, body: { ...result.body, consumerState: ASK_DECISION_CONSUMER_ABSENT } };
213
+ }
214
+ function livePendingRowOf(e) {
215
+ const listFrame = e.adopted === true ? e.cardFrame : (e.cardFrame ?? e.frame);
216
+ const approvalCard = e.adopted === true ? e.cardFrame.approvalCard : e.frame.approvalCard;
217
+ return {
218
+ approvalId: e.approvalId,
219
+ toolName: e.toolName,
220
+ ts: e.registeredAtMs,
221
+ expiresAtMs: e.expiresAtMs,
222
+ ...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
223
+ ...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
224
+ ...(e.governanceForced ? { governanceForced: true } : {}),
225
+ ...(e.mandated ? { mandated: true } : {}),
226
+ ...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
227
+ ...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
228
+ ...(e.origin !== undefined ? { origin: e.origin } : {}),
229
+ ...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
230
+ ...(approvalCard !== undefined ? { approvalCard } : {}),
231
+ frame: listFrame,
232
+ ...(e.adopted === true ? { consumerState: ASK_DECISION_CONSUMER_ABSENT } : {}),
233
+ };
234
+ }
235
+ function canPresentWithoutDurableRow(c) {
236
+ return c.emitCard === undefined;
237
+ }
184
238
  export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty string of at most ${MAX_RULE_TEXT_CHARS} characters`;
185
239
  export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
186
240
  export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
187
241
  export const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited — send exactly one arm";
188
242
  export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with decision "allow" — it is the card\'s "Yes, and add directory" answer (allow this call and add the card\'s readRootCandidate.dir to this run\'s read directories)';
243
+ export const READ_ROOT_GRANT_NOT_ACCEPTED = 'readRootGrant is not accepted on this deployment — a read-directory grant is honored only on a single-user host-lane deployment (GET /v1/capabilities approvalReadRootGrant is false here); answer with plain decision "allow" instead';
189
244
  export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
190
245
  export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
191
246
  export function parseToolApprovalResponse(body) {
@@ -310,27 +365,48 @@ export function resolveStreamApprovalGate(input) {
310
365
  return { active: false, reason: "no_park_facility" };
311
366
  return { active: true, askStore: input.backend.approvalAsk() };
312
367
  }
368
+ export const APPROVAL_FRAME_FAMILIES = ["tool_approval", "approval_request"];
313
369
  export function createApprovalCardEmitter(sinks) {
314
- return async (frame) => {
315
- let accepted = false;
316
- if (sinks.appendDurable) {
317
- try {
318
- await sinks.appendDurable(frame);
319
- accepted = true;
320
- }
321
- catch {
322
- }
370
+ const present = [sinks.appendDurable, sinks.writeLive].filter((w) => w !== undefined);
371
+ const attempt = (write, frame) => (async () => {
372
+ try {
373
+ await write(frame);
374
+ return true;
323
375
  }
324
- if (sinks.writeLive) {
325
- try {
326
- await sinks.writeLive(frame);
327
- accepted = true;
328
- }
329
- catch {
376
+ catch (err) {
377
+ defaultLogger.warn("approval_card_sink_write_failed", { frame: frame.type, approvalId: frame.approvalId, errKind: errorKindOf(err) });
378
+ return false;
379
+ }
380
+ })();
381
+ const opened = new Map();
382
+ return async (frame, registration) => {
383
+ if (frame.type === "approval_request") {
384
+ const results = present.map((write) => attempt(write, frame));
385
+ opened.set(registration, results);
386
+ const accepted = await new Promise((resolve) => {
387
+ let pending = results.length;
388
+ if (pending === 0)
389
+ resolve(false);
390
+ for (const r of results) {
391
+ void r.then((ok) => {
392
+ if (ok)
393
+ resolve(true);
394
+ else if (--pending === 0)
395
+ resolve(false);
396
+ });
397
+ }
398
+ });
399
+ if (!accepted) {
400
+ opened.delete(registration);
401
+ throw new Error("approval card undeliverable — no sink accepted the frame (live stream closed and/or the durable append failed)");
330
402
  }
403
+ return;
331
404
  }
332
- if (!accepted)
333
- throw new Error("approval card undeliverable — no sink accepted the frame (live stream closed and/or the durable append failed)");
405
+ const results = opened.get(registration);
406
+ opened.delete(registration);
407
+ if (results === undefined)
408
+ return;
409
+ await Promise.all(present.map((write, i) => results[i].then((ok) => (ok ? attempt(write, frame) : false))));
334
410
  };
335
411
  }
336
412
  export function resolveApprovalLeg(input) {
@@ -412,6 +488,7 @@ export class ToolApprovalCoordinator {
412
488
  return this.terminalClaimsInFlightCount(askId);
413
489
  }
414
490
  pendingByAskId = new Map();
491
+ presentedCardWatchers = new Map();
415
492
  allowAllSessions = new Set();
416
493
  streams = new Map();
417
494
  ttlMs;
@@ -423,6 +500,7 @@ export class ToolApprovalCoordinator {
423
500
  admitByTask = new Map();
424
501
  admitByOwner = new Map();
425
502
  suspendedPollOffset = 0;
503
+ adoptedPollOffset = 0;
426
504
  editedDecisionsInFlight = new Map();
427
505
  storeErrorWarned = false;
428
506
  storeErrorTally = 0;
@@ -430,10 +508,10 @@ export class ToolApprovalCoordinator {
430
508
  ruleConsent;
431
509
  unattendedPolicy;
432
510
  ruleScopeRootFor;
433
- parkTombstones = new Map();
434
- parkTombstoneMax;
435
- parkTombstoneTtlMs;
436
- parkTombstonePerPrincipalMax;
511
+ parkRedemptions = new Map();
512
+ parkRedemptionMax;
513
+ parkRedemptionTtlMs;
514
+ parkRedemptionPerPrincipalMax;
437
515
  parkedRedeem;
438
516
  askAudit;
439
517
  sessionGrants;
@@ -454,9 +532,9 @@ export class ToolApprovalCoordinator {
454
532
  this.admitMaxPerOwner = opts?.admitMaxPerOwner ?? DEFAULT_ADMIT_MAX_PER_OWNER;
455
533
  this.unattendedPolicy = opts?.unattendedPolicy ?? "park";
456
534
  this.parkedRedeem = opts?.parkedRedeem;
457
- this.parkTombstoneMax = opts?.parkTombstoneMax ?? DEFAULT_PARK_TOMBSTONE_MAX;
458
- this.parkTombstoneTtlMs = opts?.parkTombstoneTtlMs ?? DEFAULT_PARK_TOMBSTONE_TTL_MS;
459
- this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
535
+ this.parkRedemptionMax = opts?.parkRedemptionMax ?? DEFAULT_PARK_REDEMPTION_MAX;
536
+ this.parkRedemptionTtlMs = opts?.parkRedemptionTtlMs ?? DEFAULT_PARK_REDEMPTION_TTL_MS;
537
+ this.parkRedemptionPerPrincipalMax = opts?.parkRedemptionPerPrincipalMax ?? DEFAULT_PARK_REDEMPTION_PER_PRINCIPAL_MAX;
460
538
  this.askAudit = opts?.askAudit;
461
539
  this.sessionGrants = opts?.sessionGrants;
462
540
  if (opts?.deploymentListAsks !== undefined && opts.sessionGrants === undefined) {
@@ -951,75 +1029,84 @@ export class ToolApprovalCoordinator {
951
1029
  }
952
1030
  }
953
1031
  }
954
- recordParkTombstone(approvalId, tomb) {
955
- const existing = this.parkTombstones.get(approvalId);
956
- if (existing !== undefined) {
957
- if (existing.redeemed !== undefined || existing.inflight !== undefined)
958
- return;
959
- if (Date.now() - existing.parkedAtMs <= this.parkTombstoneTtlMs)
960
- return;
961
- this.parkTombstones.delete(approvalId);
1032
+ parkRedemptionAdmission(owner) {
1033
+ const effectivePerPrincipalMax = Math.min(this.parkRedemptionPerPrincipalMax, this.parkRedemptionMax);
1034
+ let inflight = 0;
1035
+ let ownInflight = 0;
1036
+ for (const r of this.parkRedemptions.values()) {
1037
+ if (r.inflight === undefined)
1038
+ continue;
1039
+ inflight++;
1040
+ if (r.owner === owner)
1041
+ ownInflight++;
962
1042
  }
963
- const effectivePerPrincipalMax = Math.min(this.parkTombstonePerPrincipalMax, this.parkTombstoneMax);
1043
+ if (ownInflight < effectivePerPrincipalMax && inflight < this.parkRedemptionMax)
1044
+ return undefined;
1045
+ defaultLogger.warn("parked approval redemption refused at admission: too many redemptions in flight", { owner: owner ?? "<none>", ownInflight, inflight });
1046
+ return {
1047
+ status: 429,
1048
+ body: refusalBody({
1049
+ errorCode: "limit.rate_exceeded",
1050
+ message: "too many late approval answers are being applied right now — retry after the Retry-After interval",
1051
+ facts: { retryAfterSec: PARK_REDEMPTION_ADMISSION_RETRY_AFTER_SEC },
1052
+ }),
1053
+ };
1054
+ }
1055
+ openParkRedemption(askId, owner) {
1056
+ const opened = { owner, stampedAtMs: Date.now() };
1057
+ this.parkRedemptions.delete(askId);
1058
+ this.parkRedemptions.set(askId, opened);
1059
+ return opened;
1060
+ }
1061
+ retainParkRedemption(askId, memo) {
964
1062
  const now = Date.now();
965
- let ownCount = 0;
966
- let ownExpiredVictim;
967
- let ownBridgeVictim;
968
- let ownPlainVictim;
969
- let ownEvictable;
970
- let globalExpiredVictim;
971
- let globalBridgeVictim;
972
- for (const [k, t] of this.parkTombstones) {
973
- const expired = now - t.parkedAtMs > this.parkTombstoneTtlMs;
974
- if (globalExpiredVictim === undefined && expired && t.inflight === undefined)
975
- globalExpiredVictim = k;
976
- if (globalBridgeVictim === undefined && t.kind === "voidBridge" && t.inflight === undefined)
977
- globalBridgeVictim = k;
978
- if (t.owner !== tomb.owner)
1063
+ const effectivePerPrincipalMax = Math.min(this.parkRedemptionPerPrincipalMax, this.parkRedemptionMax);
1064
+ let receipts = 0;
1065
+ let ownReceipts = 0;
1066
+ let ownExpired;
1067
+ let ownOldest;
1068
+ let globalExpired;
1069
+ for (const [k, r] of this.parkRedemptions) {
1070
+ if (k === askId || r.redeemed === undefined || r.inflight !== undefined)
979
1071
  continue;
980
- ownCount++;
981
- if (t.inflight !== undefined)
1072
+ receipts++;
1073
+ const expired = now - r.stampedAtMs > this.parkRedemptionTtlMs;
1074
+ if (globalExpired === undefined && expired)
1075
+ globalExpired = k;
1076
+ if (r.owner !== memo.owner)
982
1077
  continue;
983
- if (ownEvictable === undefined)
984
- ownEvictable = k;
985
- if (ownExpiredVictim === undefined && expired)
986
- ownExpiredVictim = k;
987
- if (ownBridgeVictim === undefined && t.kind === "voidBridge")
988
- ownBridgeVictim = k;
989
- if (ownPlainVictim === undefined && t.kind === "park" && t.redeemed === undefined)
990
- ownPlainVictim = k;
991
- }
992
- const insertingBridge = tomb.kind === "voidBridge";
993
- if (ownCount >= effectivePerPrincipalMax) {
994
- const victim = insertingBridge ? (ownExpiredVictim ?? ownBridgeVictim) : (ownExpiredVictim ?? ownBridgeVictim ?? ownPlainVictim ?? ownEvictable);
995
- if (victim === undefined) {
996
- recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"} reason=${insertingBridge ? "bridge-would-evict-redeemable" : "all-inflight"}`);
997
- return;
1078
+ ownReceipts++;
1079
+ if (ownOldest === undefined)
1080
+ ownOldest = k;
1081
+ if (ownExpired === undefined && expired)
1082
+ ownExpired = k;
1083
+ }
1084
+ if (ownReceipts >= effectivePerPrincipalMax) {
1085
+ const victim = ownExpired ?? ownOldest;
1086
+ if (victim !== undefined)
1087
+ this.parkRedemptions.delete(victim);
1088
+ }
1089
+ else if (receipts >= this.parkRedemptionMax) {
1090
+ if (globalExpired === undefined) {
1091
+ recordFailOpen("server.approval.park-redemption-cap-refused", `owner=${memo.owner ?? "<none>"}`);
1092
+ if (this.parkRedemptions.get(askId) === memo)
1093
+ this.parkRedemptions.delete(askId);
1094
+ return false;
998
1095
  }
999
- this.parkTombstones.delete(victim);
1096
+ this.parkRedemptions.delete(globalExpired);
1000
1097
  }
1001
- else if (this.parkTombstones.size >= this.parkTombstoneMax && globalExpiredVictim !== undefined) {
1002
- this.parkTombstones.delete(globalExpiredVictim);
1003
- }
1004
- else if (this.parkTombstones.size >= this.parkTombstoneMax && !insertingBridge && globalBridgeVictim !== undefined) {
1005
- this.parkTombstones.delete(globalBridgeVictim);
1006
- }
1007
- else if (this.parkTombstones.size >= this.parkTombstoneMax) {
1008
- recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"}${insertingBridge ? " reason=bridge-refused-at-global-cap" : ""}`);
1009
- return;
1010
- }
1011
- this.parkTombstones.set(approvalId, tomb);
1098
+ memo.stampedAtMs = now;
1099
+ return true;
1012
1100
  }
1013
- lookupParkTombstone(approvalId) {
1014
- const tomb = this.parkTombstones.get(approvalId);
1015
- if (tomb === undefined)
1101
+ lookupParkRedemption(askId) {
1102
+ const r = this.parkRedemptions.get(askId);
1103
+ if (r === undefined)
1016
1104
  return undefined;
1017
- if (Date.now() - tomb.parkedAtMs > this.parkTombstoneTtlMs) {
1018
- if (tomb.inflight === undefined)
1019
- this.parkTombstones.delete(approvalId);
1105
+ if (r.inflight === undefined && Date.now() - r.stampedAtMs > this.parkRedemptionTtlMs) {
1106
+ this.parkRedemptions.delete(askId);
1020
1107
  return undefined;
1021
1108
  }
1022
- return tomb;
1109
+ return r;
1023
1110
  }
1024
1111
  notifyExternalDecision(askId, outcome) {
1025
1112
  const entries = this.pendingByAskId.get(askId);
@@ -1029,7 +1116,8 @@ export class ToolApprovalCoordinator {
1029
1116
  let settled = 0;
1030
1117
  for (const entry of [...entries]) {
1031
1118
  entry.settle(verdictOfDecided(args));
1032
- settled += 1;
1119
+ if (entry.adopted !== true)
1120
+ settled += 1;
1033
1121
  }
1034
1122
  return { settled };
1035
1123
  }
@@ -1211,7 +1299,6 @@ export class ToolApprovalCoordinator {
1211
1299
  let askId;
1212
1300
  let batchId;
1213
1301
  const card = this.askStore ? buildApprovalCard(frame, req, req.requiresRealApproval === true) : undefined;
1214
- let wireApprovalId = id;
1215
1302
  let cardForFrame = card;
1216
1303
  let persistedExpiresAtMs = expiresAtMs;
1217
1304
  let lateEnsuredRow;
@@ -1257,7 +1344,7 @@ export class ToolApprovalCoordinator {
1257
1344
  toolCallId: req.toolCallId,
1258
1345
  legKey,
1259
1346
  parentToolCallId: req.delegation?.parentToolCallId ?? null,
1260
- cardJson: buildApprovalCardEnvelope(id, card),
1347
+ cardJson: buildApprovalCardEnvelope(askId, card),
1261
1348
  boundInputHash: readBoundInputHash(req),
1262
1349
  ...durableRuleMaterial(ruleLaneMaterial),
1263
1350
  schemaVersion: 1,
@@ -1289,8 +1376,6 @@ export class ToolApprovalCoordinator {
1289
1376
  return existingOrCreated.inserted ? this.landUnattendedOutcome(unattended, existingOrCreated.row) : unattended;
1290
1377
  }
1291
1378
  persistedExpiresAtMs = existingOrCreated.row.expiresAtMs;
1292
- wireApprovalId = persistedEnvelope.approvalId;
1293
- frame.approvalId = wireApprovalId;
1294
1379
  cardForFrame = persistedEnvelope.card;
1295
1380
  if (cardForFrame.governanceForced === true)
1296
1381
  frame.governanceForced = true;
@@ -1359,8 +1444,8 @@ export class ToolApprovalCoordinator {
1359
1444
  if (!confirmedRowExists) {
1360
1445
  askId = undefined;
1361
1446
  batchId = undefined;
1362
- if (reachability === "unreached") {
1363
- const unattended = this.unattendedOutcome({ arm: "unreached_no_row", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1447
+ if (!aliveCtxs.some(canPresentWithoutDurableRow)) {
1448
+ const unattended = this.unattendedOutcome({ arm: "no_row_no_presenter", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1364
1449
  const landed = abandonRow(unattended);
1365
1450
  releaseAdmission();
1366
1451
  return landed ?? unattended;
@@ -1369,6 +1454,8 @@ export class ToolApprovalCoordinator {
1369
1454
  }
1370
1455
  }
1371
1456
  }
1457
+ const wireApprovalId = askId ?? id;
1458
+ frame.approvalId = wireApprovalId;
1372
1459
  let done = false;
1373
1460
  let emitFailed = false;
1374
1461
  let emitSettled = false;
@@ -1427,19 +1514,24 @@ export class ToolApprovalCoordinator {
1427
1514
  sig.removeEventListener("abort", fn);
1428
1515
  this.pending.delete(id);
1429
1516
  if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedRoutesToPark) {
1430
- this.recordParkTombstone(wireApprovalId, { kind: "park", askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
1431
1517
  parkRouted = true;
1432
1518
  }
1519
+ let lastLiveRegistration = false;
1433
1520
  if (askId !== undefined) {
1434
1521
  const siblings = this.pendingByAskId.get(askId);
1435
1522
  if (siblings && selfEntry) {
1436
1523
  siblings.delete(selfEntry);
1437
- if (siblings.size === 0)
1524
+ if (siblings.size === 0) {
1438
1525
  this.pendingByAskId.delete(askId);
1526
+ lastLiveRegistration = true;
1527
+ }
1439
1528
  }
1440
1529
  }
1441
1530
  lastOutcome = outcome;
1442
1531
  lastCause = verdict.by === "system" ? verdict.cause : undefined;
1532
+ if (lastLiveRegistration && askId !== undefined) {
1533
+ this.notifyPresentedCardWatchers(askId, buildApprovalCompleteFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, outcome, ...(lastCause !== undefined ? { cause: lastCause } : {}), parked: parkRouted, nowMs: Date.now() }));
1534
+ }
1443
1535
  releaseAdmission();
1444
1536
  if (selfEntry)
1445
1537
  selfEntry.settledOutcome = outcome;
@@ -1511,7 +1603,7 @@ export class ToolApprovalCoordinator {
1511
1603
  settleFromClaimLoss(read, armCause);
1512
1604
  };
1513
1605
  const boundedClaim = (mk, label, timeoutMs, late) => {
1514
- const release = this.admitTerminalClaim(askId ?? wireApprovalId);
1606
+ const release = this.admitTerminalClaim(wireApprovalId);
1515
1607
  if (release === undefined)
1516
1608
  return undefined;
1517
1609
  const raw = mk();
@@ -1583,7 +1675,6 @@ export class ToolApprovalCoordinator {
1583
1675
  arm.onWon(outcome);
1584
1676
  return;
1585
1677
  }
1586
- arm.onLostTruth?.(outcome.current);
1587
1678
  const read = this.classifyClaimLoss(pinnedAskId, outcome.current, `claim:${arm.intent}`);
1588
1679
  if (done) {
1589
1680
  broadcastClaimTruth(read, arm.cause);
@@ -1673,32 +1764,23 @@ export class ToolApprovalCoordinator {
1673
1764
  const store = this.askStore;
1674
1765
  const pinnedAskId = askId;
1675
1766
  const pinnedBatchId = batchId;
1676
- const cancelLostTruth = (current) => {
1677
- if (current.state !== "VOID")
1678
- return;
1679
- this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
1680
- };
1681
1767
  await resolveLocalOutcome({
1682
1768
  intent: "cancel",
1683
1769
  cause,
1684
1770
  storeLabel: "claimTerminal(cancel)",
1685
1771
  claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel", { cause }), "claimTerminal(cancel)", timeoutMs, (late) => {
1686
1772
  if (!late.claimed) {
1687
- cancelLostTruth(late.current);
1688
1773
  lateClaimLossSettle(late.current, "cancel", cause);
1689
1774
  return;
1690
1775
  }
1691
1776
  this.settleVoidedSiblings([pinnedAskId], cause);
1692
- this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
1693
1777
  this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
1694
1778
  }),
1695
1779
  onWon: () => {
1696
1780
  settle({ by: "system", cause });
1697
1781
  this.settleVoidedSiblings([pinnedAskId], cause);
1698
- this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
1699
1782
  this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
1700
1783
  },
1701
- onLostTruth: cancelLostTruth,
1702
1784
  settleByIntent: () => settle({ by: "system", cause }),
1703
1785
  });
1704
1786
  };
@@ -1750,6 +1832,9 @@ export class ToolApprovalCoordinator {
1750
1832
  const { rememberRefused: _stale, ...cardRest } = cardForFrame;
1751
1833
  cardForFrame = rememberRefused ? { ...cardRest, rememberRefused: true } : cardRest;
1752
1834
  }
1835
+ const cardFrame = askId !== undefined && cardForFrame !== undefined
1836
+ ? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now(), live: frame })
1837
+ : undefined;
1753
1838
  const pendingEntry = {
1754
1839
  settle,
1755
1840
  forceParkNow,
@@ -1760,6 +1845,9 @@ export class ToolApprovalCoordinator {
1760
1845
  ...(child ? { originTaskId: req.sourceTaskId } : primary ? { targetCtxs: new Set(aliveCtxs), onTargetGone } : {}),
1761
1846
  ...(origin.sessionId ? { sessionId: origin.sessionId } : {}),
1762
1847
  frame,
1848
+ ...(cardFrame !== undefined ? { cardFrame } : {}),
1849
+ approvalId: wireApprovalId,
1850
+ registrationId: id,
1763
1851
  toolName: req.toolName,
1764
1852
  grantBinding: entryGrantBinding,
1765
1853
  ...(entryClasses !== undefined ? { classes: entryClasses } : {}),
@@ -1777,6 +1865,7 @@ export class ToolApprovalCoordinator {
1777
1865
  selfEntry = pendingEntry;
1778
1866
  this.pending.set(id, pendingEntry);
1779
1867
  if (askId !== undefined) {
1868
+ this.releaseAdoptedRegistrations(askId);
1780
1869
  let siblings = this.pendingByAskId.get(askId);
1781
1870
  if (!siblings) {
1782
1871
  siblings = new Set();
@@ -1798,9 +1887,6 @@ export class ToolApprovalCoordinator {
1798
1887
  });
1799
1888
  if (this.shutdownDraining)
1800
1889
  forceParkNow(APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
1801
- const cardFrame = askId !== undefined && cardForFrame !== undefined
1802
- ? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now() })
1803
- : undefined;
1804
1890
  {
1805
1891
  const serverNowMs = Date.now();
1806
1892
  frame.expiresAtMs = persistedExpiresAtMs;
@@ -1808,28 +1894,37 @@ export class ToolApprovalCoordinator {
1808
1894
  frame.expiresInMs = Math.max(0, persistedExpiresAtMs - serverNowMs);
1809
1895
  }
1810
1896
  const emitOne = async (c) => {
1811
- let delivered = true;
1812
- try {
1813
- await c.emit(frame);
1897
+ let family;
1898
+ let send;
1899
+ if (c.emitCard !== undefined) {
1900
+ if (cardFrame === undefined) {
1901
+ defaultLogger.warn("approval card leg cannot present this ask: it has no confirmed durable identity (no approval_request frame is minted without one)", {
1902
+ taskId: origin.taskId,
1903
+ approvalId: wireApprovalId,
1904
+ toolName: req.toolName,
1905
+ });
1906
+ return { delivered: false };
1907
+ }
1908
+ family = "approval_request";
1909
+ send = () => c.emitCard(cardFrame, id);
1814
1910
  }
1815
- catch {
1816
- delivered = false;
1911
+ else {
1912
+ family = "tool_approval";
1913
+ send = () => c.emit(frame);
1817
1914
  }
1818
- let cardDelivered = false;
1819
- if (cardFrame && c.emitCard) {
1820
- try {
1821
- await c.emitCard(cardFrame);
1822
- cardDelivered = true;
1823
- }
1824
- catch {
1825
- }
1915
+ try {
1916
+ await send();
1917
+ return { delivered: true };
1918
+ }
1919
+ catch (err) {
1920
+ defaultLogger.warn("approval_card_delivery_failed", { taskId: origin.taskId, approvalId: wireApprovalId, family, errKind: errorKindOf(err) });
1921
+ return { delivered: false };
1826
1922
  }
1827
- return { legacy: delivered, card: cardDelivered };
1828
1923
  };
1829
1924
  const openByCtx = new Map(aliveCtxs.map((c) => [c, emitOne(c)]));
1830
1925
  const emitAllP = (async () => {
1831
1926
  const results = await Promise.all(openByCtx.values());
1832
- const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.legacy || r.card);
1927
+ const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.delivered);
1833
1928
  emitSettled = true;
1834
1929
  if (!anySucceeded && !done) {
1835
1930
  if (this.askStore && askId !== undefined && batchId !== undefined) {
@@ -1873,15 +1968,30 @@ export class ToolApprovalCoordinator {
1873
1968
  for (const c of aliveCtxs) {
1874
1969
  void openByCtx
1875
1970
  .get(c)
1876
- .then((delivered) => delivered.legacy
1877
- ? c.emit({
1971
+ .then(({ delivered }) => {
1972
+ if (!delivered)
1973
+ return undefined;
1974
+ if (c.emitCard !== undefined) {
1975
+ if (cardFrame === undefined)
1976
+ return undefined;
1977
+ return c.emitCard(buildApprovalCompleteFrame({
1978
+ askId: cardFrame.askId,
1979
+ taskId: cardFrame.taskId,
1980
+ approvalId: wireApprovalId,
1981
+ outcome: lastOutcome,
1982
+ ...(lastCause !== undefined ? { cause: lastCause } : {}),
1983
+ parked: parkRouted,
1984
+ nowMs: Date.now(),
1985
+ }), id);
1986
+ }
1987
+ return c.emit({
1878
1988
  type: "tool_approval_complete",
1879
1989
  approvalId: wireApprovalId,
1880
1990
  outcome: lastOutcome,
1881
1991
  ...(lastCause !== undefined ? { cause: lastCause } : {}),
1882
1992
  ...(parkRouted ? { parked: true } : {}),
1883
- })
1884
- : undefined)
1993
+ });
1994
+ })
1885
1995
  .catch(() => undefined);
1886
1996
  }
1887
1997
  if (emitFailed)
@@ -1892,24 +2002,47 @@ export class ToolApprovalCoordinator {
1892
2002
  return this.unattendedOutcome({ arm: "emit_unsettled", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
1893
2003
  return shapeOutcome(settled);
1894
2004
  }
1895
- respond(id, principal, body, httpReq) {
2005
+ respond(id, principal, body, httpReq, deployment) {
1896
2006
  const parsed = parseToolApprovalResponse(body);
1897
2007
  if (!parsed.ok)
1898
- return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape", ...(parsed.field !== undefined ? { field: parsed.field } : {}) } };
1899
- const entry = this.pending.get(id);
2008
+ return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: parsed.error, facts: { ...(parsed.field !== undefined ? { field: parsed.field } : {}) } }) };
2009
+ if (parsed.readRootGrant !== undefined && deployment?.readRootGrantAccepted !== true) {
2010
+ return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: READ_ROOT_GRANT_NOT_ACCEPTED }) };
2011
+ }
2012
+ const entry = this.liveEntryFor(id);
2013
+ if (entry?.adopted === true)
2014
+ return this.respondAdopted(entry, id, principal, parsed, httpReq);
1900
2015
  if (!entry || (entry.owner !== null && entry.owner !== principal)) {
1901
2016
  return this.respondLate(id, principal, parsed, httpReq);
1902
2017
  }
2018
+ return this.answerRegistration(entry, id, principal, parsed, httpReq);
2019
+ }
2020
+ async respondAdopted(entry, id, principal, parsed, httpReq) {
2021
+ const store = this.askStore;
2022
+ if (!store)
2023
+ return this.notPending();
2024
+ const row = await this.askRowFor(store, id, "getAsk(respond-adopted)");
2025
+ if (!askRowVisibleTo(row, principal))
2026
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2027
+ if (row.state !== "STREAM_PENDING")
2028
+ return this.dispatchRowTruth(row, id, parsed, httpReq, principal);
2029
+ return this.answerRegistration(entry, id, principal, parsed, httpReq);
2030
+ }
2031
+ answerRegistration(entry, id, principal, parsed, httpReq) {
1903
2032
  const preRule = this.precheckEditedRuleBeforeDecision(entry, parsed);
1904
2033
  if (preRule !== undefined)
1905
2034
  return preRule;
1906
2035
  if (parsed.value !== "deny" && policyCannotAnswer(heldAskOf(entry.requiresRealApproval === true), parsed.decidedBy)) {
1907
- return { status: 400, body: { error: POLICY_CANNOT_ALLOW_PERSON_ASK, errorCode: "request.body_shape" } };
2036
+ return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: POLICY_CANNOT_ALLOW_PERSON_ASK }) };
1908
2037
  }
1909
2038
  const ruleEligibility = { eligible: true };
1910
- const decided = this.askStore && entry.askId !== undefined && entry.batchId !== undefined
1911
- ? this.respondWithCas(this.askStore, entry.askId, entry.batchId, id, entry, parsed, ruleEligibility)
1912
- : this.finishRespond(id, entry, parsed);
2039
+ const store = this.askStore;
2040
+ if (store && entry.askId !== undefined && entry.batchId !== undefined) {
2041
+ return this.respondWithCas(store, entry.askId, entry.batchId, entry, parsed, ruleEligibility).then((cas) => cas.lost ? this.dispatchRowTruth(cas.row, id, parsed, httpReq, principal) : this.afterLiveDecision(cas.result, entry, parsed, principal, ruleEligibility));
2042
+ }
2043
+ return this.afterLiveDecision(this.finishRespond(entry, parsed), entry, parsed, principal, ruleEligibility);
2044
+ }
2045
+ afterLiveDecision(decided, entry, parsed, principal, ruleEligibility) {
1913
2046
  const settled = this.recordDeploymentListAnswer(decided, entry, parsed, principal, ruleEligibility);
1914
2047
  if (parsed.persistRule === undefined)
1915
2048
  return settled;
@@ -1956,11 +2089,13 @@ export class ToolApprovalCoordinator {
1956
2089
  return undefined;
1957
2090
  return {
1958
2091
  status: 400,
1959
- body: {
1960
- error: verdict.message,
2092
+ body: refusalBody({
1961
2093
  errorCode: "request.body_shape",
1962
- ...(verdict.code !== undefined ? { ruleRejectCode: verdict.code } : {}),
1963
- },
2094
+ message: verdict.message,
2095
+ facts: {
2096
+ ...(verdict.code !== undefined ? { ruleRejectCode: verdict.code } : {}),
2097
+ },
2098
+ }),
1964
2099
  };
1965
2100
  }
1966
2101
  editedArmEcho(persistRule, persisted) {
@@ -1971,45 +2106,156 @@ export class ToolApprovalCoordinator {
1971
2106
  notPending(cause) {
1972
2107
  return {
1973
2108
  status: 404,
1974
- body: {
1975
- error: "no pending tool approval for this id (settled, expired, or not on this replica)",
2109
+ body: refusalBody({
1976
2110
  errorCode: "tool_approval.not_pending",
1977
- ...(cause !== undefined ? { cause } : {}),
1978
- },
2111
+ message: "no pending tool approval for this id (settled, expired, or not on this replica)",
2112
+ facts: {
2113
+ ...(cause !== undefined ? { cause } : {}),
2114
+ },
2115
+ }),
1979
2116
  };
1980
2117
  }
1981
- respondLate(id, principal, parsed, httpReq) {
2118
+ readApproval(id, principal) {
2119
+ const entry = this.liveEntryFor(id);
2120
+ if (entry?.adopted === true)
2121
+ return this.readAdopted(entry, id, principal);
2122
+ if (entry !== undefined && (entry.owner === null || entry.owner === principal)) {
2123
+ const body = { state: "pending", ...livePendingRowOf(entry) };
2124
+ return { status: 200, body };
2125
+ }
1982
2126
  const store = this.askStore;
1983
2127
  if (!store)
1984
2128
  return this.notPending();
1985
- const tomb = this.lookupParkTombstone(id);
1986
- if (tomb === undefined || (tomb.owner !== null && tomb.owner !== principal)) {
2129
+ return this.readAskRowState(store, id, principal);
2130
+ }
2131
+ async readAdopted(entry, id, principal) {
2132
+ const store = this.askStore;
2133
+ if (!store)
2134
+ return this.notPending();
2135
+ const row = await this.askRowFor(store, id, "getAsk(read-adopted)");
2136
+ if (askRowVisibleTo(row, principal) && row.state === "STREAM_PENDING") {
2137
+ const body = { state: "pending", ...livePendingRowOf(entry) };
2138
+ return { status: 200, body };
2139
+ }
2140
+ return this.readStateOfRow(row, principal);
2141
+ }
2142
+ async readAskRowState(store, id, principal) {
2143
+ return this.readStateOfRow(await this.askRowFor(store, id, "getAsk(read)"), principal);
2144
+ }
2145
+ readStateOfRow(row, principal) {
2146
+ if (!askRowVisibleTo(row, principal))
1987
2147
  return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2148
+ const projection = buildAskRowReadProjection(row, Date.now());
2149
+ let body;
2150
+ switch (row.state) {
2151
+ case "DECIDED": {
2152
+ const decided = decidedRowFacts(row);
2153
+ if (decided === undefined)
2154
+ return this.notPending();
2155
+ body = { state: "decided", ...projection, ...decided };
2156
+ break;
2157
+ }
2158
+ case "PARKING":
2159
+ case "PARKED": {
2160
+ const parkSession = parkSessionOf(row);
2161
+ body = { state: "parked", ...projection, ...(parkSession !== undefined ? { decidePath: approvalDecidePath(parkSession) } : {}) };
2162
+ break;
2163
+ }
2164
+ case "DENIED":
2165
+ case "VOID": {
2166
+ const rowCause = causeOfAskRow(row);
2167
+ body = { state: "void", ...projection, ...(rowCause?.kind === "ending" ? { cause: rowCause.cause } : {}) };
2168
+ break;
2169
+ }
2170
+ case "STREAM_PENDING":
2171
+ return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2172
+ default: {
2173
+ const unhandled = row.state;
2174
+ defaultLogger.error("tool approval read hit an unknown ask state", { askId: row.askId, state: unhandled });
2175
+ return this.notPending();
2176
+ }
1988
2177
  }
1989
- return this.dispatchLateByRow(store, tomb, id, parsed, httpReq, principal);
2178
+ return { status: 200, body };
1990
2179
  }
1991
- async dispatchLateByRow(store, tomb, id, parsed, httpReq, principal) {
1992
- let row;
2180
+ respondLate(id, principal, parsed, httpReq) {
2181
+ const store = this.askStore;
2182
+ if (!store)
2183
+ return this.notPending();
2184
+ return this.dispatchLateByRow(store, id, parsed, httpReq, principal);
2185
+ }
2186
+ watchPresentedCards(askIds, deliver) {
2187
+ const watcher = { deliver, askIds: new Set() };
2188
+ for (const askId of askIds) {
2189
+ if (!this.pendingByAskId.has(askId))
2190
+ continue;
2191
+ watcher.askIds.add(askId);
2192
+ let set = this.presentedCardWatchers.get(askId);
2193
+ if (set === undefined) {
2194
+ set = new Set();
2195
+ this.presentedCardWatchers.set(askId, set);
2196
+ }
2197
+ set.add(watcher);
2198
+ }
2199
+ return () => {
2200
+ for (const askId of watcher.askIds)
2201
+ this.dropPresentedCardWatcher(askId, watcher);
2202
+ watcher.askIds.clear();
2203
+ };
2204
+ }
2205
+ dropPresentedCardWatcher(askId, watcher) {
2206
+ const set = this.presentedCardWatchers.get(askId);
2207
+ if (set === undefined)
2208
+ return;
2209
+ set.delete(watcher);
2210
+ if (set.size === 0)
2211
+ this.presentedCardWatchers.delete(askId);
2212
+ }
2213
+ notifyPresentedCardWatchers(askId, frame) {
2214
+ const set = this.presentedCardWatchers.get(askId);
2215
+ if (set === undefined)
2216
+ return;
2217
+ this.presentedCardWatchers.delete(askId);
2218
+ for (const watcher of set) {
2219
+ watcher.askIds.delete(askId);
2220
+ try {
2221
+ watcher.deliver(frame);
2222
+ }
2223
+ catch (err) {
2224
+ defaultLogger.warn("presented-card completion notice could not be written to its connection", { askId, error: err instanceof Error ? err.message : String(err) });
2225
+ }
2226
+ }
2227
+ }
2228
+ liveEntryFor(id) {
2229
+ const local = this.pending.get(id);
2230
+ if (local !== undefined)
2231
+ return local.askId === undefined ? local : undefined;
2232
+ for (const e of this.pendingByAskId.get(id) ?? [])
2233
+ return e;
2234
+ return undefined;
2235
+ }
2236
+ async dispatchLateByRow(store, id, parsed, httpReq, principal) {
2237
+ return this.dispatchRowTruth(await this.askRowFor(store, id, "getAsk(respond-late)"), id, parsed, httpReq, principal);
2238
+ }
2239
+ async askRowFor(store, id, label) {
1993
2240
  try {
1994
- row = await this.withStoreDeadline(store.getAsk(tomb.askId), "getAsk(respond-late)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
2241
+ return await this.withStoreDeadline(store.getAsk(id), label, DURABLE_CONVERGE_READ_TIMEOUT_MS);
1995
2242
  }
1996
2243
  catch (err) {
1997
- this.noteStoreError(err, "getAsk(respond-late)");
1998
- return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2244
+ this.noteStoreError(err, label);
2245
+ return null;
1999
2246
  }
2000
- if (row === null)
2001
- return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2002
- if (row.owner !== null && row.owner !== principal) {
2003
- defaultLogger.warn("tool approval late decision rejected: the ask row changed owner since it parked", { askId: row.askId });
2247
+ }
2248
+ async dispatchRowTruth(row, id, parsed, httpReq, principal) {
2249
+ if (!askRowVisibleTo(row, principal))
2004
2250
  return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2005
- }
2006
- if (tomb.redeemed !== undefined)
2007
- return this.replayRedeemReceipt(id, tomb, row, tomb.redeemed, parsed);
2251
+ const receipt = this.lookupParkRedemption(row.askId);
2252
+ if (receipt?.redeemed !== undefined)
2253
+ return this.replayRedeemReceipt(id, receipt, row, receipt.redeemed, parsed);
2008
2254
  switch (row.state) {
2009
2255
  case "DECIDED":
2010
2256
  return this.replayDecidedAsk(id, row, parsed);
2011
2257
  case "PARKED":
2012
- return this.redeemParkedAsk(id, tomb, row, parsed, httpReq, principal);
2258
+ return this.redeemParkedAsk(id, row, parsed, httpReq, principal);
2013
2259
  case "PARKING": {
2014
2260
  const parkSession = parkSessionOf(row);
2015
2261
  return {
@@ -2036,7 +2282,7 @@ export class ToolApprovalCoordinator {
2036
2282
  return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
2037
2283
  default: {
2038
2284
  const unhandled = row.state;
2039
- defaultLogger.error("tool approval respond hit an unknown ask state", { askId: tomb.askId, state: unhandled });
2285
+ defaultLogger.error("tool approval respond hit an unknown ask state", { askId: row.askId, state: unhandled });
2040
2286
  return this.notPending();
2041
2287
  }
2042
2288
  }
@@ -2047,7 +2293,8 @@ export class ToolApprovalCoordinator {
2047
2293
  return { rulePersisted: false, ruleRefusal: "rule_lane_unavailable" };
2048
2294
  }
2049
2295
  replayDecidedAsk(id, row, parsed) {
2050
- if ((row.decision !== "approve" && row.decision !== "deny") || row.provisional || row.decidedAtMs === null) {
2296
+ const decided = decidedRowFacts(row);
2297
+ if (decided === undefined) {
2051
2298
  defaultLogger.error("tool approval late replay saw a DECIDED row that is not a usable human decision", {
2052
2299
  askId: row.askId,
2053
2300
  decision: row.decision,
@@ -2063,15 +2310,14 @@ export class ToolApprovalCoordinator {
2063
2310
  approvalId: id,
2064
2311
  askId: row.askId,
2065
2312
  idempotent: true,
2066
- decision: row.decision === "approve" ? "allow" : "deny",
2067
- decidedAtMs: row.decidedAtMs,
2068
- ...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}),
2313
+ ...decided,
2069
2314
  ...(parsed.value === "allow_session" ? { rememberApplied: false } : {}),
2315
+ ...noteRecordedReceipt(parsed.note, noteLandedOnRow(parsed.note, row.decisionNote)),
2070
2316
  ...this.lateRuleRefusal(parsed),
2071
2317
  },
2072
2318
  };
2073
2319
  }
2074
- async redeemParkedAsk(id, tomb, row, parsed, httpReq, principal) {
2320
+ async redeemParkedAsk(id, row, parsed, httpReq, principal) {
2075
2321
  const redeem = this.parkedRedeem?.();
2076
2322
  if (redeem === undefined)
2077
2323
  return this.notPending();
@@ -2079,14 +2325,19 @@ export class ToolApprovalCoordinator {
2079
2325
  defaultLogger.error("tool approval parked row carries no gate token — nothing to redeem", { askId: row.askId });
2080
2326
  return this.notPending();
2081
2327
  }
2082
- if (tomb.redeemed !== undefined)
2083
- return this.replayRedeemReceipt(id, tomb, row, tomb.redeemed, parsed);
2084
- if (tomb.inflight !== undefined) {
2085
- const shared = await tomb.inflight;
2328
+ const prior = this.lookupParkRedemption(row.askId);
2329
+ if (prior?.redeemed !== undefined)
2330
+ return this.replayRedeemReceipt(id, prior, row, prior.redeemed, parsed);
2331
+ if (prior?.inflight !== undefined) {
2332
+ const shared = await prior.inflight;
2086
2333
  if (shared.status < 200 || shared.status >= 300)
2087
2334
  return shared;
2088
- return this.replayRedeemReceipt(id, tomb, row, { decision: shared.decision, body: shared.body, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed);
2335
+ return this.replayRedeemReceipt(id, prior, row, { decision: shared.decision, body: shared.body, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed);
2089
2336
  }
2337
+ const admission = this.parkRedemptionAdmission(row.owner);
2338
+ if (admission !== undefined)
2339
+ return admission;
2340
+ const memo = this.openParkRedemption(row.askId, row.owner);
2090
2341
  const decision = parsed.value === "deny" ? "deny" : "approve";
2091
2342
  const gateToken = row.gateToken;
2092
2343
  const flight = (async () => {
@@ -2107,25 +2358,29 @@ export class ToolApprovalCoordinator {
2107
2358
  }
2108
2359
  catch (err) {
2109
2360
  defaultLogger.error("tool approval parked redemption threw", { askId: row.askId, error: err instanceof Error ? err.message : String(err) });
2110
- return { status: 500, body: { error: "the parked approval could not be redeemed right now — retry", errorCode: "internal.error" }, decision };
2361
+ return { status: 500, body: refusalBody({ errorCode: "internal.error", message: "the parked approval could not be redeemed right now — retry" }), decision };
2111
2362
  }
2112
2363
  if (res.status < 200 || res.status >= 300)
2113
2364
  return { ...res, decision };
2114
2365
  const rule = await this.persistRuleFromRow(row, parsed);
2115
2366
  return { ...res, decision, ...(rule !== undefined ? { rule } : {}) };
2116
2367
  })();
2117
- tomb.inflight = flight;
2368
+ memo.inflight = flight;
2118
2369
  let out;
2119
2370
  try {
2120
2371
  out = await flight;
2121
2372
  }
2122
2373
  finally {
2123
- tomb.inflight = undefined;
2374
+ memo.inflight = undefined;
2124
2375
  }
2125
- if (out.status < 200 || out.status >= 300)
2376
+ if (out.status < 200 || out.status >= 300) {
2377
+ if (this.parkRedemptions.get(row.askId) === memo)
2378
+ this.parkRedemptions.delete(row.askId);
2126
2379
  return { status: out.status, body: out.body };
2380
+ }
2127
2381
  const rule = out.rule;
2128
- tomb.redeemed = { decision, body: out.body, ...(rule?.terminal === true ? { rule: rule.body } : {}) };
2382
+ if (this.retainParkRedemption(row.askId, memo))
2383
+ memo.redeemed = { decision, body: out.body, ...(rule?.terminal === true ? { rule: rule.body } : {}) };
2129
2384
  return {
2130
2385
  status: out.status,
2131
2386
  body: {
@@ -2216,24 +2471,24 @@ export class ToolApprovalCoordinator {
2216
2471
  return { ...flag(false, "rule_store_error"), terminal: false };
2217
2472
  }
2218
2473
  }
2219
- async replayRedeemReceipt(id, tomb, row, receipt, parsed) {
2220
- let rule = receipt.rule;
2221
- if (rule === undefined && parsed.persistRule !== undefined) {
2222
- const flight = tomb.ruleInflight ?? this.persistRuleFromRow(row, parsed);
2223
- const isOwner = tomb.ruleInflight === undefined;
2474
+ async replayRedeemReceipt(id, memo, row, receipt, parsed) {
2475
+ let rule = receipt.decision === "approve" ? receipt.rule : this.lateRuleRefusal(parsed);
2476
+ if (receipt.decision === "approve" && rule === undefined && parsed.persistRule !== undefined) {
2477
+ const flight = memo.ruleInflight ?? this.persistRuleFromRow(row, parsed);
2478
+ const isOwner = memo.ruleInflight === undefined;
2224
2479
  if (isOwner)
2225
- tomb.ruleInflight = flight;
2480
+ memo.ruleInflight = flight;
2226
2481
  let fresh;
2227
2482
  try {
2228
2483
  fresh = await flight;
2229
2484
  }
2230
2485
  finally {
2231
2486
  if (isOwner)
2232
- tomb.ruleInflight = undefined;
2487
+ memo.ruleInflight = undefined;
2233
2488
  }
2234
2489
  rule = fresh?.body;
2235
- if (fresh?.terminal === true && tomb.redeemed !== undefined)
2236
- tomb.redeemed.rule = fresh.body;
2490
+ if (fresh?.terminal === true && memo.redeemed !== undefined)
2491
+ memo.redeemed.rule = fresh.body;
2237
2492
  }
2238
2493
  return {
2239
2494
  status: 200,
@@ -2328,7 +2583,7 @@ export class ToolApprovalCoordinator {
2328
2583
  return withFlag(false, "rule_store_error");
2329
2584
  }
2330
2585
  }
2331
- async respondWithCas(askStore, askId, batchId, id, entry, parsed, ruleEligibility) {
2586
+ async respondWithCas(askStore, askId, batchId, entry, parsed, ruleEligibility) {
2332
2587
  let winnerRow;
2333
2588
  let ambiguousConfirmedRow;
2334
2589
  let noteLanded = false;
@@ -2347,7 +2602,7 @@ export class ToolApprovalCoordinator {
2347
2602
  noteLanded = true;
2348
2603
  }
2349
2604
  if (!decided.ok) {
2350
- return this.notPending();
2605
+ return { lost: true, row: decided.row };
2351
2606
  }
2352
2607
  }
2353
2608
  catch (err) {
@@ -2358,7 +2613,7 @@ export class ToolApprovalCoordinator {
2358
2613
  if (row?.state === "DECIDED" && row.decision === (parsed.value === "deny" ? "deny" : "approve")) {
2359
2614
  winnerRow = row;
2360
2615
  ambiguousConfirmedRow = row;
2361
- noteLanded = parsed.note === undefined || row.decisionNote === parsed.note;
2616
+ noteLanded = noteLandedOnRow(parsed.note, row.decisionNote);
2362
2617
  }
2363
2618
  }
2364
2619
  catch (confirmErr) {
@@ -2367,27 +2622,27 @@ export class ToolApprovalCoordinator {
2367
2622
  }
2368
2623
  const wantAllowed = parsed.value !== "deny";
2369
2624
  const decidedWon = winnerRow !== undefined;
2370
- if (this.pending.get(id) !== entry) {
2371
- if (winnerRow !== undefined)
2372
- this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow));
2373
- const converged = decidedWon && entry.settledOutcome === (wantAllowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED);
2625
+ if (entry.adopted === true && !decidedWon)
2626
+ return { lost: false, result: this.notPending() };
2627
+ if (this.pending.get(entry.registrationId) !== entry) {
2628
+ const othersReached = winnerRow !== undefined ? this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow)).settled : 0;
2629
+ const converged = decidedWon && (entry.adopted === true || entry.settledOutcome === (wantAllowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED));
2374
2630
  if (!converged) {
2375
- return this.notPending();
2631
+ return { lost: false, result: this.notPending() };
2376
2632
  }
2377
- return this.finishRespond(id, entry, parsed, { updatedInputDelivered: false, noteRecorded: noteLanded });
2633
+ return { lost: false, result: withConsumerDisclosure(this.finishRespond(entry, parsed, { updatedInputDelivered: false, noteRecorded: noteLanded }), waitingLegsReached(entry, othersReached)) };
2378
2634
  }
2379
2635
  const payloadIsMine = ambiguousConfirmedRow === undefined ||
2380
2636
  approvalPayloadFingerprint({ updatedInput: ambiguousConfirmedRow.updatedInput }) === approvalPayloadFingerprint({ updatedInput: parsed.updatedInput });
2381
- const result = this.finishRespond(id, entry, parsed, {
2637
+ const result = this.finishRespond(entry, parsed, {
2382
2638
  noteRecorded: noteLanded,
2383
2639
  ...(winnerRow !== undefined ? { settleOutcome: outcomeOfDecidedRow(winnerRow), settleOutcomeIsThisRequest: ambiguousConfirmedRow === undefined } : {}),
2384
- ...(ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
2640
+ ...(entry.adopted === true ? { updatedInputDelivered: false } : ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
2385
2641
  });
2386
- if (winnerRow !== undefined)
2387
- this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow));
2388
- return result;
2642
+ const othersReached = winnerRow !== undefined ? this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow)).settled : 0;
2643
+ return { lost: false, result: withConsumerDisclosure(result, waitingLegsReached(entry, othersReached)) };
2389
2644
  }
2390
- finishRespond(id, entry, parsed, opts) {
2645
+ finishRespond(entry, parsed, opts) {
2391
2646
  let rememberApplied;
2392
2647
  const home = standingAnswerHome(pendingEntryStandingAnswerFacts(entry));
2393
2648
  let grantable;
@@ -2430,42 +2685,146 @@ export class ToolApprovalCoordinator {
2430
2685
  return {
2431
2686
  status: 200,
2432
2687
  body: {
2433
- approvalId: id,
2688
+ approvalId: entry.approvalId,
2434
2689
  delivery: "applied",
2435
2690
  decision: parsed.value,
2436
2691
  ...(rememberApplied !== undefined ? { rememberApplied } : {}),
2437
2692
  ...(parsed.updatedInput !== undefined && allowed && updatedInputDelivered ? { updatedInputForwarded: true } : {}),
2438
- ...(parsed.note !== undefined ? { noteRecorded } : {}),
2693
+ ...noteRecordedReceipt(parsed.note, noteRecorded),
2439
2694
  },
2440
2695
  };
2441
2696
  }
2697
+ async adoptOpenAsks() {
2698
+ const store = this.askStore;
2699
+ if (!store)
2700
+ return { adopted: 0, alreadyLive: 0, cardUnreadable: 0, capped: false };
2701
+ let rows;
2702
+ try {
2703
+ rows = await this.withStoreDeadline(store.listByState("STREAM_PENDING", ADOPT_OPEN_ASKS_MAX + 1), "listByState(adopt)");
2704
+ }
2705
+ catch (err) {
2706
+ throw new Error(`approval ledger adoption: could not read the approval asks that are still pending in the ask ledger (${err instanceof Error ? err.message : String(err)}) — ` +
2707
+ "refusing to start, because serving now would hide asks that are still waiting for an answer from GET /v1/approvals. Check the database and start again.");
2708
+ }
2709
+ const capped = rows.length > ADOPT_OPEN_ASKS_MAX;
2710
+ const nowMs = Date.now();
2711
+ let adopted = 0;
2712
+ let alreadyLive = 0;
2713
+ let cardUnreadable = 0;
2714
+ for (const row of capped ? rows.slice(0, ADOPT_OPEN_ASKS_MAX) : rows) {
2715
+ if (this.pendingByAskId.has(row.askId)) {
2716
+ alreadyLive += 1;
2717
+ continue;
2718
+ }
2719
+ const replayFrame = buildReplayFrame(row, nowMs);
2720
+ if (replayFrame === undefined) {
2721
+ cardUnreadable += 1;
2722
+ continue;
2723
+ }
2724
+ this.registerAdopted(row, replayFrame);
2725
+ adopted += 1;
2726
+ }
2727
+ return { adopted, alreadyLive, cardUnreadable, capped };
2728
+ }
2729
+ registerAdopted(row, replayFrame) {
2730
+ const card = replayFrame.card;
2731
+ const registrationId = uuidv7();
2732
+ const facts = {
2733
+ ...(card.governanceForced === true ? { governanceForced: true } : {}),
2734
+ ...(card.risk.requiresRealApproval ? { requiresRealApproval: true } : {}),
2735
+ grantBinding: { kind: "unreadable" },
2736
+ ...(card.classes !== undefined ? { classes: card.classes } : {}),
2737
+ };
2738
+ const rememberRefused = standingAnswerHome(pendingEntryStandingAnswerFacts(facts)) === "none";
2739
+ const cardFrame = (card.rememberRefused === true) === rememberRefused
2740
+ ? replayFrame
2741
+ : { ...replayFrame, card: rememberRefused ? { ...card, rememberRefused: true } : (({ rememberRefused: _stale, ...rest }) => rest)(card) };
2742
+ let done = false;
2743
+ const leave = (ended) => {
2744
+ if (done)
2745
+ return;
2746
+ done = true;
2747
+ this.pending.delete(registrationId);
2748
+ entry.settledOutcome = ended?.outcome ?? SETTLE_OUTCOME.EXPIRED;
2749
+ const siblings = this.pendingByAskId.get(row.askId);
2750
+ siblings?.delete(entry);
2751
+ const lastRegistration = siblings !== undefined && siblings.size === 0;
2752
+ if (lastRegistration)
2753
+ this.pendingByAskId.delete(row.askId);
2754
+ if (ended !== undefined && lastRegistration) {
2755
+ this.notifyPresentedCardWatchers(row.askId, buildApprovalCompleteFrame({ askId: row.askId, taskId: row.taskId, approvalId: row.askId, outcome: ended.outcome, ...(ended.cause !== undefined ? { cause: ended.cause } : {}), parked: ended.parked, nowMs: Date.now() }));
2756
+ }
2757
+ };
2758
+ const release = () => leave(undefined);
2759
+ const entry = {
2760
+ adopted: true,
2761
+ cardFrame,
2762
+ release,
2763
+ settle: (verdict) => leave(verdict.by === "system" ? { outcome: SETTLE_OUTCOME.EXPIRED, cause: verdict.cause, parked: false } : { outcome: verdict.allowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED, parked: false }),
2764
+ forceParkNow: release,
2765
+ settleParkRoute: (cause) => leave({ outcome: SETTLE_OUTCOME.EXPIRED, cause, parked: this.unattendedRoutesToPark }),
2766
+ owner: row.owner,
2767
+ ...(row.sessionId !== row.taskId ? { sessionId: row.sessionId } : {}),
2768
+ ...(card.fromSubagent === true ? { originTaskId: row.sourceTaskId } : {}),
2769
+ toolName: card.toolName,
2770
+ ...facts,
2771
+ approvalId: row.askId,
2772
+ registrationId,
2773
+ registeredAtMs: row.createdAtMs,
2774
+ expiresAtMs: row.expiresAtMs,
2775
+ askId: row.askId,
2776
+ batchId: row.batchId,
2777
+ };
2778
+ this.pending.set(registrationId, entry);
2779
+ const siblings = this.pendingByAskId.get(row.askId) ?? new Set();
2780
+ siblings.add(entry);
2781
+ this.pendingByAskId.set(row.askId, siblings);
2782
+ }
2783
+ releaseAdoptedRegistrations(askId) {
2784
+ for (const e of [...(this.pendingByAskId.get(askId) ?? [])])
2785
+ if (e.adopted === true)
2786
+ e.release();
2787
+ }
2788
+ forgetAdoptedAsk(askId) {
2789
+ this.releaseAdoptedRegistrations(askId);
2790
+ if (this.pendingByAskId.has(askId))
2791
+ return;
2792
+ const watchers = this.presentedCardWatchers.get(askId);
2793
+ if (watchers === undefined)
2794
+ return;
2795
+ this.presentedCardWatchers.delete(askId);
2796
+ for (const watcher of watchers)
2797
+ watcher.askIds.delete(askId);
2798
+ }
2799
+ followAdoptedOwner(askId, owner) {
2800
+ for (const e of this.pendingByAskId.get(askId) ?? [])
2801
+ if (e.adopted === true)
2802
+ e.owner = owner;
2803
+ }
2804
+ waitingRegistrationCount(askId) {
2805
+ let n = 0;
2806
+ for (const e of this.pendingByAskId.get(askId) ?? [])
2807
+ if (e.adopted !== true)
2808
+ n += 1;
2809
+ return n;
2810
+ }
2442
2811
  listLivePending(scope) {
2443
2812
  const rows = [];
2444
- for (const [id, e] of this.pending) {
2445
- if (scope !== undefined && e.owner !== scope)
2813
+ const owner = scope === undefined ? undefined : ownerOfCheckpointScope(scope);
2814
+ const seen = new Set();
2815
+ for (const e of this.pending.values()) {
2816
+ if (owner !== undefined && e.owner !== owner)
2446
2817
  continue;
2447
- rows.push({
2448
- approvalId: id,
2449
- toolName: e.toolName,
2450
- ts: e.registeredAtMs,
2451
- expiresAtMs: e.expiresAtMs,
2452
- ...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
2453
- ...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
2454
- ...(e.governanceForced ? { governanceForced: true } : {}),
2455
- ...(e.mandated ? { mandated: true } : {}),
2456
- ...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
2457
- ...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
2458
- ...(e.origin !== undefined ? { origin: e.origin } : {}),
2459
- ...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
2460
- ...(e.frame.approvalCard !== undefined ? { approvalCard: e.frame.approvalCard } : {}),
2461
- frame: e.frame.approvalId === id ? e.frame : { ...e.frame, approvalId: id },
2462
- });
2818
+ if (seen.has(e.approvalId))
2819
+ continue;
2820
+ seen.add(e.approvalId);
2821
+ rows.push(livePendingRowOf(e));
2463
2822
  }
2464
2823
  return rows;
2465
2824
  }
2466
2825
  hasLivePendingFrom(originTaskId) {
2467
2826
  for (const e of this.pending.values())
2468
- if (e.originTaskId === originTaskId)
2827
+ if (e.originTaskId === originTaskId && e.adopted !== true)
2469
2828
  return true;
2470
2829
  return false;
2471
2830
  }
@@ -2473,27 +2832,42 @@ export class ToolApprovalCoordinator {
2473
2832
  const store = this.askStore;
2474
2833
  if (!store)
2475
2834
  return { polled: 0, settled: 0, backlog: 0, truncated: false };
2476
- const candidates = [];
2835
+ const waiting = [];
2836
+ const adopted = [];
2477
2837
  const seen = new Set();
2478
2838
  for (const entry of this.pending.values()) {
2479
- if (entry.suspended === true && entry.askId !== undefined && entry.settledOutcome === undefined && !seen.has(entry.askId)) {
2480
- seen.add(entry.askId);
2481
- candidates.push(entry.askId);
2482
- }
2483
- }
2484
- const start = candidates.length > 0 ? this.suspendedPollOffset % candidates.length : 0;
2485
- const ordered = [...candidates.slice(start), ...candidates.slice(0, start)];
2839
+ if (entry.askId === undefined || entry.settledOutcome !== undefined || seen.has(entry.askId))
2840
+ continue;
2841
+ if (entry.suspended === true)
2842
+ waiting.push(entry.askId);
2843
+ else if (entry.adopted === true)
2844
+ adopted.push(entry.askId);
2845
+ else
2846
+ continue;
2847
+ seen.add(entry.askId);
2848
+ }
2849
+ const waitingStart = waiting.length > 0 ? this.suspendedPollOffset % waiting.length : 0;
2850
+ const adoptedStart = adopted.length > 0 ? this.adoptedPollOffset % adopted.length : 0;
2851
+ const ordered = [
2852
+ ...[...waiting.slice(waitingStart), ...waiting.slice(0, waitingStart)].map((askId) => ({ askId, tier: "waiting" })),
2853
+ ...[...adopted.slice(adoptedStart), ...adopted.slice(0, adoptedStart)].map((askId) => ({ askId, tier: "adopted" })),
2854
+ ];
2486
2855
  const deadlineMs = Date.now() + SUSPENDED_POLL_BUDGET_MS;
2487
2856
  let settled = 0;
2488
2857
  let polled = 0;
2858
+ let polledWaiting = 0;
2859
+ let polledAdopted = 0;
2489
2860
  let truncated = false;
2490
- for (const askId of ordered) {
2861
+ for (const { askId, tier } of ordered) {
2491
2862
  if (polled >= SUSPENDED_POLL_MAX_PER_TICK || Date.now() >= deadlineMs) {
2492
2863
  truncated = true;
2493
2864
  break;
2494
2865
  }
2495
2866
  polled++;
2496
- this.suspendedPollOffset = start + polled;
2867
+ if (tier === "waiting")
2868
+ this.suspendedPollOffset = waitingStart + ++polledWaiting;
2869
+ else
2870
+ this.adoptedPollOffset = adoptedStart + ++polledAdopted;
2497
2871
  let row;
2498
2872
  try {
2499
2873
  row = await this.withStoreDeadline(store.getAsk(askId), "getAsk(poll-suspended)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
@@ -2502,20 +2876,26 @@ export class ToolApprovalCoordinator {
2502
2876
  this.noteStoreError(err, "getAsk(poll-suspended)");
2503
2877
  continue;
2504
2878
  }
2505
- if (row === null || row.state === "STREAM_PENDING")
2879
+ if (row === null) {
2880
+ this.forgetAdoptedAsk(askId);
2881
+ continue;
2882
+ }
2883
+ if (row.state === "STREAM_PENDING") {
2884
+ this.followAdoptedOwner(askId, row.owner);
2506
2885
  continue;
2886
+ }
2507
2887
  if (this.editedDecisionInFlight(askId))
2508
2888
  continue;
2509
2889
  const rowCause = causeOfAskRow(row);
2510
2890
  if (rowCause?.kind === "ending") {
2511
- const before = this.pendingByAskId.get(askId)?.size ?? 0;
2891
+ const before = this.waitingRegistrationCount(askId);
2512
2892
  this.settleVoidedSiblings([askId], rowCause.cause);
2513
2893
  settled += before;
2514
2894
  continue;
2515
2895
  }
2516
2896
  const outcome = replayTerminalAskRow(row);
2517
2897
  if (outcome === "unavailable") {
2518
- const before = this.pendingByAskId.get(askId)?.size ?? 0;
2898
+ const before = this.waitingRegistrationCount(askId);
2519
2899
  const parkCause = (rowCause?.kind === "park_trigger" ? rowCause.cause : undefined) ?? (Date.now() >= row.expiresAtMs ? APPROVAL_ENDING_CAUSES.TTL_EXPIRED : APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
2520
2900
  this.settleSameAskIdParkRoute(askId, parkCause);
2521
2901
  settled += before;
@@ -2524,7 +2904,7 @@ export class ToolApprovalCoordinator {
2524
2904
  const delivered = this.notifyExternalDecision(askId, outcome).settled;
2525
2905
  settled += delivered;
2526
2906
  }
2527
- const backlog = Math.max(0, candidates.length - polled);
2907
+ const backlog = Math.max(0, waiting.length + adopted.length - polled);
2528
2908
  if (truncated) {
2529
2909
  defaultLogger.warn("approval_suspended_poll_truncated", {
2530
2910
  polled,
@@ -2533,6 +2913,7 @@ export class ToolApprovalCoordinator {
2533
2913
  maxPerTick: SUSPENDED_POLL_MAX_PER_TICK,
2534
2914
  budgetMs: SUSPENDED_POLL_BUDGET_MS,
2535
2915
  resumeFromOffset: this.suspendedPollOffset,
2916
+ resumeAdoptedFromOffset: this.adoptedPollOffset,
2536
2917
  });
2537
2918
  }
2538
2919
  return { polled, settled, backlog, truncated };
@@ -2582,8 +2963,11 @@ export class ToolApprovalCoordinator {
2582
2963
  sessionAllowedCount() {
2583
2964
  return this.allowAllSessions.size;
2584
2965
  }
2585
- parkTombstoneCount() {
2586
- return this.parkTombstones.size;
2966
+ presentedCardWatcherCount() {
2967
+ return this.presentedCardWatchers.size;
2968
+ }
2969
+ parkRedemptionCount() {
2970
+ return this.parkRedemptions.size;
2587
2971
  }
2588
2972
  }
2589
2973
  //# sourceMappingURL=tool-approval.js.map