@sema-agent/server 7.132.0 → 7.134.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 (473) hide show
  1. package/MIGRATION.md +353 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +301 -64
  5. package/deploy/sema-up/README.md +35 -1
  6. package/deploy/sema-up/chart/Chart.yaml +1 -1
  7. package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
  9. package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
  10. package/deploy/sema-up/chart/templates/server-v4pro.yaml +19 -2
  11. package/deploy/sema-up/chart/templates/server.yaml +19 -2
  12. package/deploy/sema-up/chart/values-full-single.yaml +5 -1
  13. package/deploy/sema-up/chart/values.yaml +34 -0
  14. package/deploy/sema-up/stack/compose.yaml +3 -0
  15. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  16. package/dist/adoption/plan.d.ts +3 -2
  17. package/dist/adoption/plan.js +7 -5
  18. package/dist/adoption/runner.js +3 -10
  19. package/dist/adoption/sql.d.ts +3 -2
  20. package/dist/adoption/sql.js +3 -5
  21. package/dist/adoption/wire.d.ts +4 -1
  22. package/dist/adoption/wire.js +1 -0
  23. package/dist/approval-ask-audit-store.d.ts +32 -14
  24. package/dist/approval-ask-audit-store.js +272 -74
  25. package/dist/approval-ask-machine.d.ts +15 -0
  26. package/dist/approval-ask-machine.js +6 -0
  27. package/dist/approval-ask-retention.d.ts +136 -0
  28. package/dist/approval-ask-retention.js +102 -0
  29. package/dist/approval-card.d.ts +83 -23
  30. package/dist/approval-card.js +45 -15
  31. package/dist/approval-content-kind.d.ts +5 -2
  32. package/dist/approval-content-kind.js +16 -3
  33. package/dist/approval-deny-reasons.d.ts +1 -1
  34. package/dist/approval-gate-currency.d.ts +72 -0
  35. package/dist/approval-gate-currency.js +94 -0
  36. package/dist/approval.d.ts +15 -0
  37. package/dist/approval.js +55 -17
  38. package/dist/audit.d.ts +27 -10
  39. package/dist/audit.js +12 -16
  40. package/dist/bake-runner/main.js +3 -2
  41. package/dist/boot/coordinators.d.ts +6 -1
  42. package/dist/boot/coordinators.js +6 -3
  43. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  44. package/dist/boot/execution-env.js +9 -7
  45. package/dist/boot/execution-lane.js +11 -0
  46. package/dist/boot/leader.d.ts +2 -0
  47. package/dist/boot/leader.js +4 -1
  48. package/dist/boot/memory-boundary.js +2 -2
  49. package/dist/boot/memory-consolidation.d.ts +5 -4
  50. package/dist/boot/memory-consolidation.js +7 -6
  51. package/dist/boot/org-delivery.d.ts +28 -0
  52. package/dist/boot/org-delivery.js +20 -0
  53. package/dist/boot/read-deny-anchor-audit.js +2 -2
  54. package/dist/boot/reapers.d.ts +3 -0
  55. package/dist/boot/reapers.js +21 -1
  56. package/dist/boot/resolve-spec.d.ts +3 -0
  57. package/dist/boot/resolve-spec.js +60 -81
  58. package/dist/boot/runner-deps.d.ts +18 -5
  59. package/dist/boot/runner-deps.js +10 -0
  60. package/dist/boot/session-faces.d.ts +1 -0
  61. package/dist/boot/session-faces.js +4 -2
  62. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  63. package/dist/boot/stage-06-runners.d.ts +6 -3
  64. package/dist/boot/stage-06-runners.js +20 -4
  65. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  66. package/dist/boot/stage-07-capability-layer.js +24 -3
  67. package/dist/boot/stage-08-reapers.d.ts +4 -1
  68. package/dist/boot/stage-08-reapers.js +2 -2
  69. package/dist/boot/stage-09-leader.d.ts +5 -1
  70. package/dist/boot/stage-09-leader.js +2 -2
  71. package/dist/boot/stage-10-http-server.d.ts +5 -1
  72. package/dist/boot/stage-10-http-server.js +6 -6
  73. package/dist/boot/stores.d.ts +8 -0
  74. package/dist/boot/stores.js +10 -14
  75. package/dist/boot/web-admin.d.ts +10 -0
  76. package/dist/boot/web-admin.js +162 -65
  77. package/dist/boot/workflow-orchestration.d.ts +2 -0
  78. package/dist/boot/workflow-orchestration.js +2 -1
  79. package/dist/brain.js +8 -3
  80. package/dist/breaker-key.d.ts +6 -0
  81. package/dist/breaker-key.js +29 -0
  82. package/dist/budget.js +5 -1
  83. package/dist/caller-text-echo.d.ts +11 -6
  84. package/dist/caller-text-echo.js +10 -12
  85. package/dist/canonical-json.d.ts +2 -41
  86. package/dist/canonical-json.js +2 -13
  87. package/dist/capabilities/center-plugins.d.ts +7 -0
  88. package/dist/capabilities/center-plugins.js +1 -1
  89. package/dist/capabilities/composite-members.d.ts +1 -1
  90. package/dist/capabilities/composite-members.js +1 -1
  91. package/dist/capabilities/execution-lane.js +2 -2
  92. package/dist/capabilities/hands-lane.d.ts +21 -0
  93. package/dist/capabilities/hands-lane.js +10 -0
  94. package/dist/capabilities/mcp-probe.d.ts +36 -4
  95. package/dist/capabilities/mcp-probe.js +29 -6
  96. package/dist/capabilities/scenarios.d.ts +63 -7
  97. package/dist/capabilities/scenarios.js +39 -7
  98. package/dist/capability-facts.d.ts +36 -4
  99. package/dist/capability-facts.js +30 -4
  100. package/dist/center-config-tier.d.ts +69 -0
  101. package/dist/center-config-tier.js +42 -0
  102. package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
  103. package/dist/checkpoint-ctx-seal-m28.js +19 -0
  104. package/dist/checkpoint-identity.d.ts +16 -0
  105. package/dist/checkpoint-identity.js +19 -0
  106. package/dist/config-catalog.d.ts +17 -10
  107. package/dist/config-catalog.js +43 -28
  108. package/dist/config-d-migration.d.ts +3 -1
  109. package/dist/config-d-migration.js +2 -2
  110. package/dist/config-dir-trust.d.ts +114 -0
  111. package/dist/config-dir-trust.js +388 -0
  112. package/dist/config-invariants.d.ts +18 -5
  113. package/dist/config-invariants.js +35 -3
  114. package/dist/config-provider.d.ts +6 -1
  115. package/dist/config-provider.js +6 -2
  116. package/dist/config-types.d.ts +71 -14
  117. package/dist/config-types.js +8 -0
  118. package/dist/config.d.ts +13 -0
  119. package/dist/config.js +121 -59
  120. package/dist/delegated-session-owner.d.ts +185 -0
  121. package/dist/delegated-session-owner.js +119 -0
  122. package/dist/device-protocol.d.ts +5 -3
  123. package/dist/device-protocol.js +1 -1
  124. package/dist/device-ws-hub.js +21 -2
  125. package/dist/device-ws-protocol.d.ts +70 -6
  126. package/dist/device-ws-protocol.js +50 -23
  127. package/dist/elicitation.js +3 -2
  128. package/dist/entry-problems.d.ts +34 -0
  129. package/dist/entry-problems.js +14 -0
  130. package/dist/execution-lane-caps.d.ts +49 -10
  131. package/dist/execution-lane-caps.js +43 -4
  132. package/dist/field-conflict.d.ts +44 -0
  133. package/dist/field-conflict.js +21 -0
  134. package/dist/file-history-disclosure.d.ts +34 -5
  135. package/dist/file-history-disclosure.js +2 -2
  136. package/dist/fleet/fleet-bus.d.ts +84 -6
  137. package/dist/fleet/fleet-bus.js +42 -9
  138. package/dist/hooks/classic-stations.d.ts +10 -5
  139. package/dist/hooks/classic-stations.js +4 -4
  140. package/dist/hooks/command-spawn.d.ts +57 -0
  141. package/dist/hooks/command-spawn.js +139 -0
  142. package/dist/hooks/hook-llm.js +2 -2
  143. package/dist/hooks/hook-runner.d.ts +127 -25
  144. package/dist/hooks/hook-runner.js +668 -287
  145. package/dist/hooks/hook-source.d.ts +44 -0
  146. package/dist/hooks/hook-source.js +40 -0
  147. package/dist/http/active-run-conflict.d.ts +5 -13
  148. package/dist/http/active-run-conflict.js +17 -27
  149. package/dist/http/admission.js +111 -60
  150. package/dist/http/approval-sweeps.js +58 -19
  151. package/dist/http/caller-trust.d.ts +77 -0
  152. package/dist/http/caller-trust.js +143 -0
  153. package/dist/http/checkpoint-ctx.d.ts +173 -18
  154. package/dist/http/checkpoint-ctx.js +171 -12
  155. package/dist/http/claimed-run.d.ts +4 -3
  156. package/dist/http/device-placement.d.ts +2 -4
  157. package/dist/http/device-placement.js +2 -1
  158. package/dist/http/dispatch.js +13 -7
  159. package/dist/http/durable-run-leg.d.ts +4 -32
  160. package/dist/http/durable-run-leg.js +37 -12
  161. package/dist/http/idempotency.d.ts +2 -2
  162. package/dist/http/idempotency.js +2 -2
  163. package/dist/http/owner-access.d.ts +56 -0
  164. package/dist/http/owner-access.js +42 -0
  165. package/dist/http/principal-gate.d.ts +78 -27
  166. package/dist/http/principal-gate.js +64 -14
  167. package/dist/http/readiness.d.ts +11 -5
  168. package/dist/http/readiness.js +10 -3
  169. package/dist/http/refusal-body.d.ts +12 -0
  170. package/dist/http/refusal-body.js +11 -0
  171. package/dist/http/request-guards.d.ts +16 -32
  172. package/dist/http/request-guards.js +31 -30
  173. package/dist/http/resume-ctx-read.d.ts +50 -6
  174. package/dist/http/resume-ctx-read.js +33 -7
  175. package/dist/http/resume-legs.d.ts +5 -5
  176. package/dist/http/resume-legs.js +163 -91
  177. package/dist/http/retired-service-deps.d.ts +43 -0
  178. package/dist/http/retired-service-deps.js +18 -0
  179. package/dist/http/route-ctx.d.ts +44 -37
  180. package/dist/http/route-ctx.js +4 -0
  181. package/dist/http/route-table.js +2 -0
  182. package/dist/http/routes/a2a-serve.js +3 -4
  183. package/dist/http/routes/admin-config-refresh.js +1 -1
  184. package/dist/http/routes/admin-drain.js +1 -1
  185. package/dist/http/routes/admin-memory-optout.js +5 -1
  186. package/dist/http/routes/adoption.js +2 -9
  187. package/dist/http/routes/agents-roster.js +2 -3
  188. package/dist/http/routes/approvals-assistant.js +91 -56
  189. package/dist/http/routes/background.js +3 -2
  190. package/dist/http/routes/capabilities.js +20 -4
  191. package/dist/http/routes/config-catalog.d.ts +3 -3
  192. package/dist/http/routes/config-catalog.js +1 -1
  193. package/dist/http/routes/devices.js +3 -17
  194. package/dist/http/routes/diagnostics.d.ts +3 -3
  195. package/dist/http/routes/diagnostics.js +6 -2
  196. package/dist/http/routes/fleet.js +4 -4
  197. package/dist/http/routes/images.js +6 -7
  198. package/dist/http/routes/leader.js +1 -8
  199. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  200. package/dist/http/routes/mcp-reconnect.js +3 -2
  201. package/dist/http/routes/memory-bundle.js +1 -1
  202. package/dist/http/routes/memory-compliance.js +1 -1
  203. package/dist/http/routes/memory-consolidation.js +1 -1
  204. package/dist/http/routes/memory-origin.d.ts +1 -1
  205. package/dist/http/routes/memory-origin.js +1 -1
  206. package/dist/http/routes/memory-policy.d.ts +1 -1
  207. package/dist/http/routes/memory-policy.js +3 -2
  208. package/dist/http/routes/notify-wake.js +8 -21
  209. package/dist/http/routes/observability.js +2 -2
  210. package/dist/http/routes/principal-inbox.d.ts +39 -0
  211. package/dist/http/routes/principal-inbox.js +76 -0
  212. package/dist/http/routes/retention-ops.js +2 -9
  213. package/dist/http/routes/rules.js +9 -22
  214. package/dist/http/routes/runs.d.ts +5 -0
  215. package/dist/http/routes/runs.js +63 -115
  216. package/dist/http/routes/session-sync.d.ts +3 -2
  217. package/dist/http/routes/session-sync.js +53 -48
  218. package/dist/http/routes/sessions-list.d.ts +1 -1
  219. package/dist/http/routes/sessions-list.js +3 -4
  220. package/dist/http/routes/sessions.js +33 -43
  221. package/dist/http/routes/side-query.js +6 -4
  222. package/dist/http/routes/tasks.js +71 -30
  223. package/dist/http/routes/trace-usage.js +7 -9
  224. package/dist/http/routes/workflows.d.ts +351 -0
  225. package/dist/http/routes/workflows.js +153 -119
  226. package/dist/http/run-declared-families.d.ts +16 -0
  227. package/dist/http/run-declared-families.js +12 -0
  228. package/dist/http/send.d.ts +24 -3
  229. package/dist/http/send.js +14 -2
  230. package/dist/http/server.d.ts +23 -18
  231. package/dist/http/server.js +20 -15
  232. package/dist/http/stored-body-replay.js +17 -4
  233. package/dist/http/submit-ctx.d.ts +4 -3
  234. package/dist/http/submit-ctx.js +23 -6
  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 +17 -10
  238. package/dist/index.d.ts +3 -1
  239. package/dist/index.js +1 -1
  240. package/dist/lane-home-probe.d.ts +78 -9
  241. package/dist/lane-home-probe.js +78 -12
  242. package/dist/leader/endpoint.d.ts +4 -1
  243. package/dist/leader/endpoint.js +11 -10
  244. package/dist/leader/wire.d.ts +4 -0
  245. package/dist/leader/wire.js +8 -7
  246. package/dist/limit-refusal.d.ts +17 -5
  247. package/dist/limit-refusal.js +4 -1
  248. package/dist/main.js +1 -1
  249. package/dist/mcp-assertion-header.d.ts +31 -10
  250. package/dist/mcp-assertion-header.js +10 -3
  251. package/dist/memory-origin-fence.js +2 -2
  252. package/dist/memory-project-layer.d.ts +37 -0
  253. package/dist/memory-project-layer.js +46 -0
  254. package/dist/memory-scope.d.ts +23 -16
  255. package/dist/memory-scope.js +24 -8
  256. package/dist/memory-sync-client.d.ts +27 -13
  257. package/dist/memory-sync-client.js +107 -18
  258. package/dist/model-compat.d.ts +1 -1
  259. package/dist/model-entry-refusal.d.ts +9 -1
  260. package/dist/model-entry-refusal.js +6 -1
  261. package/dist/model-plane-face.d.ts +51 -0
  262. package/dist/model-plane-face.js +24 -0
  263. package/dist/model-provider.d.ts +5 -0
  264. package/dist/model-provider.js +4 -2
  265. package/dist/model-route-endpoint.d.ts +8 -11
  266. package/dist/model-route-endpoint.js +4 -7
  267. package/dist/model-route-env.d.ts +22 -0
  268. package/dist/model-route-env.js +2 -0
  269. package/dist/model-route-gap.d.ts +60 -30
  270. package/dist/model-route-gap.js +21 -15
  271. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  272. package/dist/observability/corrupt-read-seat.js +9 -0
  273. package/dist/observability/err-text.d.ts +6 -0
  274. package/dist/observability/err-text.js +18 -4
  275. package/dist/observability/fail-open.d.ts +35 -9
  276. package/dist/observability/fail-open.js +35 -9
  277. package/dist/observability/metrics.js +1 -0
  278. package/dist/observability/prompt-manifest.d.ts +22 -25
  279. package/dist/observability/prompt-manifest.js +54 -50
  280. package/dist/observability/tool-trace.js +9 -10
  281. package/dist/operator-ask.d.ts +10 -0
  282. package/dist/operator-ask.js +3 -0
  283. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  284. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  285. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  286. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  287. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  288. package/dist/orchestration/workflow-notify-journal.js +27 -34
  289. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  290. package/dist/orchestration/workflow-park-decision.js +119 -0
  291. package/dist/org-delivery-gate.d.ts +81 -0
  292. package/dist/org-delivery-gate.js +59 -0
  293. package/dist/panel-tool-names.d.ts +23 -0
  294. package/dist/panel-tool-names.js +8 -0
  295. package/dist/parked-decide.d.ts +43 -11
  296. package/dist/parked-decide.js +100 -51
  297. package/dist/parked-revive-surface.d.ts +9 -2
  298. package/dist/parked-revive-surface.js +11 -8
  299. package/dist/permission-rule-list.d.ts +1 -1
  300. package/dist/permission-rule-list.js +2 -2
  301. package/dist/permission-rule-vocab.d.ts +1 -23
  302. package/dist/permission-rule-vocab.js +0 -13
  303. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  304. package/dist/plugins/approval-ask-store-file.js +21 -0
  305. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  306. package/dist/plugins/approval-ask-store-memory.js +34 -2
  307. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  308. package/dist/plugins/approval-ask-store-sql.js +37 -1
  309. package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
  310. package/dist/plugins/approval-payload-fingerprint.js +1 -1
  311. package/dist/plugins/blob-backend.d.ts +38 -25
  312. package/dist/plugins/blob-backend.js +29 -19
  313. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  314. package/dist/plugins/breaker-state-sql.js +4 -4
  315. package/dist/plugins/caching-session-store.d.ts +4 -1
  316. package/dist/plugins/caching-session-store.js +7 -3
  317. package/dist/plugins/checkpoint-store-sql.d.ts +36 -1
  318. package/dist/plugins/checkpoint-store-sql.js +65 -3
  319. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  320. package/dist/plugins/file-history-store-sql.js +3 -1
  321. package/dist/plugins/file-run-store.d.ts +5 -13
  322. package/dist/plugins/file-run-store.js +9 -0
  323. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  324. package/dist/plugins/fork-routing-session-store.js +12 -1
  325. package/dist/plugins/host-platform.d.ts +2 -0
  326. package/dist/plugins/host-platform.js +2 -1
  327. package/dist/plugins/local-checkpoint-store.d.ts +15 -2
  328. package/dist/plugins/local-checkpoint-store.js +32 -1
  329. package/dist/plugins/local-session-store.d.ts +13 -10
  330. package/dist/plugins/local-session-store.js +23 -14
  331. package/dist/plugins/local-session-window.d.ts +45 -0
  332. package/dist/plugins/local-session-window.js +38 -0
  333. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  334. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  335. package/dist/plugins/mailbox-store-sql.js +77 -52
  336. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  337. package/dist/plugins/memory-run-store.d.ts +5 -13
  338. package/dist/plugins/memory-run-store.js +8 -0
  339. package/dist/plugins/one-time-migrations.d.ts +121 -5
  340. package/dist/plugins/one-time-migrations.js +332 -8
  341. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  342. package/dist/plugins/org-delivery-quota-store.js +228 -0
  343. package/dist/plugins/permission-rule-store-file.js +10 -6
  344. package/dist/plugins/permission-rule-store-sql.js +8 -8
  345. package/dist/plugins/pg-pool.js +39 -7
  346. package/dist/plugins/pg-session-storage.d.ts +18 -5
  347. package/dist/plugins/pg-session-storage.js +68 -23
  348. package/dist/plugins/remote-env-adb.d.ts +6 -12
  349. package/dist/plugins/remote-env-adb.js +12 -2
  350. package/dist/plugins/remote-env-device.d.ts +14 -7
  351. package/dist/plugins/remote-env-device.js +23 -15
  352. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  353. package/dist/plugins/remote-env-k8s.js +23 -5
  354. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  355. package/dist/plugins/remote-env-local-docker.js +12 -9
  356. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  357. package/dist/plugins/remote-env-ssh.js +17 -11
  358. package/dist/plugins/run-store-sql.d.ts +16 -14
  359. package/dist/plugins/run-store-sql.js +22 -2
  360. package/dist/plugins/schema-indexes.js +2 -0
  361. package/dist/plugins/send-user-file.js +2 -2
  362. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  363. package/dist/plugins/session-epoch-anchor.js +2 -3
  364. package/dist/plugins/session-ownership.d.ts +11 -0
  365. package/dist/plugins/session-ownership.js +11 -0
  366. package/dist/plugins/session-placement.d.ts +12 -14
  367. package/dist/plugins/session-placement.js +2 -15
  368. package/dist/plugins/session-store.d.ts +18 -8
  369. package/dist/plugins/session-store.js +15 -4
  370. package/dist/plugins/sql-driver.d.ts +74 -11
  371. package/dist/plugins/sql-driver.js +210 -12
  372. package/dist/plugins/sql-errors.d.ts +23 -0
  373. package/dist/plugins/sql-errors.js +21 -0
  374. package/dist/plugins/store-backend.d.ts +36 -1
  375. package/dist/plugins/store-backend.js +40 -14
  376. package/dist/plugins/store-contracts.d.ts +42 -0
  377. package/dist/plugins/store-contracts.js +1 -0
  378. package/dist/plugins/tidb-pool.d.ts +6 -1
  379. package/dist/plugins/tidb-pool.js +44 -10
  380. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  381. package/dist/plugins/tidb-session-storage.js +2 -2
  382. package/dist/plugins/tidb-session-store.d.ts +25 -5
  383. package/dist/plugins/tidb-session-store.js +67 -21
  384. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  385. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  386. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  387. package/dist/plugins/workflow-run-store-sql.js +50 -9
  388. package/dist/posture-source.d.ts +18 -10
  389. package/dist/posture-source.js +4 -1
  390. package/dist/project-identity.d.ts +45 -6
  391. package/dist/project-identity.js +162 -8
  392. package/dist/project-layer-key.d.ts +28 -0
  393. package/dist/project-layer-key.js +29 -0
  394. package/dist/question.js +3 -2
  395. package/dist/read-deny-entry.d.ts +25 -0
  396. package/dist/read-deny-entry.js +12 -0
  397. package/dist/read-face-posture.d.ts +37 -11
  398. package/dist/read-face-posture.js +32 -3
  399. package/dist/request-key-closure.d.ts +7 -0
  400. package/dist/request-key-closure.js +9 -1
  401. package/dist/request-tool-axes.d.ts +12 -0
  402. package/dist/request-tool-axes.js +20 -0
  403. package/dist/request-value-closure.d.ts +5 -0
  404. package/dist/request-value-closure.js +11 -3
  405. package/dist/rule-entry-list.d.ts +26 -2
  406. package/dist/rule-entry-list.js +72 -12
  407. package/dist/rules-consent.js +3 -4
  408. package/dist/run-acceptance.d.ts +19 -3
  409. package/dist/run-acceptance.js +13 -1
  410. package/dist/run-local.js +37 -6
  411. package/dist/runs.d.ts +12 -10
  412. package/dist/runs.js +25 -6
  413. package/dist/runtime-caps-resolver.d.ts +16 -6
  414. package/dist/runtime-caps-resolver.js +18 -5
  415. package/dist/sandbox-pkg-source.d.ts +19 -1
  416. package/dist/sandbox-pkg-source.js +29 -7
  417. package/dist/sealed-key.d.ts +32 -0
  418. package/dist/sealed-key.js +55 -1
  419. package/dist/security.d.ts +95 -15
  420. package/dist/security.js +52 -9
  421. package/dist/session-error-wire.d.ts +31 -7
  422. package/dist/session-error-wire.js +9 -2
  423. package/dist/session-sync-kernel.d.ts +37 -10
  424. package/dist/session-sync-kernel.js +10 -0
  425. package/dist/session-titled-frame.d.ts +33 -0
  426. package/dist/session-titled-frame.js +4 -0
  427. package/dist/session-titler.d.ts +9 -1
  428. package/dist/session-titler.js +15 -4
  429. package/dist/spec-fields.js +21 -2
  430. package/dist/sql-engine-posture.d.ts +3 -1
  431. package/dist/task-a2a.js +4 -3
  432. package/dist/task-cwd.d.ts +1 -1
  433. package/dist/task-mcp.d.ts +5 -5
  434. package/dist/task-mcp.js +27 -74
  435. package/dist/task-settings.d.ts +93 -88
  436. package/dist/task-settings.js +71 -63
  437. package/dist/text-bidi.d.ts +0 -4
  438. package/dist/text-bidi.js +1 -3
  439. package/dist/tighten-only-seats.d.ts +150 -0
  440. package/dist/tighten-only-seats.js +196 -0
  441. package/dist/tool-approval.d.ts +451 -136
  442. package/dist/tool-approval.js +733 -256
  443. package/dist/tool-rule-names.d.ts +6 -0
  444. package/dist/trace/core-keyset-guard.d.ts +108 -25
  445. package/dist/trace/exit-seat.d.ts +40 -0
  446. package/dist/trace/exit-seat.js +130 -0
  447. package/dist/trace/ledger-events.d.ts +6 -3
  448. package/dist/trace/project.d.ts +98 -13
  449. package/dist/trace/project.js +57 -32
  450. package/dist/trace/projection-table.d.ts +100 -0
  451. package/dist/trace/projection-table.js +130 -0
  452. package/dist/trace/redact.d.ts +57 -0
  453. package/dist/trace/redact.js +962 -168
  454. package/dist/trace/self-minted-wire.d.ts +349 -1
  455. package/dist/trace/self-minted-wire.js +118 -1
  456. package/dist/trace/wire-projection-faces.d.ts +304 -2
  457. package/dist/trace/wire-projection-faces.js +160 -13
  458. package/dist/url-refusal.js +2 -0
  459. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  460. package/dist/web-admin-client/apply-effective.js +56 -5
  461. package/dist/web-admin-client/facade.d.ts +1 -1
  462. package/dist/web-admin-client/facade.js +1 -1
  463. package/dist/web-admin-client/read-face.js +5 -9
  464. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  465. package/dist/web-admin-client/skills-mcp.js +6 -8
  466. package/dist/web-admin-client/types.d.ts +1 -1
  467. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  468. package/package.json +8 -4
  469. package/scripts/drain-check-local.mjs +34 -2
  470. package/scripts/drain-check.mysql.sql +20 -2
  471. package/scripts/drain-check.pg.sql +20 -2
  472. package/dist/mcp-tool-faces.d.ts +0 -69
  473. package/dist/mcp-tool-faces.js +0 -106
@@ -1,7 +1,9 @@
1
1
  import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
4
- import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
4
+ import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
5
+ import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
6
+ const DELIVERY_NOT_RECORDED = "workflow park decide: this delivery no longer holds the host session (its claim was released) — the decision was not recorded and this delivery was not started";
5
7
  const sessionFaceUnavailable = async (sessionId) => {
6
8
  throw new Error(`parked decide cannot verify which occupant session ${sessionId} belongs to: this deployment's session store has no sessionFaceOf read (every built-in store has one) — refusing rather than resuming unverified`);
7
9
  };
@@ -9,12 +11,14 @@ import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../se
9
11
  import { parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
10
12
  import { failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane } from "../terminal.js";
11
13
  import { buildCancelledLedgerRow, createRunAbort, mintCancelledResult } from "../run-cancel-context.js";
14
+ import { isTerminalRunStatus } from "../plugins/store-contracts.js";
12
15
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
13
16
  import { recordResumeResourceWindow } from "../resource-window.js";
14
17
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
15
18
  import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
16
19
  import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
17
- import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
20
+ import { currentApprovalGateToken } from "../approval-gate-currency.js";
21
+ import { createApprovalCardEmitter, declaredApprovalFrameFamilies, isApprovalGateKind, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
18
22
  import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
19
23
  import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
20
24
  import { serviceVersion } from "../service-version.js";
@@ -32,7 +36,7 @@ import { errorText } from "../observability/err-text.js";
32
36
  import { recordFailOpen } from "../observability/fail-open.js";
33
37
  import { verifyRoundsFromBody } from "./verify-rounds.js";
34
38
  import { planReviewDecisionExitsPlan, storedBodyPermissionMode } from "../task-settings.js";
35
- import { acceptedResumingReceipt, buildHostLegOutcome } from "./route-ctx.js";
39
+ import { acceptedResumingReceipt, acceptedTaskIdOf, buildHostLegOutcome } from "./route-ctx.js";
36
40
  import { mintAndDriveDurableRun } from "./durable-run-leg.js";
37
41
  import { createSessionsLocal } from "./routes/sessions.js";
38
42
  import { createSessionSyncLocal } from "./routes/session-sync.js";
@@ -44,12 +48,13 @@ import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js
44
48
  import { commitSessionInheritEnv } from "./session-inherit-env.js";
45
49
  import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
46
50
  import { deploymentListAsks } from "../approval.js";
47
- import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
48
- import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
49
- import { refusalBody } from "./refusal-body.js";
51
+ import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnsealableAnswer } from "./resume-ctx-read.js";
52
+ import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
53
+ import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
50
54
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
51
55
  import { httpErrorRefusal } from "./send.js";
52
56
  import { createRunAcceptance } from "../run-acceptance.js";
57
+ import { fieldConflictFacts } from "../field-conflict.js";
53
58
  function legMayHaveReopenedCheckpoint(r) {
54
59
  return r.terminal.kind === "failed" && nestedPauseOf(r.terminal) === undefined;
55
60
  }
@@ -121,6 +126,9 @@ const CHECKPOINT_ERROR_DISPOSITION = {
121
126
  "steering.invalid_content": "terminal",
122
127
  "steering.queue_full": "terminal",
123
128
  };
129
+ function windowZeroSeatOf(toolApproval) {
130
+ return (identity) => toolApproval.boundUnattendedAsk(identity, { arm: "window_zero" });
131
+ }
124
132
  export function resumeRefusalDisposition(e) {
125
133
  const word = Object.hasOwn(CHECKPOINT_ERROR_DISPOSITION, e.code) ? CHECKPOINT_ERROR_DISPOSITION[e.code] : undefined;
126
134
  switch (word) {
@@ -170,7 +178,7 @@ function decisionRefusalCauseOf(e, gate) {
170
178
  return role === undefined ? undefined : FIELD_ROLE_CAUSE[role];
171
179
  }
172
180
  function notResumableAnswer() {
173
- return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
181
+ return { status: 409, body: refusalBody({ errorCode: "conflict.not_resumable", message: "run is not in a resumable (suspended) state (already resumed, decided, or expired)" }) };
174
182
  }
175
183
  export function checkpointRefusalAnswer(e, at) {
176
184
  const refusalCause = e.code === "checkpoint.invalid_outcome" ? decisionRefusalCauseOf(e, at.gate) : undefined;
@@ -182,9 +190,7 @@ export function checkpointRefusalAnswer(e, at) {
182
190
  errorCode: e.code === "checkpoint.invalid_outcome" ? (refusalCause !== undefined ? WIRE_CODE_OF_DECISION_REFUSAL[refusalCause] : UNCLASSIFIED_INVALID_OUTCOME_CODE) : e.code,
183
191
  facts: {
184
192
  ...(at.taskId !== undefined ? { taskId: at.taskId } : {}),
185
- ...(detail?.code !== undefined ? { blockedBy: detail.code } : {}),
186
- ...(detail?.field ? { field: detail.field } : {}),
187
- ...(detail?.reason !== undefined ? { reason: detail.reason } : {}),
193
+ ...resumeRefusalFacts(detail),
188
194
  ...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof detail?.retryAfterMs === "number" && Number.isFinite(detail.retryAfterMs) && detail.retryAfterMs > 0
189
195
  ? { retryAfterSec: Math.max(1, Math.ceil(detail.retryAfterMs / 1000)) }
190
196
  : {}),
@@ -194,11 +200,12 @@ export function checkpointRefusalAnswer(e, at) {
194
200
  }, at.retriable);
195
201
  }
196
202
  export function createResumeLegs(ctx) {
197
- const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
203
+ const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
204
+ const ctxOpening = (sessionId) => ({ sessionId, opener: deps.checkpointCtxSealing });
198
205
  const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
199
206
  async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, onResumeCommitted, acceptEarly, decider) {
200
207
  const cs = deps.checkpointStore;
201
- const token = (await cs.findPendingTokenBySession(sessionId, undefined, { gateKinds: APPROVAL_GATE_KINDS })) ?? (await cs.findPendingTokenBySession(sessionId));
208
+ const token = await currentApprovalGateToken(cs, sessionId);
202
209
  if (binding?.checkpointToken && binding.checkpointToken !== token) {
203
210
  let seenUnreadable = false;
204
211
  const seen = await cs.get(binding.checkpointToken).catch((e) => {
@@ -212,7 +219,7 @@ export function createResumeLegs(ctx) {
212
219
  }
213
220
  if (!token && !attributable && !seenUnreadable) {
214
221
  deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true });
215
- return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
222
+ return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
216
223
  }
217
224
  let currentPending;
218
225
  if (token && decider !== undefined) {
@@ -225,7 +232,7 @@ export function createResumeLegs(ctx) {
225
232
  const scopeOk = cur !== null && (decider.explicitOperator || curOwner === undefined || curOwner === decider.principal);
226
233
  if (cur && cur.sessionId === sessionId && !scopeOk) {
227
234
  deps.logger?.info?.("approval_decide_no_pending", { sessionId, staleTokenEchoed: true, foreignCurrentSuppressed: true });
228
- return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
235
+ return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
229
236
  }
230
237
  if (cur && cur.status === "pending" && cur.sessionId === sessionId && scopeOk && curPa?.kind === "tool_approval" && curPa.toolCallId) {
231
238
  currentPending = {
@@ -237,22 +244,24 @@ export function createResumeLegs(ctx) {
237
244
  }
238
245
  return {
239
246
  status: 409,
240
- body: {
241
- error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
247
+ body: refusalBody({
242
248
  errorCode: "approval_stale",
243
- ...(attributable ? { terminal: "resolved" } : {}),
244
- ...(currentPending ? { currentPending } : {}),
245
- },
249
+ message: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
250
+ facts: {
251
+ ...(attributable ? { terminal: "resolved" } : {}),
252
+ ...(currentPending ? { currentPending } : {}),
253
+ },
254
+ }),
246
255
  };
247
256
  }
248
257
  if (!token) {
249
258
  deps.logger?.info?.("approval_decide_no_pending", { sessionId });
250
- return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
259
+ return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
251
260
  }
252
261
  const cpPromise = cs.get(token);
253
262
  const ctxPromise = cs.getCtx(sessionId);
254
263
  void ctxPromise.catch(() => { });
255
- const noApproval = { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
264
+ const noApproval = { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
256
265
  let cp;
257
266
  try {
258
267
  cp = await cpPromise;
@@ -263,18 +272,18 @@ export function createResumeLegs(ctx) {
263
272
  deps.logger?.warn?.("approval_decide_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
264
273
  return noApproval;
265
274
  }
266
- return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
275
+ return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) };
267
276
  }
268
277
  if (!cp) {
269
278
  if (!(await mayDiscloseRowError(cs, token, decider)))
270
279
  return noApproval;
271
- return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
280
+ return { status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) };
272
281
  }
273
282
  if (decider !== undefined && !decider.explicitOperator) {
274
283
  const cpOwner = decodeCheckpointScope(cp.scope);
275
284
  if (cpOwner !== undefined && cpOwner !== decider.principal) {
276
285
  deps.logger?.warn?.("approval_decide_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
277
- return { status: 404, body: { error: "approval not found", errorCode: "not_found.approval" } };
286
+ return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
278
287
  }
279
288
  }
280
289
  const decideGateKind = cp.gate?.kind;
@@ -287,21 +296,25 @@ export function createResumeLegs(ctx) {
287
296
  : null;
288
297
  return {
289
298
  status: 409,
290
- body: {
291
- error: `task is suspended on a '${decideGateKind ?? "unknown"}' gate, not a tool approval — a plan_review gate is resolved via POST /v1/assistant/tasks/:id/plan_review, a resource_limit suspension via POST /v1/assistant/tasks/:id/resume`,
299
+ body: refusalBody({
292
300
  errorCode: "gate_not_tool_approval",
293
- ...(decideGateKind ? { pendingGate: { kind: decideGateKind, ...(gnPath ? { decidePath: gnPath } : {}) } } : {}),
294
- },
301
+ message: `task is suspended on a '${decideGateKind ?? "unknown"}' gate, not a tool approval — a plan_review gate is resolved via POST /v1/assistant/tasks/:id/plan_review, a resource_limit suspension via POST /v1/assistant/tasks/:id/resume`,
302
+ facts: {
303
+ ...(decideGateKind ? { pendingGate: { kind: decideGateKind, ...(gnPath ? { decidePath: gnPath } : {}) } } : {}),
304
+ },
305
+ }),
295
306
  };
296
307
  }
297
308
  if (req !== undefined && !binding?.checkpointToken && !binding?.boundCallId) {
298
309
  return {
299
310
  status: 400,
300
- body: {
301
- error: "decide requires the decision-action binding — echo checkpointToken and/or boundCallId from the pending approval (GET /v1/approvals)",
311
+ body: refusalBody({
302
312
  errorCode: "decide_requires_binding",
303
- bindingEnforced: true,
304
- },
313
+ message: "decide requires the decision-action binding — echo checkpointToken and/or boundCallId from the pending approval (GET /v1/approvals)",
314
+ facts: {
315
+ bindingEnforced: true,
316
+ },
317
+ }),
305
318
  };
306
319
  }
307
320
  const s57RowCallId = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.toolCallId : undefined;
@@ -316,11 +329,13 @@ export function createResumeLegs(ctx) {
316
329
  }
317
330
  return {
318
331
  status: 409,
319
- body: {
320
- error: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
332
+ body: refusalBody({
321
333
  errorCode: "approval_stale",
322
- ...(s57CurrentPending ? { currentPending: s57CurrentPending } : {}),
323
- },
334
+ message: "the approval you are deciding is no longer current (already resolved or superseded since you viewed it)",
335
+ facts: {
336
+ ...(s57CurrentPending ? { currentPending: s57CurrentPending } : {}),
337
+ },
338
+ }),
324
339
  };
325
340
  }
326
341
  const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
@@ -374,7 +389,7 @@ export function createResumeLegs(ctx) {
374
389
  const parkedReq = {
375
390
  token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
376
391
  decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
377
- ...(binding !== undefined ? { binding: { ...(binding.boundCallId !== undefined ? { boundCallId: binding.boundCallId } : {}), ...(binding.boundInputHash !== undefined ? { boundInputHash: binding.boundInputHash } : {}), ...(binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}) } } : {}),
392
+ ...(binding !== undefined ? { binding: (({ checkpointToken: _staleGuardOnly, ...rest }) => rest)(binding) } : {}),
378
393
  ...(answer !== undefined ? { answer } : {}),
379
394
  ...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
380
395
  };
@@ -387,7 +402,7 @@ export function createResumeLegs(ctx) {
387
402
  sessionFaceOf: deps.sessionStorage?.sessionFaceOf ? deps.sessionStorage.sessionFaceOf.bind(deps.sessionStorage) : sessionFaceUnavailable,
388
403
  ...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
389
404
  ...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
390
- ...(deps.toolApproval ? { approverSeatFor: deps.toolApproval.boundUnattendedAsk } : {}),
405
+ ...(deps.toolApproval ? { approverSeatFor: windowZeroSeatOf(deps.toolApproval) } : {}),
391
406
  ...(deps.logger ? { warn: parkWarn } : {}),
392
407
  }, parkedReq, { handle: origin.handle, row: origin.row }));
393
408
  void ctxPromise.catch(() => { });
@@ -407,22 +422,58 @@ export function createResumeLegs(ctx) {
407
422
  const plan = planWorkflowParkedDecide(origin, parkedReq);
408
423
  if (!plan.ok)
409
424
  return plan.deny;
410
- const parkedResume = { workflowParkedResume: [{ runId: plan.runId, token, outcome: plan.outcome }] };
425
+ const decisions = deps.workflowParkDecisions;
426
+ const runStore = deps.runStore;
427
+ if (decisions === undefined || runStore === undefined) {
428
+ return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
429
+ }
430
+ const fingerprint = parkDecisionFingerprint(plan.outcome);
431
+ const liveReceiptOf = async (held) => {
432
+ if (held === null || held.fingerprint !== fingerprint)
433
+ return undefined;
434
+ const run = await runStore.getRun(held.deliveredTaskId);
435
+ return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId) : undefined;
436
+ };
437
+ const replay = await liveReceiptOf(await decisions.get(token));
438
+ if (replay !== undefined)
439
+ return replay;
440
+ let recordFailure;
441
+ const delivery = {
442
+ carried: [{ runId: plan.runId, token, outcome: plan.outcome }],
443
+ commitBeforeLaunch: async (deliveredTaskId) => {
444
+ try {
445
+ const recorded = await decisions.record({ token, runId: plan.runId, hostSessionId: plan.hostSessionId, outcome: plan.outcome, fingerprint, deliveredTaskId, acceptedAtMs: Date.now() });
446
+ if (!recorded)
447
+ throw new Error(DELIVERY_NOT_RECORDED);
448
+ }
449
+ catch (err) {
450
+ recordFailure = { err };
451
+ throw err;
452
+ }
453
+ },
454
+ };
411
455
  const hostToken = await cs
412
456
  .findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
413
457
  .catch(() => undefined);
414
458
  const admittedPayer = decodeCheckpointScope(cp.scope);
415
- return hostToken
416
- ? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, parkedResume.workflowParkedResume, admittedPayer)
417
- : await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, parkedResume, plan.runId);
459
+ const out = hostToken
460
+ ? await resumeWake(plan.hostSessionId, plan.message, decider?.principal, req, acceptEarly, delivery, admittedPayer)
461
+ : await startFreshHostTurn(plan.hostSessionId, plan.message, admittedPayer, req, delivery, plan.runId);
462
+ if (recordFailure !== undefined)
463
+ throw recordFailure.err;
464
+ if (acceptedTaskIdOf(out) === undefined)
465
+ return (await liveReceiptOf(await decisions.get(token))) ?? out;
466
+ return out;
418
467
  }
419
468
  }
420
- const ctxRead0 = await readResumeCtx(ctxPromise);
469
+ const ctxRead0 = await readResumeCtx(ctxPromise, ctxOpening(sessionId));
421
470
  if (ctxRead0.kind === "corrupt")
422
471
  return resumeCtxCorruptAnswer(ctxRead0.where);
472
+ if (ctxRead0.kind === "unsealable")
473
+ return resumeCtxUnsealableAnswer(ctxRead0);
423
474
  const ctx = ctxRead0.kind === "ok" ? ctxRead0.ctx : null;
424
475
  if (!ctx)
425
- return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
476
+ return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
426
477
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
427
478
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
428
479
  if (!resolved.ok)
@@ -430,10 +481,10 @@ export function createResumeLegs(ctx) {
430
481
  const { spec, engineNotices } = resolved;
431
482
  const pendingTool = cp.pendingAction.toolName;
432
483
  if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
433
- return { status: 400, body: { error: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", errorCode: "request.field_conflict" } };
484
+ return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", facts: { ...fieldConflictFacts("ask_answer", ["decision", "answer"]) } }) };
434
485
  }
435
486
  if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
436
- return { status: 400, body: { error: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, errorCode: "request.field_conflict" } };
487
+ return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, facts: { ...fieldConflictFacts("ask_answer", ["answer", "decision"]) } }) };
437
488
  }
438
489
  const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
439
490
  const pa = cp.pendingAction;
@@ -446,6 +497,7 @@ export function createResumeLegs(ctx) {
446
497
  decision: decision === "approve" ? "allow" : "deny",
447
498
  ...(decision === "approve" && binding?.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
448
499
  ...(reason ? { reason } : {}),
500
+ ...(binding?.feedback !== undefined ? { feedback: binding.feedback } : {}),
449
501
  ...(answer !== undefined ? { answer } : {}),
450
502
  hostDecision,
451
503
  };
@@ -508,7 +560,7 @@ export function createResumeLegs(ctx) {
508
560
  const resumeLegStartedAt = Date.now();
509
561
  const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx } = args;
510
562
  const reparkStatus = outcome.gate === "plan_review" ? "needs_review" : "suspended";
511
- const commitResumeCtx = () => deps.checkpointStore.putCtx(resumeCtx.sessionId, { ...resumeCtx.ctx, writerVersion: serviceVersion() }).catch((e) => {
563
+ const commitResumeCtx = () => Promise.resolve().then(() => deps.checkpointStore.putCtx(resumeCtx.sessionId, sealCheckpointCtx({ ...resumeCtx.ctx, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, resumeCtx.sessionId))).catch((e) => {
512
564
  deps.metrics?.inc("resume_ctx_not_persisted_total");
513
565
  deps.logger?.warn?.("resume_ctx_not_persisted", { sessionId: resumeCtx.sessionId, gate: outcome.gate, error: errorText(e), note: "this continuation runs with the body it was given; the durable rebuild input still holds the PREVIOUS body, so the NEXT continuation of this session rebuilds from that one (never wider than this leg's)" });
514
566
  });
@@ -517,7 +569,7 @@ export function createResumeLegs(ctx) {
517
569
  toolApprovalEnabled: Boolean(deps.toolApproval),
518
570
  streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
519
571
  backend: deps.backend,
520
- parkFacility: deps.checkpointStore !== undefined,
572
+ checkpointStore: deps.checkpointStore,
521
573
  }).active;
522
574
  const approvalLeg = resolveApprovalLeg({
523
575
  streamApprovalOn,
@@ -525,6 +577,7 @@ export function createResumeLegs(ctx) {
525
577
  windowMarginMs: deps.config.streamAskWindowMarginMs,
526
578
  ...(typeof taskConfig.limits?.maxWalltimeMs === "number" ? { legWalltimeMs: taskConfig.limits.maxWalltimeMs } : {}),
527
579
  nowMonotonicMs: performance.now(),
580
+ declaredFamilies: declaredApprovalFrameFamilies(resumeCtx.ctx.body.settings),
528
581
  });
529
582
  const legDeadlineMonotonic = approvalLeg.legDeadlineMonotonic;
530
583
  const ownerQuotaDenial = args.callerInitiated === true ? ownerCostQuotaDenied(principal) : undefined;
@@ -533,6 +586,7 @@ export function createResumeLegs(ctx) {
533
586
  const ownerLeaseDenial = args.callerInitiated === true && args.ownerLeaseAdmitted !== true ? await ownerFleetLeaseDenied(principal) : undefined;
534
587
  if (ownerLeaseDenial)
535
588
  return ownerLeaseDenial;
589
+ const heldDecisions = await heldWorkflowParkDecisions(deps, sessionId, args.workflowDecision?.carried);
536
590
  let taskId = await deps.runStore?.getActiveTaskId(sessionId);
537
591
  let freshRunClaimed = false;
538
592
  if (args.mintFreshRun && taskId && deps.runStore) {
@@ -562,8 +616,8 @@ export function createResumeLegs(ctx) {
562
616
  const resumeTaskConfig = taskId ? { ...taskConfig, taskId } : taskConfig;
563
617
  if (deps.toolApproval && taskId) {
564
618
  const toolApproval = deps.toolApproval;
565
- resumeTaskConfig.onAsk = approvalLeg.windowZero
566
- ? toolApproval.boundUnattendedAsk({ owner: principal ?? null, taskId, sessionId })
619
+ resumeTaskConfig.onAsk = approvalLeg.unattended
620
+ ? toolApproval.boundUnattendedAsk({ owner: principal ?? null, taskId, sessionId }, approvalLeg.unattended)
567
621
  : toolApproval.boundAsk({
568
622
  owner: principal ?? null,
569
623
  taskId,
@@ -654,6 +708,14 @@ export function createResumeLegs(ctx) {
654
708
  deps.modelUsage?.register(taskId);
655
709
  resumeManifestLease = deps.promptManifests?.register(taskId) ?? 0;
656
710
  }
711
+ if (args.workflowDecision !== undefined && taskId === undefined) {
712
+ throw new Error("workflow decision delivery: no run row was claimed for the delivering leg — refusing to start a delivery whose decision could not be recorded against it");
713
+ }
714
+ const workflowDecision = args.workflowDecision;
715
+ const launchInternals = {
716
+ ...heldDecisions,
717
+ ...(workflowDecision !== undefined && taskId !== undefined ? { afterCheckpointResolve: () => workflowDecision.commitBeforeLaunch(taskId) } : {}),
718
+ };
657
719
  commitSessionInheritEnv(deps, sessionId, resumeCtx.ctx.body);
658
720
  if (args.onResumeCommitted)
659
721
  await args.onResumeCommitted();
@@ -689,6 +751,7 @@ export function createResumeLegs(ctx) {
689
751
  cancelLedgerAppend = vAppend;
690
752
  const verifyTailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
691
753
  const verifyInternals = {
754
+ ...launchInternals,
692
755
  ...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
693
756
  onForwardEvent: (e) => {
694
757
  fleetPub?.onForwardEvent(e);
@@ -817,7 +880,7 @@ export function createResumeLegs(ctx) {
817
880
  resumeLegLive = true;
818
881
  const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
819
882
  const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
820
- ...(args.workflowParkedResume !== undefined ? { workflowParkedResume: args.workflowParkedResume } : {}),
883
+ ...launchInternals,
821
884
  onForwardEvent: (e) => {
822
885
  fleetPub?.onForwardEvent(e);
823
886
  {
@@ -1041,11 +1104,10 @@ export function createResumeLegs(ctx) {
1041
1104
  const { type, ...rest } = frame;
1042
1105
  return append(type, rest);
1043
1106
  };
1044
- const withApproval = () => deps.toolApproval && taskId && approvalLeg.active && !approvalLeg.windowZero
1107
+ const withApproval = () => deps.toolApproval && taskId && approvalLeg.active && approvalLeg.unattended === undefined
1045
1108
  ? deps.toolApproval.runWithContext({
1046
1109
  taskId,
1047
1110
  owner: principal ?? null,
1048
- emit: approvalEmit,
1049
1111
  abortSignal: cancelCtrl.signal,
1050
1112
  sessionId,
1051
1113
  emitCard: createApprovalCardEmitter({ appendDurable: (f) => approvalEmit(f) }),
@@ -1181,7 +1243,7 @@ export function createResumeLegs(ctx) {
1181
1243
  const RESUMABLE_GATE_KIND = "resource_limit";
1182
1244
  const preemptBinding = { gateKinds: [RESUMABLE_GATE_KIND] };
1183
1245
  const token = (await cs.findPendingTokenBySession(sessionId, undefined, preemptBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
1184
- const noSuspension = { status: 404, body: { error: "no resumable suspension for this task (already running, resumed, or expired)", errorCode: "not_found.suspension" } };
1246
+ const noSuspension = { status: 404, body: refusalBody({ errorCode: "not_found.suspension", message: "no resumable suspension for this task (already running, resumed, or expired)" }) };
1185
1247
  if (!token)
1186
1248
  return noSuspension;
1187
1249
  if (decider !== undefined && !decider.explicitOperator) {
@@ -1204,12 +1266,12 @@ export function createResumeLegs(ctx) {
1204
1266
  deps.logger?.warn?.("assistant_resume_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
1205
1267
  return noSuspension;
1206
1268
  }
1207
- return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
1269
+ return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) };
1208
1270
  }
1209
1271
  if (!cp) {
1210
1272
  if (!(await mayDiscloseRowError(cs, token, decider)))
1211
1273
  return noSuspension;
1212
- return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
1274
+ return { status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) };
1213
1275
  }
1214
1276
  if (decider !== undefined && !decider.explicitOperator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== decider.principal) {
1215
1277
  deps.logger?.warn?.("assistant_resume_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
@@ -1217,13 +1279,15 @@ export function createResumeLegs(ctx) {
1217
1279
  }
1218
1280
  const gateKind = cp.gate?.kind;
1219
1281
  if (gateKind !== RESUMABLE_GATE_KIND) {
1220
- return { status: 409, body: { error: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a resumable resource/preempt suspension — an approval gate must be decided via POST /v1/approvals/:id/decide`, errorCode: "gate_not_resumable" } };
1282
+ return { status: 409, body: refusalBody({ errorCode: "gate_not_resumable", message: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a resumable resource/preempt suspension — an approval gate must be decided via POST /v1/approvals/:id/decide` }) };
1221
1283
  }
1222
- const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
1284
+ const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
1223
1285
  if (ctxRead.kind === "corrupt")
1224
1286
  return resumeCtxCorruptAnswer(ctxRead.where);
1287
+ if (ctxRead.kind === "unsealable")
1288
+ return resumeCtxUnsealableAnswer(ctxRead);
1225
1289
  if (ctxRead.kind === "absent")
1226
- return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1290
+ return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
1227
1291
  const ctx = ctxRead.ctx;
1228
1292
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1229
1293
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
@@ -1234,24 +1298,28 @@ export function createResumeLegs(ctx) {
1234
1298
  const verifyRounds = verifyRoundsFromBody(ctx.body);
1235
1299
  return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1236
1300
  }
1237
- async function startFreshHostTurn(sessionId, objective, principal, req, trustedInternals, runId) {
1301
+ async function startFreshHostTurn(sessionId, objective, principal, req, workflowDecision, runId) {
1238
1302
  const cs = deps.checkpointStore;
1239
1303
  if (!deps.runStore) {
1240
- return { status: 501, body: { error: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)", errorCode: "capability.run_store_required" } };
1304
+ return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
1241
1305
  }
1242
- const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
1306
+ const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
1243
1307
  if (ctxRead.kind === "corrupt")
1244
1308
  return resumeCtxCorruptAnswer(ctxRead.where);
1309
+ if (ctxRead.kind === "unsealable")
1310
+ return resumeCtxUnsealableAnswer(ctxRead);
1245
1311
  const ctx = ctxRead.kind === "ok" ? ctxRead.ctx : null;
1246
1312
  if (!ctx) {
1247
1313
  return {
1248
1314
  status: 409,
1249
- body: {
1250
- error: "this approval waited too long to be delivered: the session this run was started from no longer keeps what a new turn would need, because that state is dropped once the session has been idle past this deployment's retention window (REAP_RUN_STALE_SEC — its current value is staleAfterSec below). Nothing was decided and the approval is still pending: resume the run yourself with the runId below, or ask whoever operates this deployment to raise REAP_RUN_STALE_SEC so approvals can sit longer before someone decides them",
1315
+ body: refusalBody({
1251
1316
  errorCode: "conflict.resume_context_unavailable",
1252
- runId,
1253
- staleAfterSec: deps.config.runStaleSec,
1254
- },
1317
+ message: "this approval waited too long to be delivered: the session this run was started from no longer keeps what a new turn would need, because that state is dropped once the session has been idle past this deployment's retention window (REAP_RUN_STALE_SEC — its current value is staleAfterSec below). Nothing was decided and the approval is still pending: resume the run yourself with the runId below, or ask whoever operates this deployment to raise REAP_RUN_STALE_SEC so approvals can sit longer before someone decides them",
1318
+ facts: {
1319
+ runId,
1320
+ staleAfterSec: deps.config.runStaleSec,
1321
+ },
1322
+ }),
1255
1323
  };
1256
1324
  }
1257
1325
  const windowDenial = await usageWindowDenialFor(principal || GLOBAL_USAGE_KEY);
@@ -1268,7 +1336,7 @@ export function createResumeLegs(ctx) {
1268
1336
  fleetScope: resumeFleetScope(req, auth),
1269
1337
  elicitOwner: principal ?? null,
1270
1338
  source: "workflow_parked_decide",
1271
- trustedInternals,
1339
+ workflowDecision,
1272
1340
  });
1273
1341
  if (out.existing) {
1274
1342
  throw new Error(`decide host turn: createRun observed an existing row ${out.existing.taskId} for a freshly minted taskId (no idempotency key was sent)`);
@@ -1278,18 +1346,18 @@ export function createResumeLegs(ctx) {
1278
1346
  const minted = out.body;
1279
1347
  return acceptedResumingReceipt(minted.taskId, minted.sessionId);
1280
1348
  }
1281
- async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowParkedResume, ownerLeaseAdmittedFor) {
1349
+ async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowDecision, ownerLeaseAdmittedFor) {
1282
1350
  const cs = deps.checkpointStore;
1283
1351
  const wakeBinding = { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" };
1284
1352
  const token = (await cs.findPendingTokenBySession(sessionId, undefined, wakeBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
1285
1353
  if (!token)
1286
- return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1287
- const operator = explicitOperatorOk(caller, deps.config.operatorPrincipals);
1354
+ return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
1355
+ const operator = explicitOperatorOk(caller, deps.config);
1288
1356
  if (!operator) {
1289
1357
  const scope = await cs.peekPendingScope(sessionId, wakeBinding).then((s) => (s === undefined ? cs.peekPendingScope(sessionId) : s)).catch(() => undefined);
1290
1358
  const decodedScope = scope != null ? decodeCheckpointScope(scope) : undefined;
1291
1359
  if (decodedScope !== undefined && decodedScope !== caller) {
1292
- return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1360
+ return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
1293
1361
  }
1294
1362
  }
1295
1363
  let cp;
@@ -1299,22 +1367,22 @@ export function createResumeLegs(ctx) {
1299
1367
  catch (e) {
1300
1368
  if (!(await mayDiscloseRowError(cs, token, { principal: caller, explicitOperator: operator }))) {
1301
1369
  deps.logger?.warn?.("wake_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
1302
- return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1370
+ return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
1303
1371
  }
1304
- return { status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } };
1372
+ return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) };
1305
1373
  }
1306
1374
  if (!cp) {
1307
1375
  if (!(await mayDiscloseRowError(cs, token, { principal: caller, explicitOperator: operator }))) {
1308
- return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1376
+ return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
1309
1377
  }
1310
- return { status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } };
1378
+ return { status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) };
1311
1379
  }
1312
1380
  if (!operator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== caller) {
1313
- return { status: 404, body: { error: "no parked checkpoint for this session (nothing to wake)", errorCode: "not_found.parked_checkpoint" } };
1381
+ return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
1314
1382
  }
1315
1383
  const gateKind = cp.gate?.kind;
1316
1384
  if (gateKind !== "task_done") {
1317
- return { status: 409, body: { error: `cannot wake: this session is waiting on a pending gate decision (gate "${gateKind ?? "unknown"}") — resolve it through its own decide entry`, errorCode: "wake.gate_pending" } };
1385
+ return { status: 409, body: refusalBody({ errorCode: "wake.gate_pending", message: `cannot wake: this session is waiting on a pending gate decision (gate "${gateKind ?? "unknown"}") — resolve it through its own decide entry` }) };
1318
1386
  }
1319
1387
  const trusted = operator;
1320
1388
  if (message !== undefined) {
@@ -1323,16 +1391,18 @@ export function createResumeLegs(ctx) {
1323
1391
  }
1324
1392
  catch (e) {
1325
1393
  if (e instanceof CheckpointError && e.code === "steering.invalid_content") {
1326
- return { status: 422, body: { errorCode: "steering.invalid_content", error: e.message } };
1394
+ return { status: 422, body: refusalBody({ errorCode: "steering.invalid_content", message: e.message }) };
1327
1395
  }
1328
1396
  throw e;
1329
1397
  }
1330
1398
  }
1331
- const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
1399
+ const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
1332
1400
  if (ctxRead.kind === "corrupt")
1333
1401
  return resumeCtxCorruptAnswer(ctxRead.where);
1402
+ if (ctxRead.kind === "unsealable")
1403
+ return resumeCtxUnsealableAnswer(ctxRead);
1334
1404
  if (ctxRead.kind === "absent")
1335
- return { status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } };
1405
+ return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
1336
1406
  const ctx = ctxRead.ctx;
1337
1407
  const auth = { sessionId: cp.sessionId, principal: decodeCheckpointScope(cp.scope), memoryScope: ctx.memoryScope };
1338
1408
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
@@ -1352,10 +1422,10 @@ export function createResumeLegs(ctx) {
1352
1422
  verifyRounds: undefined,
1353
1423
  resumeCtx: { sessionId, ctx },
1354
1424
  mintFreshRun: { source: "wake" },
1425
+ ...(workflowDecision !== undefined ? { workflowDecision } : {}),
1355
1426
  ...(engineNotices.length > 0 ? { engineNotices } : {}),
1356
1427
  ...(acceptEarly === true ? { acceptEarly: true } : {}),
1357
1428
  ...(req !== undefined ? { callerInitiated: true } : {}),
1358
- ...(workflowParkedResume !== undefined ? { workflowParkedResume } : {}),
1359
1429
  ...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
1360
1430
  }));
1361
1431
  }
@@ -1370,7 +1440,7 @@ export function createResumeLegs(ctx) {
1370
1440
  const PLAN_REVIEW_GATE_KIND = "plan_review";
1371
1441
  const planReviewBinding = { gateKinds: [PLAN_REVIEW_GATE_KIND] };
1372
1442
  const token = (await cs.findPendingTokenBySession(sessionId, undefined, planReviewBinding)) ?? (await cs.findPendingTokenBySession(sessionId));
1373
- const noPending = { status: 404, body: { error: "no pending plan_review for this session (already decided or expired)", errorCode: "not_found.plan_review" } };
1443
+ const noPending = { status: 404, body: refusalBody({ errorCode: "not_found.plan_review", message: "no pending plan_review for this session (already decided or expired)" }) };
1374
1444
  if (!token) {
1375
1445
  log404("no-pending-checkpoint");
1376
1446
  return refuse(noPending);
@@ -1395,13 +1465,13 @@ export function createResumeLegs(ctx) {
1395
1465
  deps.logger?.warn?.("plan_review_unreadable_row_withheld", { sessionId, note: "row unreadable and the caller's ownership of THAT token is unproven — folded into the uniform 404" });
1396
1466
  return refuse(noPending);
1397
1467
  }
1398
- return refuse({ status: 409, body: { error: e instanceof Error ? e.message : String(e), errorCode: "conflict.checkpoint_unreadable" } });
1468
+ return refuse({ status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: e instanceof Error ? e.message : String(e) }) });
1399
1469
  }
1400
1470
  if (!cp) {
1401
1471
  log404("checkpoint-missing");
1402
1472
  if (!(await mayDiscloseRowError(cs, token, decider)))
1403
1473
  return refuse(noPending);
1404
- return refuse({ status: 404, body: { error: "checkpoint not found", errorCode: "not_found.checkpoint" } });
1474
+ return refuse({ status: 404, body: refusalBody({ errorCode: "not_found.checkpoint", message: "checkpoint not found" }) });
1405
1475
  }
1406
1476
  if (decider !== undefined && !decider.explicitOperator && cp.scope !== CHECKPOINT_PUBLIC_SCOPE && cp.scope !== decider.principal) {
1407
1477
  deps.logger?.warn?.("plan_review_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
@@ -1411,17 +1481,19 @@ export function createResumeLegs(ctx) {
1411
1481
  if (gateKind !== PLAN_REVIEW_GATE_KIND) {
1412
1482
  return refuse({
1413
1483
  status: 409,
1414
- body: {
1415
- error: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a plan_review — a tool approval must be decided via POST /v1/approvals/:id/decide`,
1484
+ body: refusalBody({
1416
1485
  errorCode: "gate_not_plan_review",
1417
- },
1486
+ message: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a plan_review — a tool approval must be decided via POST /v1/approvals/:id/decide`,
1487
+ }),
1418
1488
  });
1419
1489
  }
1420
- const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
1490
+ const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
1421
1491
  if (ctxRead.kind === "corrupt")
1422
1492
  return refuse(resumeCtxCorruptAnswer(ctxRead.where));
1493
+ if (ctxRead.kind === "unsealable")
1494
+ return refuse(resumeCtxUnsealableAnswer(ctxRead));
1423
1495
  if (ctxRead.kind === "absent")
1424
- return refuse({ status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } });
1496
+ return refuse({ status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) });
1425
1497
  const replay = storedBodyReplayRefusal(ctxRead.ctx, deps.config);
1426
1498
  if (replay !== undefined)
1427
1499
  return refuse(refuseStoredBodyReplay(sessionId, replay));
@@ -1457,7 +1529,7 @@ export function createResumeLegs(ctx) {
1457
1529
  const routeCtxBase = {
1458
1530
  deps,
1459
1531
  registry: { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters },
1460
- helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk },
1532
+ helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk },
1461
1533
  local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },
1462
1534
  legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, preparePlanReview, drivePlanReview },
1463
1535
  };