@sema-agent/server 7.148.0 → 7.150.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 (360) hide show
  1. package/MIGRATION.md +551 -9
  2. package/README.md +17 -2
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +126 -68
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +33 -33
  7. package/dist/approval-gate-currency.d.ts +1 -1
  8. package/dist/approval-leg-origin.d.ts +33 -0
  9. package/dist/approval-leg-origin.js +4 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.js +2 -2
  12. package/dist/auth-bridge.d.ts +9 -1
  13. package/dist/auth-bridge.js +4 -4
  14. package/dist/auth-keys.d.ts +10 -1
  15. package/dist/auth-keys.js +8 -0
  16. package/dist/background-session-reap.d.ts +1 -1
  17. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  18. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  19. package/dist/boot/execution-env.js +10 -11
  20. package/dist/boot/execution-lane.d.ts +6 -4
  21. package/dist/boot/execution-lane.js +3 -1
  22. package/dist/boot/org-delivery.d.ts +3 -2
  23. package/dist/boot/parked-revive-gate.d.ts +5 -5
  24. package/dist/boot/parked-revive-gate.js +2 -3
  25. package/dist/boot/resolve-spec.d.ts +4 -5
  26. package/dist/boot/resolve-spec.js +66 -28
  27. package/dist/boot/retention-lane.d.ts +1 -1
  28. package/dist/boot/runner-deps.js +3 -1
  29. package/dist/boot/session-faces.js +21 -21
  30. package/dist/boot/stage-01-config.d.ts +1 -0
  31. package/dist/boot/stage-01-config.js +4 -3
  32. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  33. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  34. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  35. package/dist/boot/stage-03-store-backend.js +4 -4
  36. package/dist/boot/stage-04-stores.d.ts +1 -0
  37. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  38. package/dist/boot/stage-06-runners.d.ts +2 -1
  39. package/dist/boot/stage-06-runners.js +1 -2
  40. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  41. package/dist/boot/stage-07-capability-layer.js +1 -1
  42. package/dist/boot/stage-08-reapers.d.ts +2 -1
  43. package/dist/boot/stage-09-leader.d.ts +2 -1
  44. package/dist/boot/stage-10-http-server.d.ts +35 -2
  45. package/dist/boot/stage-10-http-server.js +9 -4
  46. package/dist/boot/stores.d.ts +3 -0
  47. package/dist/boot/stores.js +22 -19
  48. package/dist/boot/web-admin.d.ts +1 -1
  49. package/dist/capabilities/tool-defer.d.ts +1 -1
  50. package/dist/capability-facts.d.ts +70 -60
  51. package/dist/capability-facts.js +45 -49
  52. package/dist/card-settle-stored-scope.d.ts +56 -0
  53. package/dist/card-settle-stored-scope.js +27 -0
  54. package/dist/card-void.d.ts +7 -12
  55. package/dist/card-void.js +2 -7
  56. package/dist/config-catalog.d.ts +12 -0
  57. package/dist/config-catalog.js +37 -33
  58. package/dist/config-invariants.d.ts +5 -4
  59. package/dist/config-invariants.js +4 -4
  60. package/dist/config-types.d.ts +49 -16
  61. package/dist/config.d.ts +6 -5
  62. package/dist/config.js +35 -24
  63. package/dist/context-window-notice.d.ts +31 -0
  64. package/dist/context-window-notice.js +28 -0
  65. package/dist/deployment-governance.d.ts +7 -28
  66. package/dist/deployment-governance.js +1 -14
  67. package/dist/deployment-shape.d.ts +20 -11
  68. package/dist/deployment-shape.js +1 -0
  69. package/dist/device-ws-hub.d.ts +1 -0
  70. package/dist/device-ws-hub.js +64 -28
  71. package/dist/device-ws-protocol.d.ts +20 -6
  72. package/dist/device-ws-protocol.js +9 -2
  73. package/dist/engine-identity.d.ts +7 -1
  74. package/dist/engine-identity.js +5 -0
  75. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  76. package/dist/env-name-allowlist-knobs.js +5 -0
  77. package/dist/execution-lane-caps.d.ts +20 -5
  78. package/dist/execution-lane-caps.js +18 -4
  79. package/dist/field-conflict.d.ts +2 -2
  80. package/dist/fleet/engine-signal.d.ts +14 -1
  81. package/dist/fleet/engine-signal.js +5 -1
  82. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  83. package/dist/fleet/serving-lease.d.ts +18 -3
  84. package/dist/fleet/serving-lease.js +81 -35
  85. package/dist/fleet-client.d.ts +13 -11
  86. package/dist/fleet-client.js +53 -33
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +1 -3
  89. package/dist/hitl-delivery.js +1 -1
  90. package/dist/hooks/command-spawn.d.ts +7 -2
  91. package/dist/hooks/command-spawn.js +13 -6
  92. package/dist/hooks/hook-runner.d.ts +5 -0
  93. package/dist/hooks/hook-runner.js +11 -7
  94. package/dist/http/active-run-conflict.d.ts +3 -7
  95. package/dist/http/active-run-conflict.js +2 -2
  96. package/dist/http/admission.js +5 -5
  97. package/dist/http/approval-sweeps.js +5 -3
  98. package/dist/http/caller-trust.d.ts +2 -2
  99. package/dist/http/caller-trust.js +3 -3
  100. package/dist/http/checkpoint-ctx.d.ts +18 -63
  101. package/dist/http/checkpoint-ctx.js +1 -24
  102. package/dist/http/claim-heartbeat.d.ts +6 -6
  103. package/dist/http/claim-heartbeat.js +6 -6
  104. package/dist/http/claimed-run.d.ts +16 -9
  105. package/dist/http/claimed-run.js +8 -0
  106. package/dist/http/dispatch.js +7 -5
  107. package/dist/http/durable-run-leg.d.ts +8 -1
  108. package/dist/http/durable-run-leg.js +34 -15
  109. package/dist/http/execution-context.d.ts +115 -14
  110. package/dist/http/execution-context.js +130 -40
  111. package/dist/http/owner-access.d.ts +246 -21
  112. package/dist/http/owner-access.js +186 -8
  113. package/dist/http/principal-gate.d.ts +18 -11
  114. package/dist/http/principal-gate.js +3 -1
  115. package/dist/http/readiness.d.ts +4 -2
  116. package/dist/http/resume-ctx-read.d.ts +14 -3
  117. package/dist/http/resume-ctx-read.js +24 -3
  118. package/dist/http/resume-legs.d.ts +35 -3
  119. package/dist/http/resume-legs.js +262 -106
  120. package/dist/http/route-ctx.d.ts +29 -11
  121. package/dist/http/routes/a2a-serve.js +17 -14
  122. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  123. package/dist/http/routes/approvals-assistant.js +11 -11
  124. package/dist/http/routes/capabilities.js +5 -4
  125. package/dist/http/routes/config-catalog.d.ts +1 -1
  126. package/dist/http/routes/diagnostics.d.ts +3 -1
  127. package/dist/http/routes/diagnostics.js +30 -5
  128. package/dist/http/routes/mcp-reconnect.js +2 -2
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/notify-wake.js +2 -3
  131. package/dist/http/routes/principal-inbox.d.ts +3 -1
  132. package/dist/http/routes/principal-inbox.js +3 -2
  133. package/dist/http/routes/projects.d.ts +1 -0
  134. package/dist/http/routes/projects.js +5 -3
  135. package/dist/http/routes/retention-ops.d.ts +1 -1
  136. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  137. package/dist/http/routes/run-permission-mode.js +2 -2
  138. package/dist/http/routes/runs.d.ts +1 -1
  139. package/dist/http/routes/runs.js +33 -37
  140. package/dist/http/routes/session-memory-sync.js +4 -5
  141. package/dist/http/routes/session-sync.d.ts +24 -5
  142. package/dist/http/routes/session-sync.js +45 -37
  143. package/dist/http/routes/sessions-list.d.ts +1 -1
  144. package/dist/http/routes/sessions.d.ts +1 -1
  145. package/dist/http/routes/sessions.js +18 -44
  146. package/dist/http/routes/tasks.js +136 -101
  147. package/dist/http/routes/trace-usage.js +14 -14
  148. package/dist/http/routes/workflows.d.ts +1 -0
  149. package/dist/http/routes/workflows.js +20 -38
  150. package/dist/http/server.d.ts +12 -9
  151. package/dist/http/server.js +35 -6
  152. package/dist/http/stored-body-replay.d.ts +3 -24
  153. package/dist/http/stored-body-replay.js +2 -12
  154. package/dist/http/submit-ctx.d.ts +2 -2
  155. package/dist/http/submit-ctx.js +5 -5
  156. package/dist/http/submit-replay.d.ts +15 -2
  157. package/dist/http/submit-replay.js +10 -4
  158. package/dist/http/wire-types.d.ts +6 -3
  159. package/dist/index.d.ts +3 -1
  160. package/dist/index.js +1 -0
  161. package/dist/leader/endpoint.js +9 -8
  162. package/dist/leg-facts.d.ts +13 -2
  163. package/dist/leg-facts.js +2 -0
  164. package/dist/local-store-declaration.d.ts +54 -0
  165. package/dist/local-store-declaration.js +59 -0
  166. package/dist/mcp-assertion-header.d.ts +1 -1
  167. package/dist/mcp-assertion-header.js +1 -1
  168. package/dist/mcp-authorization-face.d.ts +18 -14
  169. package/dist/mcp-authorization-face.js +3 -2
  170. package/dist/memory-project-layer.js +1 -1
  171. package/dist/memory-scope.d.ts +4 -4
  172. package/dist/memory-scope.js +4 -4
  173. package/dist/memory-store-refusal.d.ts +1 -1
  174. package/dist/memory-sync-client.js +2 -2
  175. package/dist/model-context-window.d.ts +50 -11
  176. package/dist/model-context-window.js +20 -1
  177. package/dist/model-entry-refusal.d.ts +5 -4
  178. package/dist/model-entry-refusal.js +17 -3
  179. package/dist/model-reach-scope.d.ts +2 -1
  180. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  181. package/dist/observability/fail-open.d.ts +19 -7
  182. package/dist/observability/fail-open.js +19 -7
  183. package/dist/observability/logger.d.ts +7 -0
  184. package/dist/observability/logger.js +3 -1
  185. package/dist/observability/metrics.js +1 -1
  186. package/dist/observability/run-terminal.d.ts +1 -1
  187. package/dist/observability/run-terminal.js +1 -0
  188. package/dist/observability/secret-env-scrub.d.ts +1 -1
  189. package/dist/observability/secret-env-scrub.js +1 -1
  190. package/dist/org-delivery-gate.d.ts +31 -6
  191. package/dist/org-delivery-gate.js +7 -4
  192. package/dist/owner-faces.d.ts +30 -5
  193. package/dist/owner-faces.js +2 -0
  194. package/dist/park-row-commit.d.ts +67 -0
  195. package/dist/park-row-commit.js +76 -0
  196. package/dist/parked-decide.d.ts +9 -7
  197. package/dist/parked-decide.js +25 -6
  198. package/dist/peer-directory.d.ts +1 -1
  199. package/dist/peer-seat.d.ts +1 -1
  200. package/dist/peer-seat.js +52 -59
  201. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  202. package/dist/plugins/adoption-log-sql.js +19 -19
  203. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  204. package/dist/plugins/approval-ask-store-sql.js +3 -5
  205. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  206. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  207. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  208. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  209. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  210. package/dist/plugins/approval-exemption-store.js +28 -19
  211. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  212. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  213. package/dist/plugins/exec-path-law.d.ts +34 -0
  214. package/dist/plugins/exec-path-law.js +13 -0
  215. package/dist/plugins/file-run-store.d.ts +22 -9
  216. package/dist/plugins/file-run-store.js +51 -23
  217. package/dist/plugins/local-checkpoint-store.d.ts +8 -1
  218. package/dist/plugins/local-checkpoint-store.js +5 -1
  219. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  220. package/dist/plugins/local-data-root-admission.js +100 -0
  221. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  222. package/dist/plugins/local-engine-high-water.js +27 -22
  223. package/dist/plugins/local-session-store.d.ts +134 -49
  224. package/dist/plugins/local-session-store.js +149 -108
  225. package/dist/plugins/memory-run-store.d.ts +14 -9
  226. package/dist/plugins/memory-run-store.js +48 -22
  227. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  228. package/dist/plugins/one-time-migration-marker.js +7 -1
  229. package/dist/plugins/one-time-migrations.d.ts +110 -59
  230. package/dist/plugins/one-time-migrations.js +224 -136
  231. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  232. package/dist/plugins/org-delivery-quota-store.js +29 -14
  233. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  234. package/dist/plugins/permission-rule-store-sql.js +5 -9
  235. package/dist/plugins/pg-pool.d.ts +1 -1
  236. package/dist/plugins/pg-pool.js +14 -12
  237. package/dist/plugins/posix-shell-fs.js +4 -2
  238. package/dist/plugins/remote-env-adb.js +3 -2
  239. package/dist/plugins/remote-env-device.d.ts +3 -2
  240. package/dist/plugins/remote-env-device.js +8 -6
  241. package/dist/plugins/remote-env-e2b.d.ts +4 -8
  242. package/dist/plugins/remote-env-e2b.js +21 -15
  243. package/dist/plugins/remote-env-host.d.ts +51 -10
  244. package/dist/plugins/remote-env-host.js +97 -31
  245. package/dist/plugins/remote-env-k8s.js +7 -4
  246. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  247. package/dist/plugins/remote-env-local-docker.js +11 -25
  248. package/dist/plugins/remote-env-ssh.js +4 -3
  249. package/dist/plugins/remote-shell.d.ts +3 -1
  250. package/dist/plugins/remote-shell.js +3 -3
  251. package/dist/plugins/replica-registration.d.ts +35 -0
  252. package/dist/plugins/replica-registration.js +48 -0
  253. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  254. package/dist/plugins/run-store-sql.d.ts +27 -8
  255. package/dist/plugins/run-store-sql.js +46 -18
  256. package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
  257. package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
  258. package/dist/plugins/schema-consistency.d.ts +91 -5
  259. package/dist/plugins/schema-consistency.js +189 -7
  260. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  261. package/dist/plugins/schema-drift-sentence.js +13 -5
  262. package/dist/plugins/serving-requirement.d.ts +49 -0
  263. package/dist/plugins/serving-requirement.js +53 -0
  264. package/dist/plugins/session-placement.d.ts +5 -4
  265. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  266. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  267. package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
  268. package/dist/plugins/stop-then-start-sentence.js +51 -0
  269. package/dist/plugins/store-backend.d.ts +23 -9
  270. package/dist/plugins/store-backend.js +58 -24
  271. package/dist/plugins/store-contracts.d.ts +45 -5
  272. package/dist/plugins/store-contracts.js +19 -0
  273. package/dist/plugins/tidb-pool.d.ts +11 -6
  274. package/dist/plugins/tidb-pool.js +22 -12
  275. package/dist/plugins/workspace-custody.d.ts +44 -0
  276. package/dist/plugins/workspace-custody.js +47 -0
  277. package/dist/process-instance.d.ts +3 -0
  278. package/dist/process-instance.js +7 -0
  279. package/dist/project-context.d.ts +66 -0
  280. package/dist/project-context.js +70 -0
  281. package/dist/project-identity.d.ts +8 -7
  282. package/dist/project-identity.js +40 -17
  283. package/dist/project-layer-key.d.ts +13 -2
  284. package/dist/project-layer-key.js +9 -1
  285. package/dist/question.d.ts +9 -21
  286. package/dist/question.js +28 -19
  287. package/dist/read-deny-floor-notice.d.ts +36 -0
  288. package/dist/read-deny-floor-notice.js +40 -0
  289. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  290. package/dist/request-declaration-disposition.js +39 -0
  291. package/dist/run-cancel-card.d.ts +28 -0
  292. package/dist/run-cancel-card.js +20 -0
  293. package/dist/run-cancel-context.d.ts +10 -0
  294. package/dist/run-cancel-context.js +1 -0
  295. package/dist/run-local.js +28 -13
  296. package/dist/runs.d.ts +31 -7
  297. package/dist/runs.js +34 -16
  298. package/dist/security.d.ts +3 -3
  299. package/dist/server-secret-env.d.ts +34 -20
  300. package/dist/server-secret-env.js +61 -18
  301. package/dist/session-agent.d.ts +4 -12
  302. package/dist/session-agent.js +0 -6
  303. package/dist/session-children-retire.d.ts +6 -3
  304. package/dist/session-children-retire.js +2 -5
  305. package/dist/session-incarnation.d.ts +18 -4
  306. package/dist/session-incarnation.js +4 -1
  307. package/dist/session-notify-door.d.ts +4 -4
  308. package/dist/session-notify-door.js +5 -6
  309. package/dist/session-rules-seat.d.ts +4 -3
  310. package/dist/session-rules-seat.js +3 -11
  311. package/dist/session-sync.js +3 -3
  312. package/dist/session-workspace.d.ts +3 -3
  313. package/dist/store-admission.d.ts +33 -0
  314. package/dist/store-admission.js +49 -0
  315. package/dist/store-posture.d.ts +5 -0
  316. package/dist/store-posture.js +18 -1
  317. package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
  318. package/dist/stores-sql/checkpoint-store-sql.js +109 -23
  319. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  320. package/dist/stores-sql/file-history-store-sql.js +3 -5
  321. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  322. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  323. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  324. package/dist/task-a2a.d.ts +3 -8
  325. package/dist/task-a2a.js +25 -27
  326. package/dist/task-cwd.d.ts +27 -10
  327. package/dist/task-cwd.js +3 -0
  328. package/dist/task-mcp.d.ts +3 -3
  329. package/dist/task-mcp.js +1 -1
  330. package/dist/task-settings.d.ts +13 -0
  331. package/dist/task-settings.js +14 -7
  332. package/dist/terminal.d.ts +2 -0
  333. package/dist/terminal.js +21 -6
  334. package/dist/tool-approval.d.ts +50 -41
  335. package/dist/tool-approval.js +96 -73
  336. package/dist/trace/engine-notice-wire.d.ts +38 -21
  337. package/dist/trace/engine-notice-wire.js +25 -4
  338. package/dist/trace/ledger-sink.d.ts +3 -1
  339. package/dist/trace/project.d.ts +8 -5
  340. package/dist/trace/project.js +11 -9
  341. package/dist/trace/projection-table.d.ts +33 -2
  342. package/dist/trace/redact.d.ts +12 -0
  343. package/dist/trace/redact.js +5 -1
  344. package/dist/trace/self-minted-wire.d.ts +268 -56
  345. package/dist/trace/self-minted-wire.js +110 -40
  346. package/dist/trace/tool-card.d.ts +3 -0
  347. package/dist/trace/tool-card.js +24 -0
  348. package/dist/trace/tool-output-media.d.ts +2 -1
  349. package/dist/unattended-permission-mode.d.ts +1 -1
  350. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  351. package/dist/web-admin-client/apply-effective.js +22 -9
  352. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  353. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  354. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  355. package/docs/error-text-lexicon.json +317 -0
  356. package/package.json +4 -2
  357. package/scripts/embed-minimal.mjs +183 -0
  358. package/wire-changes/server@7.149.0.json +372 -0
  359. package/wire-changes/server@7.150.0.json +335 -0
  360. package/dist/plain-string-record.js +0 -70
@@ -3,18 +3,19 @@ import { createHash } from "node:crypto";
3
3
  import { uuidv7, resumeWithVerification, resumeCascade, CheckpointError, judgeCheckpointRow, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY } from "@sema-agent/core";
4
4
  import { decideParkedAgent, deliverToHoldingRun, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
5
5
  import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
6
- import { headReadOf } from "../session-incarnation.js";
6
+ import { runFactsOf, sealRouteDeps, sessionFactsReadOf } from "./owner-access.js";
7
7
  import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
8
8
  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";
9
9
  import { HttpError, checkpointPartsOf, decodeCheckpointScope, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
10
10
  import { keyAdmits } from "../owner-faces.js";
11
11
  import { personOf } from "../owner-brands.js";
12
12
  import { recoveredSubject } from "../owner-faces.js";
13
- import { beatRunHeartbeat, graftParkToolCallId, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort, voidHeldParkCard } from "../runs.js";
13
+ import { beatRunHeartbeat, graftParkToolCallId, heldParkOf, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort, voidHeldParkCard } from "../runs.js";
14
+ import { commitParkRow } from "../park-row-commit.js";
14
15
  import { atHostBoundary, failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane, outwardThrownMessage } from "../terminal.js";
15
16
  import { voidVerifierPauseCard } from "../verifier-pause-card.js";
16
- import { buildCancelledLedgerRow, CARD_SETTLED_BY_CANCEL_MESSAGE, createRunAbort, mintCancelledResult, withCardSettledByCancelMessage } from "../run-cancel-context.js";
17
- import { isTerminalRunStatus, runProvenElsewhere } from "../plugins/store-contracts.js";
17
+ import { buildCancelledLedgerRow, CANCELLED_CODE, CARD_SETTLED_BY_CANCEL_MESSAGE, createRunAbort, mintCancelledResult, withCardSettledByCancelMessage } from "../run-cancel-context.js";
18
+ import { createLegCtxClaim, isTerminalRunStatus } from "../plugins/store-contracts.js";
18
19
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
19
20
  import { recordResumeResourceWindow } from "../resource-window.js";
20
21
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
@@ -27,15 +28,19 @@ import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
27
28
  import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
28
29
  import { serviceVersion } from "../service-version.js";
29
30
  import { TERMINAL_PARK_GATE_KINDS, UnreadableCheckpointRowError } from "../plugins/checkpoint-store-face.js";
31
+ import { cancelSettlesCard } from "../run-cancel-card.js";
32
+ import { CARD_EXPIRE_OUTCOME, expireCardUnderStoredScope } from "../card-settle-stored-scope.js";
30
33
  import { withSubject } from "../observability/principal-context.js";
31
34
  import { withModelReachScope } from "../model-reach-scope.js";
35
+ import { approvalLegOriginOf } from "../approval-leg-origin.js";
32
36
  import { legFactsOf } from "../leg-facts.js";
37
+ import { compositionLegRoot } from "../task-cwd.js";
33
38
  import { createInstructionSelectionLeg } from "../instruction-selection.js";
34
39
  import { modelTextEventData, appendParkRow, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, createPromptManifestSeat } from "../trace/project.js";
35
40
  import { redactSecrets } from "../trace/redact.js";
36
41
  import { buildRunObjectivePreview } from "../user-authored-text.js";
37
42
  import { createForwardAppendFailedNote, createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
38
- import { registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
43
+ import { defaultEngineNoticeRouter, registerEngineNoticeLeg } from "../trace/engine-notice-wire.js";
39
44
  import { blockText, reasoningDeltaBytes, reasoningSegmentEventId } from "../trace/model-segment.js";
40
45
  import { errorText } from "../observability/err-text.js";
41
46
  import { recordFailOpen } from "../observability/fail-open.js";
@@ -52,15 +57,16 @@ import { createImagesLocal } from "./routes/images.js";
52
57
  import { createCapabilitiesLocal } from "./routes/capabilities.js";
53
58
  import { gatedSubject, explicitOperatorOk, runIdentityOf } from "./principal-gate.js";
54
59
  import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
55
- import { commitExecutionContext, isRootSessionGoneError, resolveExecutionContext, rootSessionAbsentAnswer, stampSpecRoot } from "./execution-context.js";
60
+ import { refusalAfterLostClaim } from "./claim-heartbeat.js";
61
+ import { CONTINUATION_CLAIM_FACE, ClaimFaultAnswer, commitExecutionContext, faultedAnswerOf, isRootSessionGoneError, resolveBeforeClaim, rootSessionAbsentAnswer, stampSpecRoot, withdrawClaim } from "./execution-context.js";
56
62
  import { timeClaim } from "./claim-latency.js";
57
63
  import { commitSessionInheritEnv } from "./session-inherit-env.js";
58
64
  import { ASK_USER_QUESTION_TOOL_NAME, isApprovalGateKind, isEngineQuestionRow } from "../approval-content-kind.js";
59
65
  import { deploymentListAsks } from "../approval.js";
60
66
  import { isParkLedgerEventType } from "../trace/ledger-events.js";
61
- import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnreadableAnswer, resumeCtxUnsealableAnswer, resumeStoreReadFailureOf } from "./resume-ctx-read.js";
67
+ import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnreadableAnswer, resumeCtxUnsealableAnswer, resumeStoreFailureOf } from "./resume-ctx-read.js";
62
68
  import { objectiveWithheldIn } from "./submit-ctx.js";
63
- import { buildCheckpointVersionKeys, resumeAssemblyRefusalAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
69
+ import { buildCheckpointVersionKeys, resumeAssemblyRefusalAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields } from "./checkpoint-ctx.js";
64
70
  import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
65
71
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
66
72
  import { restateResumeCtx } from "./resume-restatement.js";
@@ -204,6 +210,9 @@ function decisionRefusalCauseOf(e, gate) {
204
210
  function notResumableAnswer() {
205
211
  return { status: 409, body: refusalBody({ errorCode: "conflict.not_resumable", message: "run is not in a resumable (suspended) state (already resumed, decided, or expired)" }) };
206
212
  }
213
+ function resumeClaimFaultAnswerOf(e) {
214
+ return faultedAnswerOf(e, CONTINUATION_CLAIM_FACE.faulted);
215
+ }
207
216
  function approvalStaleAnswer(facts) {
208
217
  return {
209
218
  status: 409,
@@ -217,13 +226,41 @@ function approvalStaleAnswer(facts) {
217
226
  }),
218
227
  };
219
228
  }
220
- function rowRefusalAnswer(verdict, diagnostic, ctx) {
229
+ function rowRefusalAnswer(verdict, diagnostic, ctx, wayOut) {
221
230
  const refusal = new CheckpointError(verdict.code, diagnostic === undefined ? verdict.sentence : `${verdict.sentence} Diagnostic: ${diagnostic}`, verdict.reason === undefined ? undefined : { reason: verdict.reason });
222
- return checkpointRefusalAnswer(refusal, { gate: "policy_ask", retriable: resumeRefusalDisposition(refusal) === "retriable", versionKeys: refusalVersionKeys(refusal, ctx) });
231
+ return checkpointRefusalAnswer(refusal, { gate: "policy_ask", retriable: resumeRefusalDisposition(refusal) === "retriable", versionKeys: refusalVersionKeys(refusal, ctx), ...(wayOut !== undefined ? { wayOut } : {}) });
232
+ }
233
+ export async function rowWayOutOf(seat, kind) {
234
+ switch (kind) {
235
+ case "lacking":
236
+ return "newer_worker";
237
+ case "damaged":
238
+ try {
239
+ return (await cancelSettlesCard(seat.cs, seat.runs, seat.token)) ? "cancel_run" : undefined;
240
+ }
241
+ catch (e) {
242
+ seat.logger?.warn?.("row_way_out_probe_failed", { note: "the store could not answer whether cancel would settle this card — no wayOut offered", err: errorText(e) });
243
+ return undefined;
244
+ }
245
+ default: {
246
+ const unhandled = kind;
247
+ throw new Error(`row verdict ${String(unhandled)} has no way out`);
248
+ }
249
+ }
250
+ }
251
+ async function loadedRowWayOut(cp, seat) {
252
+ const verdict = judgeCheckpointRow(cp);
253
+ return verdict.kind === "ok" ? undefined : rowWayOutOf(seat, verdict.kind);
254
+ }
255
+ async function loadedRowRefusal(cp, seat, writer) {
256
+ const verdict = judgeCheckpointRow(cp);
257
+ if (verdict.kind === "ok")
258
+ return undefined;
259
+ return rowRefusalAnswer(verdict, undefined, verdict.code === "checkpoint.unsupported_version" ? await writer() : {}, await rowWayOutOf(seat, verdict.kind));
223
260
  }
224
- export function rowReadRefusalAnswer(e) {
261
+ export async function rowReadRefusalAnswer(e, seat) {
225
262
  if (e instanceof UnreadableCheckpointRowError && e.rowVerdict.kind !== "ok")
226
- return rowRefusalAnswer(e.rowVerdict, errorText(e), {});
263
+ return rowRefusalAnswer(e.rowVerdict, errorText(e), {}, seat === undefined ? undefined : await rowWayOutOf(seat, e.rowVerdict.kind));
227
264
  return { status: 409, body: refusalBody({ errorCode: "conflict.checkpoint_unreadable", message: errorText(e) }) };
228
265
  }
229
266
  function refusalVersionKeys(e, ctx) {
@@ -252,6 +289,7 @@ export function checkpointRefusalAnswer(e, at) {
252
289
  : {}),
253
290
  ...at.versionKeys,
254
291
  ...(e.code === "resume.constraint_rejected" ? acceptableFormFacts(detail) : {}),
292
+ ...(at.wayOut !== undefined ? { wayOut: at.wayOut } : {}),
255
293
  },
256
294
  }),
257
295
  }, at.retriable);
@@ -335,7 +373,7 @@ export function createResumeLegs(ctx) {
335
373
  return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
336
374
  }
337
375
  const cpPromise = cs.get(token);
338
- const runSessionPromise = cs.hostSessionOfCard(token).then((id) => (typeof id === "string" ? { kind: "owned", id } : { kind: "unowned" }), (e) => ({ kind: "failed", failure: resumeStoreReadFailureOf(e) }));
376
+ const runSessionPromise = cs.hostSessionOfCard(token).then((id) => (typeof id === "string" ? { kind: "owned", id } : { kind: "unowned" }), (e) => ({ kind: "failed", failure: resumeStoreFailureOf(e) }));
339
377
  const runCtxOf = async () => {
340
378
  const owner = await runSessionPromise;
341
379
  if (owner.kind === "unowned")
@@ -352,7 +390,6 @@ export function createResumeLegs(ctx) {
352
390
  const ctxPromise = runCtxOf();
353
391
  void ctxPromise.catch(() => { });
354
392
  const noApproval = { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
355
- const rowOwnRefusal = async (verdict) => rowRefusalAnswer(verdict, undefined, verdict.code === "checkpoint.unsupported_version" ? await ctxPromise.then((r) => (r !== undefined && "ctx" in r ? (r.ctx ?? {}) : {}), () => ({})) : {});
356
393
  let cp;
357
394
  try {
358
395
  cp = await cpPromise;
@@ -363,7 +400,7 @@ export function createResumeLegs(ctx) {
363
400
  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" });
364
401
  return noApproval;
365
402
  }
366
- return rowReadRefusalAnswer(e);
403
+ return rowReadRefusalAnswer(e, { cs, runs: deps.runStore, token, logger: deps.logger });
367
404
  }
368
405
  if (!cp) {
369
406
  if (!(await mayDiscloseRowError(cs, token, decider)))
@@ -374,9 +411,9 @@ export function createResumeLegs(ctx) {
374
411
  deps.logger?.warn?.("approval_decide_row_scope_mismatch", { sessionId, note: "loaded pending row is foreign to the verified caller — TOCTOU fold" });
375
412
  return { status: 404, body: refusalBody({ errorCode: "not_found.approval", message: "approval not found" }) };
376
413
  }
377
- const verdict = judgeCheckpointRow(cp);
378
- if (verdict.kind !== "ok")
379
- return rowOwnRefusal(verdict);
414
+ const rowOwn = await loadedRowRefusal(cp, { cs, runs: deps.runStore, token, logger: deps.logger }, () => ctxPromise.then((r) => (r !== undefined && "ctx" in r ? (r.ctx ?? {}) : {}), () => ({})));
415
+ if (rowOwn !== undefined)
416
+ return rowOwn;
380
417
  const decideGateKind = cp.gate?.kind;
381
418
  if (!isApprovalGateKind(decideGateKind)) {
382
419
  if (typeof decideGateKind !== "string" || decideGateKind === "") {
@@ -491,12 +528,14 @@ export function createResumeLegs(ctx) {
491
528
  ...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
492
529
  ...(deps.toolApproval ? { approverSeatFor: windowZeroSeatOf(deps.toolApproval) } : {}),
493
530
  execution: {
494
- resolve: async (root) => {
531
+ resolve: async (root, face) => {
495
532
  let ctx;
496
533
  try {
497
- ctx = await resolveExecutionContext(deps, { kind: "continuation", root, principal: checkpointPartsOf(cp.scope)?.person });
534
+ ctx = await resolveBeforeClaim(deps, { kind: "continuation", root, principal: checkpointPartsOf(cp.scope)?.person }, face);
498
535
  }
499
536
  catch (e) {
537
+ if (e instanceof ClaimFaultAnswer)
538
+ return { ok: false, gone: false, answer: e.answer };
500
539
  if (isRootSessionGoneError(e))
501
540
  return { ok: false, gone: true };
502
541
  if (e instanceof HttpError)
@@ -541,36 +580,59 @@ export function createResumeLegs(ctx) {
541
580
  const liveReceiptOf = async (held) => {
542
581
  if (held === null || held.fingerprint !== fingerprint)
543
582
  return undefined;
544
- const run = await runStore.getRun(held.deliveredTaskId);
583
+ const run = await runFactsOf(runStore, held.deliveredTaskId);
545
584
  return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId, NO_SETTLEMENT_EVIDENCE) : undefined;
546
585
  };
547
586
  const replay = await liveReceiptOf(await decisions.get(token));
548
587
  if (replay !== undefined)
549
588
  return replay;
550
589
  let recordFailure;
590
+ const answerOfRecordFailure = async (failed) => {
591
+ if (failed.kind === "faulted")
592
+ return faultedAnswerOf(failed.err, CONTINUATION_CLAIM_FACE.faulted);
593
+ try {
594
+ return await refusalAfterLostClaim(deps, runStore, plan.hostSessionId, failed.deliveredTaskId);
595
+ }
596
+ catch (e) {
597
+ return faultedAnswerOf(e, CONTINUATION_CLAIM_FACE.faulted);
598
+ }
599
+ };
551
600
  const delivery = {
552
601
  carried: [{ runId: plan.runId, token, outcome: plan.outcome }],
553
602
  commitBeforeLaunch: async (deliveredTaskId) => {
603
+ let recorded;
554
604
  try {
555
- const recorded = await decisions.record({ token, runId: plan.runId, hostSessionId: plan.hostSessionId, outcome: plan.outcome, fingerprint, deliveredTaskId, acceptedAtMs: Date.now() });
556
- if (!recorded)
557
- throw new Error(DELIVERY_NOT_RECORDED);
605
+ recorded = await decisions.record({ token, runId: plan.runId, hostSessionId: plan.hostSessionId, outcome: plan.outcome, fingerprint, deliveredTaskId, acceptedAtMs: Date.now() });
558
606
  }
559
607
  catch (err) {
560
- recordFailure = { err };
608
+ recordFailure = { kind: "faulted", err };
561
609
  throw err;
562
610
  }
611
+ if (!recorded) {
612
+ recordFailure = { kind: "not_recorded", deliveredTaskId };
613
+ throw new Error(DELIVERY_NOT_RECORDED);
614
+ }
563
615
  },
564
616
  };
565
617
  const hostToken = await cs
566
618
  .findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
567
619
  .catch(() => undefined);
568
620
  const admittedPayer = decodeCheckpointScope(cp.scope);
569
- const out = hostToken
570
- ? await resumeWake(plan.hostSessionId, plan.message, decider?.subject, req, acceptEarly, delivery, admittedPayer, restatement)
571
- : await startFreshHostTurn(plan.hostSessionId, plan.message.text, admittedPayer, req, delivery, plan.runId, restatement);
572
- if (recordFailure !== undefined)
573
- throw recordFailure.err;
621
+ let out;
622
+ try {
623
+ out = hostToken
624
+ ? await resumeWake(plan.hostSessionId, plan.message, decider?.subject, req, acceptEarly, delivery, admittedPayer, restatement)
625
+ : await startFreshHostTurn(plan.hostSessionId, plan.message.text, admittedPayer, req, delivery, plan.runId, restatement);
626
+ }
627
+ catch (err) {
628
+ const failed = recordFailure;
629
+ if (failed === undefined)
630
+ throw err;
631
+ return await answerOfRecordFailure(failed);
632
+ }
633
+ const failed = recordFailure;
634
+ if (failed !== undefined)
635
+ return await answerOfRecordFailure(failed);
574
636
  if (acceptedTaskIdOf(out) === undefined)
575
637
  return (await liveReceiptOf(await decisions.get(token))) ?? out;
576
638
  return out;
@@ -623,17 +685,18 @@ export function createResumeLegs(ctx) {
623
685
  const ONE_SHOT_SUBMIT_STRIP = Object.fromEntries(ONE_SHOT_SUBMIT_KEYS.map((k) => [k, undefined]));
624
686
  const resolveResumeSpec = async (ctx, req, auth, lease) => {
625
687
  const engineNotices = [];
626
- const execution = await resolveExecutionContext(deps, { kind: "continuation", root: auth.sessionId, principal: auth.subject === undefined ? undefined : personOf(recoveredSubject(auth.subject)) });
688
+ const execution = await resolveBeforeClaim(deps, { kind: "continuation", root: auth.sessionId, principal: auth.subject === undefined ? undefined : personOf(recoveredSubject(auth.subject)) }, CONTINUATION_CLAIM_FACE);
627
689
  const resolved = await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_SUBMIT_STRIP }, req, auth, {
628
690
  leg: "resume",
629
691
  onNotice: (n) => engineNotices.push(n),
630
692
  ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
693
+ ...(ctx.projectContext !== undefined ? { projectContext: ctx.projectContext } : {}),
631
694
  lease,
632
695
  execution,
633
696
  });
634
697
  const legFacts = legFactsOf(resolved);
635
698
  const runner = runnerFor(resolved);
636
- const spec = stampSpecRoot(resolved, execution, ctx.body);
699
+ const spec = stampSpecRoot(resolved, execution, { use: "session", requireExistingSession: ctx.body.requireExistingSession });
637
700
  return { spec, runner, legFacts, engineNotices, execution };
638
701
  };
639
702
  const refuseStoredBodyReplay = (sessionId, refusal) => {
@@ -651,6 +714,8 @@ export function createResumeLegs(ctx) {
651
714
  }
652
715
  catch (e) {
653
716
  lease.release();
717
+ if (e instanceof ClaimFaultAnswer)
718
+ return { ok: false, answer: e.answer };
654
719
  if (e instanceof HttpError) {
655
720
  return { ok: false, answer: resumeRefusalWithCard(resumeAssemblyRefusalAnswer(e.status, httpErrorRefusal(e)), true) };
656
721
  }
@@ -684,16 +749,24 @@ export function createResumeLegs(ctx) {
684
749
  const payer = legSubject === undefined ? undefined : personOf(legSubject);
685
750
  const heldObjective = objectiveWithheldIn(resumeCtx.ctx) ? undefined : resumeObjective;
686
751
  const reparkStatus = outcome.gate === "plan_review" ? "needs_review" : "suspended";
752
+ const commitResumePark = (runStore, runId, word, parkResult, cardToken) => {
753
+ if (heldLeg === undefined)
754
+ throw new Error(`resume leg: run ${runId} parked without the holding leg its claim handed back — the park cannot be committed`);
755
+ return commitParkRow({
756
+ runStore, taskId: runId, word, result: parkResult, card: { token: cardToken, sessionId }, cards: deps.checkpointStore, leg: "resume",
757
+ legId: heldLeg, staleWindowMs: deps.config?.runStaleSec === undefined ? undefined : deps.config.runStaleSec * 1000,
758
+ });
759
+ };
687
760
  const resumeCtxNotPersisted = (why) => {
688
761
  deps.metrics?.inc("resume_ctx_not_persisted_total");
689
762
  deps.logger?.warn?.("resume_ctx_not_persisted", { sessionId: resumeCtx.sessionId, gate: outcome.gate, error: why, 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 — its permission mode is never wider than this leg's, but if this leg restated its MCP / A2A peer tables (mcpServers / a2aPeers on the resume request), a later continuation that does not restate them runs with the previous tables again" });
690
763
  };
691
764
  const commitResumeCtx = () => Promise.resolve().then(async () => {
692
765
  const sealed = sealCheckpointCtx({ ...resumeCtx.ctx, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, resumeCtx.sessionId);
693
- const claim = claimedRow && taskId !== undefined && deps.runStore !== undefined ? { fence: deps.runStore, taskId, legHolds: (run) => !runProvenElsewhere(run, deps.instanceId) } : undefined;
766
+ const claim = heldLeg !== undefined && taskId !== undefined && deps.runStore !== undefined ? createLegCtxClaim(deps.runStore, taskId, heldLeg) : undefined;
694
767
  const landed = await deps.checkpointStore.putCtx(resumeCtx.sessionId, sealed, claim);
695
768
  if (landed === "claim_released")
696
- resumeCtxNotPersisted("claim_released: this run no longer holds the session claim (settled or reaped before the write-back landed) — nothing written");
769
+ resumeCtxNotPersisted("claim_released: this leg no longer holds the run (settled, reaped, or claimed again by a later leg before the write-back landed) — nothing written");
697
770
  }).catch((e) => resumeCtxNotPersisted(errorText(e)));
698
771
  const legKey = createHash("sha256").update(String(token)).digest("hex");
699
772
  const streamApprovalOn = resolveStreamApprovalGate({
@@ -720,16 +793,27 @@ export function createResumeLegs(ctx) {
720
793
  const heldDecisions = await heldWorkflowParkDecisions(deps, sessionId, args.workflowDecision?.carried);
721
794
  let taskId = await deps.runStore?.getActiveTaskId(sessionId);
722
795
  let freshRunClaimed = false;
796
+ let heldLeg;
797
+ let legClaim;
798
+ let engineHandedOff = false;
799
+ let restored;
800
+ const cancelledInClaimWindow = () => restored?.kind === "ended" && restored.row?.errorCode === CANCELLED_CODE;
723
801
  if (args.mintFreshRun && taskId && deps.runStore) {
724
802
  return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, turnActivity: readTurnActivityMs }, sessionId, taskId) };
725
803
  }
726
804
  if (!taskId && args.mintFreshRun && deps.runStore) {
727
805
  const freshId = uuidv7();
728
- const created = await timeClaim(deps.metrics, "run_claim", deps.runStore.createRun(freshId, sessionId, subject ?? null, deps.instanceId ?? "default", {
729
- source: args.mintFreshRun.source,
730
- identity: args.mintFreshRun.identity,
731
- objectivePreview: heldObjective === undefined ? null : buildRunObjectivePreview(heldObjective),
732
- }));
806
+ let created;
807
+ try {
808
+ created = await timeClaim(deps.metrics, "run_claim", deps.runStore.createRun(freshId, sessionId, subject ?? null, deps.instanceId ?? "default", {
809
+ source: args.mintFreshRun.source,
810
+ identity: args.mintFreshRun.identity,
811
+ objectivePreview: heldObjective === undefined ? null : buildRunObjectivePreview(heldObjective),
812
+ }));
813
+ }
814
+ catch (e) {
815
+ return resumeClaimFaultAnswerOf(e);
816
+ }
733
817
  if (created.existing) {
734
818
  throw new Error(`wake fresh run: createRun observed an existing row for the freshly minted taskId ${freshId} (no idempotency key was sent) — refusing to treat it as a busy session`);
735
819
  }
@@ -738,11 +822,20 @@ export function createResumeLegs(ctx) {
738
822
  if (!created.ok) {
739
823
  return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, turnActivity: readTurnActivityMs }, sessionId, created.activeTaskId) };
740
824
  }
741
- const placementReject = await commitExecutionContext(deps, { kind: "run", runStore: deps.runStore, taskId: freshId }, execution);
825
+ legClaim = { kind: "run", runStore: deps.runStore, taskId: freshId, legId: created.legId };
826
+ let placementReject;
827
+ try {
828
+ const refusal = await commitExecutionContext(deps, legClaim, execution);
829
+ placementReject = refusal === null ? null : CONTINUATION_CLAIM_FACE.refused(refusal);
830
+ }
831
+ catch (e) {
832
+ placementReject = resumeClaimFaultAnswerOf(e);
833
+ }
742
834
  if (placementReject)
743
835
  return placementReject;
744
836
  taskId = freshId;
745
837
  freshRunClaimed = true;
838
+ heldLeg = created.legId;
746
839
  }
747
840
  const fleetPub = taskId && deps.fleetBus
748
841
  ? fleetRunPublisher(deps.fleetBus, { runId: taskId, scope: fleetScope, rootTaskId: taskId, sessionId, ...fleetRunLabels(heldObjective) })
@@ -751,12 +844,12 @@ export function createResumeLegs(ctx) {
751
844
  if (deps.toolApproval && taskId) {
752
845
  const toolApproval = deps.toolApproval;
753
846
  resumeTaskConfig.onAsk = approvalLeg.unattended
754
- ? toolApproval.boundUnattendedAsk({ owner: subject ?? null, taskId, sessionId, executionLane: execution.lane }, approvalLeg.unattended)
847
+ ? toolApproval.boundUnattendedAsk({ owner: subject ?? null, taskId, sessionId, ...approvalLegOriginOf(execution) }, approvalLeg.unattended)
755
848
  : toolApproval.boundAsk({
756
849
  owner: subject ?? null,
757
850
  taskId,
758
851
  sessionId,
759
- executionLane: execution.lane,
852
+ ...approvalLegOriginOf(execution),
760
853
  ...(approvalLeg.active ? { legKey, ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}) } : {}),
761
854
  });
762
855
  }
@@ -791,12 +884,9 @@ export function createResumeLegs(ctx) {
791
884
  if (!cs2)
792
885
  return false;
793
886
  try {
794
- const live = await cs2.get(token);
795
- if (live?.status !== "pending")
796
- return false;
797
- if (await cs2.expire(token, live.scope))
887
+ if ((await expireCardUnderStoredScope(cs2, token)) === CARD_EXPIRE_OUTCOME.expired)
798
888
  return true;
799
- deps.logger?.warn?.("resume_cancel_checkpoint_settle_lost", { taskId, sessionId });
889
+ deps.logger?.info?.("resume_cancel_checkpoint_not_settled", { taskId, sessionId });
800
890
  return false;
801
891
  }
802
892
  catch (cancelSettleErr) {
@@ -826,19 +916,45 @@ export function createResumeLegs(ctx) {
826
916
  let cancelLedgerAppend;
827
917
  const preemptCtrl = new AbortController();
828
918
  if (taskId && deps.runStore) {
829
- claimedRow = freshRunClaimed ? true : await deps.runStore.markResuming(taskId, deps.instanceId ?? "default");
919
+ if (freshRunClaimed)
920
+ claimedRow = true;
921
+ else {
922
+ try {
923
+ heldLeg = await deps.runStore.markResuming(taskId, deps.instanceId ?? "default");
924
+ }
925
+ catch (e) {
926
+ return resumeClaimFaultAnswerOf(e);
927
+ }
928
+ claimedRow = heldLeg !== undefined;
929
+ }
830
930
  if (!claimedRow)
831
931
  return notResumableAnswer();
932
+ inflightRuns.set(taskId, cancelCtrl);
832
933
  if (!freshRunClaimed) {
833
- const placementReject = await commitExecutionContext(deps, { kind: "run", runStore: deps.runStore, taskId }, execution);
834
- if (placementReject)
835
- return placementReject;
934
+ const rs0 = deps.runStore;
935
+ const claimedTask = taskId;
936
+ const restoreClaim = async () => {
937
+ restored = await commitResumePark(rs0, claimedTask, reparkStatus, null, token);
938
+ };
939
+ legClaim = { kind: "resumed", runStore: rs0, taskId: claimedTask, restore: restoreClaim };
940
+ let placementReject;
941
+ try {
942
+ const refusal = await commitExecutionContext(deps, legClaim, execution);
943
+ placementReject = refusal === null ? null : CONTINUATION_CLAIM_FACE.refused(refusal);
944
+ }
945
+ catch (e) {
946
+ placementReject = resumeClaimFaultAnswerOf(e);
947
+ }
948
+ if (placementReject) {
949
+ if (inflightRuns.get(claimedTask) === cancelCtrl)
950
+ inflightRuns.delete(claimedTask);
951
+ return cancelledInClaimWindow() ? { status: 200, body: { taskId: claimedTask, sessionId, status: "failed", errorCode: "cancelled" } } : placementReject;
952
+ }
836
953
  }
837
954
  markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, subject);
838
955
  fleetPub?.onStart();
839
956
  const rs = deps.runStore;
840
957
  const owner = subject ?? null;
841
- inflightRuns.set(taskId, cancelCtrl);
842
958
  preemptableRuns.set(taskId, preemptCtrl);
843
959
  resumeHeartbeat = setInterval(() => {
844
960
  beatRunHeartbeat(rs, taskId, owner, () => cancelCtrl.abort("stall_verdict"), () => undefined);
@@ -867,6 +983,10 @@ export function createResumeLegs(ctx) {
867
983
  if (args.onResumeCommitted)
868
984
  await args.onResumeCommitted();
869
985
  let unregisterEngineNotice;
986
+ const handToEngine = (start) => {
987
+ engineHandedOff = true;
988
+ return start();
989
+ };
870
990
  try {
871
991
  const instructionLeg = createInstructionSelectionLeg();
872
992
  const legWiringSections = () => ({ instructions: instructionLeg.segment(), skills: args.legFacts?.skills });
@@ -885,6 +1005,21 @@ export function createResumeLegs(ctx) {
885
1005
  return write;
886
1006
  }, { route: "resume-open", connection: "durable-append", log: (m, x) => deps.logger?.info?.(m, x) });
887
1007
  }
1008
+ const openEngineNoticePort = async (append) => {
1009
+ unregisterEngineNotice = registerEngineNoticeLeg({
1010
+ sessionId,
1011
+ ...(taskId !== undefined ? { taskId } : {}),
1012
+ durable: (row) => append("engine_notice", row).then(() => true, (err) => {
1013
+ recordFailOpen("server.engine-notice.durable-append-failed", `leg=resume code=${row.code} task=${taskId ?? ""}`);
1014
+ deps.logger?.warn?.("engine_notice_append_failed", { taskId, noticeCode: row.code, sessionId: row.sessionId, err: err instanceof Error ? err.message : String(err) });
1015
+ return false;
1016
+ }),
1017
+ });
1018
+ for (const notice of args.engineNotices ?? [])
1019
+ await defaultEngineNoticeRouter.routeConfirmed(notice);
1020
+ if (deps.userInputLedger && taskId)
1021
+ await deps.userInputLedger.announceUndrained({ sessionId, taskId });
1022
+ };
888
1023
  if (composition !== undefined) {
889
1024
  const rsV = deps.runStore;
890
1025
  const vSeqBase = rsV && taskId ? await rsV.maxSeq(taskId) : 0;
@@ -897,9 +1032,11 @@ export function createResumeLegs(ctx) {
897
1032
  });
898
1033
  const vAppend = (type, data) => verifySink.append(type, data);
899
1034
  cancelLedgerAppend = vAppend;
1035
+ await openEngineNoticePort(vAppend);
900
1036
  const verifyTailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
901
1037
  const verifyInternals = {
902
1038
  ...launchInternals,
1039
+ ...compositionLegRoot(args.legFacts?.registeredCwd),
903
1040
  ...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
904
1041
  onForwardEvent: (e) => {
905
1042
  fleetPub?.onForwardEvent(e);
@@ -949,10 +1086,10 @@ export function createResumeLegs(ctx) {
949
1086
  let vrEngine;
950
1087
  switch (composition.entry) {
951
1088
  case "verify":
952
- vrEngine = await resumeWithVerification(legRunner, token, outcome, legSpec, resumeObjective, composition.rounds, verifyInternals);
1089
+ vrEngine = await handToEngine(() => resumeWithVerification(legRunner, token, outcome, legSpec, resumeObjective, composition.rounds, verifyInternals));
953
1090
  break;
954
1091
  case "cascade":
955
- vrEngine = await resumeCascade(legRunner, token, outcome, stampSpecRoot({ ...legSpec, ...composition.objectiveInputs, objective: resumeObjective }, execution, resumeCtx.ctx.body), composition.config, composition.parkedLeg, verifyInternals);
1092
+ vrEngine = await handToEngine(() => resumeCascade(legRunner, token, outcome, stampSpecRoot({ ...legSpec, ...composition.objectiveInputs, objective: resumeObjective }, execution, { use: "ladder_host" }), composition.config, composition.parkedLeg, verifyInternals));
956
1093
  break;
957
1094
  default: {
958
1095
  const unhandled = composition;
@@ -989,8 +1126,9 @@ export function createResumeLegs(ctx) {
989
1126
  }
990
1127
  }
991
1128
  catch (e) {
992
- if (!vrReopened)
1129
+ if (!reSuspended && !vrReopened)
993
1130
  throw e;
1131
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=resume-park-projection ${errorText(e)}`);
994
1132
  }
995
1133
  }
996
1134
  if (reSuspended)
@@ -1009,21 +1147,11 @@ export function createResumeLegs(ctx) {
1009
1147
  const serialAppend = createSerialLedgerAppend((s, type, data) => rs.appendEvent(taskId, s, type, data), startSeq);
1010
1148
  const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
1011
1149
  cancelLedgerAppend = append;
1012
- unregisterEngineNotice = registerEngineNoticeLeg({
1013
- sessionId,
1014
- ...(args.engineNotices !== undefined && args.engineNotices.length > 0 ? { pending: [...args.engineNotices] } : {}),
1015
- durable: (row) => append("engine_notice", row).then(() => true, (err) => {
1016
- recordFailOpen("server.engine-notice.durable-append-failed", `leg=resume code=${row.code} task=${taskId ?? ""}`);
1017
- deps.logger?.warn?.("engine_notice_append_failed", { taskId, noticeCode: row.code, sessionId: row.sessionId, err: err instanceof Error ? err.message : String(err) });
1018
- return false;
1019
- }),
1020
- });
1021
- if (deps.userInputLedger && taskId)
1022
- await deps.userInputLedger.announceUndrained({ sessionId, taskId });
1150
+ await openEngineNoticePort(append);
1023
1151
  const anchorOwner = subject ?? null;
1024
- const headOf = headReadOf(deps.sessionStorage);
1025
- const captureTurnAnchor = deps.resumeAnchorStore && headOf
1026
- ? async (eventId) => { const leaf = (await headOf(sessionId))?.leafId; if (leaf)
1152
+ const sessionFactsOf = sessionFactsReadOf(deps.sessionStorage);
1153
+ const captureTurnAnchor = deps.resumeAnchorStore && sessionFactsOf
1154
+ ? async (eventId) => { const leaf = (await sessionFactsOf(sessionId))?.leafId; if (leaf)
1027
1155
  await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner); }
1028
1156
  : undefined;
1029
1157
  const captureUserMessageAnchor = deps.resumeAnchorStore && taskId && freshRunClaimed
@@ -1047,7 +1175,7 @@ export function createResumeLegs(ctx) {
1047
1175
  resumeLegLive = true;
1048
1176
  const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
1049
1177
  const resumeWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), args.legFacts?.sessionWitness);
1050
- const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
1178
+ const stream = await handToEngine(() => legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
1051
1179
  ...launchInternals,
1052
1180
  onForwardEvent: (e) => {
1053
1181
  fleetPub?.onForwardEvent(e);
@@ -1102,7 +1230,7 @@ export function createResumeLegs(ctx) {
1102
1230
  },
1103
1231
  }
1104
1232
  : {}),
1105
- });
1233
+ }));
1106
1234
  await commitResumeCtx();
1107
1235
  resumeStreamRef = stream;
1108
1236
  if (taskId)
@@ -1260,11 +1388,7 @@ export function createResumeLegs(ctx) {
1260
1388
  const settledLeg = await reopenedAfterLeg(r, causeAtDone !== undefined);
1261
1389
  if (settledLeg.reopened) {
1262
1390
  reopenConfirmedPark = true;
1263
- try {
1264
- await appendParkRow(append, { modelUsage: deps.modelUsage, taskId }, reparkStatus, r, failureCodeOf(r.terminal) ?? null);
1265
- }
1266
- catch {
1267
- }
1391
+ await appendParkRow(append, { modelUsage: deps.modelUsage, taskId }, reparkStatus, r, failureCodeOf(r.terminal) ?? null);
1268
1392
  return r;
1269
1393
  }
1270
1394
  const safe = await attachModelUsage(sealResultForExit(settledLeg.leg), { append, getEvents: (id, a) => rs.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
@@ -1283,7 +1407,7 @@ export function createResumeLegs(ctx) {
1283
1407
  owner: subject ?? null,
1284
1408
  abortSignal: cancelCtrl.signal,
1285
1409
  sessionId,
1286
- executionLane: execution.lane,
1410
+ ...approvalLegOriginOf(execution),
1287
1411
  family: "approval_request",
1288
1412
  transport: hitlTransport,
1289
1413
  legKey,
@@ -1293,9 +1417,12 @@ export function createResumeLegs(ctx) {
1293
1417
  const withElicitation = () => deps.elicitation && taskId
1294
1418
  ? deps.elicitation.runWithContext({ taskId, owner: subject ?? null, transport: hitlTransport, abortSignal: cancelCtrl.signal }, withApproval)
1295
1419
  : withApproval();
1420
+ const withQuestion = () => deps.question && taskId
1421
+ ? deps.question.runWithContext({ taskId, owner: subject ?? null, transport: hitlTransport, abortSignal: cancelCtrl.signal }, withElicitation)
1422
+ : withElicitation();
1296
1423
  return await (deps.sendUserFile && taskId
1297
- ? deps.sendUserFile.runWithContext({ taskId, emit: async (frame) => { const { type, ...rest } = frame; await append(type, rest); } }, withElicitation)
1298
- : withElicitation());
1424
+ ? deps.sendUserFile.runWithContext({ taskId, emit: async (frame) => { const { type, ...rest } = frame; await append(type, rest); } }, withQuestion)
1425
+ : withQuestion());
1299
1426
  })));
1300
1427
  }
1301
1428
  catch (e) {
@@ -1315,6 +1442,7 @@ export function createResumeLegs(ctx) {
1315
1442
  return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
1316
1443
  }
1317
1444
  const retriable = disposition === "retriable";
1445
+ let reparkEnded = false;
1318
1446
  if (claimedRow && taskId && deps.runStore) {
1319
1447
  if (retriable && freshRunClaimed) {
1320
1448
  const c = { taskId, sessionId, terminal: { kind: "failed", code: e.code, message: e.message }, stats: { turns: 0, tokens: 0, usageMissing: true } };
@@ -1324,15 +1452,17 @@ export function createResumeLegs(ctx) {
1324
1452
  settleFleet("failed");
1325
1453
  }
1326
1454
  else if (retriable) {
1327
- await (reparkStatus === "needs_review" ? deps.runStore.setNeedsReview(taskId, null) : deps.runStore.setSuspended(taskId, null)).catch(() => undefined);
1328
- settleFleet(reparkStatus);
1455
+ const parked = await commitResumePark(deps.runStore, taskId, reparkStatus, null, token);
1456
+ reparkEnded = parked.kind === "ended";
1457
+ settleFleet(reparkEnded ? "failed" : reparkStatus);
1329
1458
  }
1330
1459
  else {
1331
1460
  await deps.runStore.setTerminal(taskId, "failed", null, e.message).catch(() => undefined);
1332
1461
  settleFleet("failed");
1333
1462
  }
1334
1463
  }
1335
- return checkpointRefusalAnswer(e, { gate: outcome.gate, retriable, ...(taskId !== undefined ? { taskId } : {}), versionKeys: refusalVersionKeys(e, resumeCtx.ctx) });
1464
+ const answerRetriable = retriable && !reparkEnded;
1465
+ return checkpointRefusalAnswer(e, { gate: outcome.gate, retriable: answerRetriable, ...(taskId !== undefined ? { taskId } : {}), versionKeys: refusalVersionKeys(e, resumeCtx.ctx), ...(answerRetriable && args.rowWayOut !== undefined ? { wayOut: args.rowWayOut } : {}) });
1336
1466
  }
1337
1467
  const thrownCause = cancelCtrl.cause();
1338
1468
  if (thrownCause !== undefined) {
@@ -1345,12 +1475,28 @@ export function createResumeLegs(ctx) {
1345
1475
  settleFleet("failed");
1346
1476
  return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
1347
1477
  }
1348
- if (claimedRow && taskId && deps.runStore) {
1349
- await voidHeldParkCard(heldPark, sessionId, deps.checkpointStore, "resume");
1350
- await deps.runStore.setTerminal(taskId, "failed", null, outwardThrownMessage(e, errorText(e))).catch(() => undefined);
1478
+ if (!engineHandedOff && legClaim !== undefined) {
1479
+ const claim = legClaim;
1480
+ await withdrawClaim(deps, claim, `the resume could not be handed to the engine: ${outwardThrownMessage(e, errorText(e))}`, "faulted");
1481
+ const parkedAgain = claim.kind === "resumed" && restored?.kind !== "ended";
1482
+ settleFleet(parkedAgain ? reparkStatus : "failed");
1483
+ if (cancelledInClaimWindow())
1484
+ return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
1485
+ return resumeClaimFaultAnswerOf(e);
1486
+ }
1487
+ const heldParkWord = heldPark === undefined ? undefined : heldParkOf(heldPark);
1488
+ if (!(claimedRow && taskId && deps.runStore && heldPark !== undefined && heldParkWord !== undefined)) {
1489
+ if (claimedRow && taskId && deps.runStore) {
1490
+ await voidHeldParkCard(heldPark, sessionId, deps.checkpointStore, "resume");
1491
+ await deps.runStore.setTerminal(taskId, "failed", null, outwardThrownMessage(e, errorText(e))).catch(() => undefined);
1492
+ }
1493
+ settleFleet("failed");
1494
+ throw e;
1351
1495
  }
1352
- settleFleet("failed");
1353
- throw e;
1496
+ recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=resume-park-held ${errorText(e)}`);
1497
+ if (cancelLedgerAppend !== undefined)
1498
+ await appendParkRow(cancelLedgerAppend, { modelUsage: deps.modelUsage, taskId }, heldParkWord.word, await graftParkToolCallId(heldPark, deps.checkpointStore));
1499
+ result = heldPark;
1354
1500
  }
1355
1501
  finally {
1356
1502
  unregisterEngineNotice?.();
@@ -1375,25 +1521,32 @@ export function createResumeLegs(ctx) {
1375
1521
  const checkpointReopened = reopenConfirmedPark;
1376
1522
  const sealed = sealResultForExit(result);
1377
1523
  const resumePlane = outwardPlane(sealed.terminal);
1524
+ let parkEnded;
1378
1525
  if (taskId && deps.runStore) {
1379
- if (checkpointReopened && reparkStatus === "needs_review")
1380
- await deps.runStore.setNeedsReview(taskId, null);
1381
- else if (resumePlane.status === "suspended" || checkpointReopened)
1382
- await deps.runStore.setSuspended(taskId, sealed.terminal.kind === "paused" ? sealed : null);
1383
- else if (resumePlane.status === "needs_review")
1384
- await deps.runStore.setNeedsReview(taskId, sealed);
1526
+ const park = checkpointReopened && reparkStatus === "needs_review" ? { word: "needs_review", result: null }
1527
+ : resumePlane.status === "suspended" || checkpointReopened ? { word: "suspended", result: sealed.terminal.kind === "paused" ? sealed : null }
1528
+ : resumePlane.status === "needs_review" ? { word: "needs_review", result: sealed }
1529
+ : undefined;
1530
+ if (park !== undefined) {
1531
+ const parked = await commitResumePark(deps.runStore, taskId, park.word, park.result, heldPark?.terminal.kind === "paused" ? heldPark.terminal.token : token);
1532
+ if (parked.kind === "ended")
1533
+ parkEnded = { row: parked.row };
1534
+ }
1385
1535
  else
1386
1536
  await deps.runStore.setTerminal(taskId, resumePlane.status, sealed, resumePlane.errorMessage ?? null);
1387
1537
  }
1388
- settleFleet(checkpointReopened ? reparkStatus : resumePlane.status, fleetRunResiduals(sealed, sealed ? failureCodeOf(sealed.terminal) : undefined));
1538
+ settleFleet(parkEnded !== undefined ? "failed" : checkpointReopened ? reparkStatus : resumePlane.status, fleetRunResiduals(sealed, sealed ? failureCodeOf(sealed.terminal) : undefined));
1539
+ const answered = parkEnded !== undefined
1540
+ ? { status: parkEnded.row?.status ?? "failed", errorCode: parkEnded.row?.errorCode ?? undefined, errorMessage: parkEnded.row?.error ?? undefined }
1541
+ : { status: resumePlane.status, errorCode: resumePlane.errorCode, errorMessage: resumePlane.errorMessage };
1389
1542
  return {
1390
1543
  status: 200,
1391
1544
  body: {
1392
- taskId, sessionId, status: resumePlane.status,
1545
+ taskId, sessionId, status: answered.status,
1393
1546
  bindingEnforced: true,
1394
- ...(resumePlane.errorCode ? { errorCode: resumePlane.errorCode } : {}),
1395
- ...(resumePlane.errorMessage ? { errorMessage: redactErrorMessage(resumePlane.errorMessage) ?? "" } : {}),
1396
- ...(checkpointReopened ? { retriable: true } : {}),
1547
+ ...(answered.errorCode ? { errorCode: answered.errorCode } : {}),
1548
+ ...(answered.errorMessage ? { errorMessage: redactErrorMessage(answered.errorMessage) ?? "" } : {}),
1549
+ ...(checkpointReopened && parkEnded === undefined ? { retriable: true } : {}),
1397
1550
  ...buildSettlementEvidence({ at: "engine_terminal", commitment: args.commitment }),
1398
1551
  },
1399
1552
  };
@@ -1446,7 +1599,7 @@ export function createResumeLegs(ctx) {
1446
1599
  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" });
1447
1600
  return noSuspension;
1448
1601
  }
1449
- return rowReadRefusalAnswer(e);
1602
+ return rowReadRefusalAnswer(e, { cs, runs: deps.runStore, token, logger: deps.logger });
1450
1603
  }
1451
1604
  if (!cp) {
1452
1605
  if (!(await mayDiscloseRowError(cs, token, decider)))
@@ -1482,7 +1635,8 @@ export function createResumeLegs(ctx) {
1482
1635
  if (composition?.entry === "cascade_unlinked")
1483
1636
  return compositionUnlinkedAnswer();
1484
1637
  const selectedGate = durableRowIdentity("decision-receipt", cp.checkpointId, token);
1485
- return await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId, subject: auth.subject, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, commitment: { kind: "gate_resolved", ...selectedGate }, composition, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1638
+ const rowWayOut = await loadedRowWayOut(cp, { cs, runs: deps.runStore, token, logger: deps.logger });
1639
+ return await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId, subject: auth.subject, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, commitment: { kind: "gate_resolved", ...selectedGate }, composition, resumeCtx: { sessionId, ctx }, ...(rowWayOut !== undefined ? { rowWayOut } : {}), ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1486
1640
  }
1487
1641
  finally {
1488
1642
  resolved.peerLease.release();
@@ -1532,6 +1686,7 @@ export function createResumeLegs(ctx) {
1532
1686
  source: "workflow_parked_decide",
1533
1687
  identity: runIdentityOf(req, deps.config, subject ?? null),
1534
1688
  workflowDecision,
1689
+ face: CONTINUATION_CLAIM_FACE,
1535
1690
  });
1536
1691
  if (out.existing) {
1537
1692
  throw new Error(`decide host turn: createRun observed an existing row ${out.existing.taskId} for a freshly minted taskId (no idempotency key was sent)`);
@@ -1567,7 +1722,7 @@ export function createResumeLegs(ctx) {
1567
1722
  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" });
1568
1723
  return { status: 404, body: refusalBody({ errorCode: "not_found.parked_checkpoint", message: "no parked checkpoint for this session (nothing to wake)" }) };
1569
1724
  }
1570
- return rowReadRefusalAnswer(e);
1725
+ return rowReadRefusalAnswer(e, undefined);
1571
1726
  }
1572
1727
  if (!cp) {
1573
1728
  if (!(await mayDiscloseRowError(cs, token, who))) {
@@ -1672,7 +1827,7 @@ export function createResumeLegs(ctx) {
1672
1827
  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" });
1673
1828
  return refuse(noPending);
1674
1829
  }
1675
- return refuse(rowReadRefusalAnswer(e));
1830
+ return refuse(await rowReadRefusalAnswer(e, { cs, runs: deps.runStore, token, logger: deps.logger }));
1676
1831
  }
1677
1832
  if (!cp) {
1678
1833
  log404("checkpoint-missing");
@@ -1726,7 +1881,7 @@ export function createResumeLegs(ctx) {
1726
1881
  throw new Error("drivePlanReview: permissionModeAfter was passed for a task whose stored permission mode is not \"plan\" — the caller must refuse this before any state changes (see the plan_review route's pre-nonce gate)");
1727
1882
  }
1728
1883
  const approvedMode = planReviewDecisionExitsPlan(decision) && effMode === "plan" ? (permissionModeAfter ?? "default") : undefined;
1729
- const rebuildCtx = approvedMode === undefined ? ctx : withPermissionModeRewritten(ctx, { ...ctx.body, permissionMode: approvedMode });
1884
+ const rebuildCtx = approvedMode === undefined ? ctx : { ...ctx, body: { ...ctx.body, permissionMode: approvedMode } };
1730
1885
  const resolved = await resolveResumeSpecOrBlocked(rebuildCtx, req, auth);
1731
1886
  if (!resolved.ok)
1732
1887
  return resolved.answer;
@@ -1744,7 +1899,8 @@ export function createResumeLegs(ctx) {
1744
1899
  hostDecision,
1745
1900
  };
1746
1901
  const selectedGate = durableRowIdentity("decision-receipt", cp.checkpointId, token);
1747
- const out = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId, subject: auth.subject, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...selectedGate }, composition, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1902
+ const rowWayOut = await loadedRowWayOut(cp, { cs: deps.checkpointStore, runs: deps.runStore, token, logger: deps.logger });
1903
+ const out = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, execution: resolved.execution, runner: resolved.runner, legFacts: resolved.legFacts, token, sessionId, subject: auth.subject, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...selectedGate }, composition, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(rowWayOut !== undefined ? { rowWayOut } : {}), ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1748
1904
  return out;
1749
1905
  }
1750
1906
  finally {
@@ -1755,10 +1911,10 @@ export function createResumeLegs(ctx) {
1755
1911
  activeTaskIdOf: async (sessionId) => deps.runStore?.getActiveTaskId?.(sessionId),
1756
1912
  liveRuns: steerableRuns,
1757
1913
  inbox: deps.workflowCompletionInbox,
1758
- headOf: headReadOf(deps.sessionStorage),
1914
+ sessionStorage: deps.sessionStorage,
1759
1915
  });
1760
1916
  const routeCtxBase = {
1761
- deps,
1917
+ deps: sealRouteDeps(deps),
1762
1918
  registry: { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters },
1763
1919
  helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, admitRun, safeDecode },
1764
1920
  local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },