@sema-agent/server 7.150.0 → 7.151.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 (341) hide show
  1. package/MIGRATION.md +185 -0
  2. package/USAGE.md +53 -25
  3. package/deploy/sema-up/README.md +7 -1
  4. package/deploy/sema-up/chart/Chart.yaml +1 -1
  5. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  6. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  8. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  9. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  10. package/deploy/sema-up/chart/values.yaml +8 -1
  11. package/deploy/sema-up/cluster-up.sh +1 -0
  12. package/deploy/sema-up/kube-up.sh +1 -1
  13. package/deploy/sema-up/lib/flagshape.sh +1 -1
  14. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  16. package/dist/adoption/import.js +3 -3
  17. package/dist/approval-ask-machine.d.ts +1 -1
  18. package/dist/approval-card.d.ts +2 -2
  19. package/dist/approval-gate-currency.d.ts +1 -1
  20. package/dist/auth-bridge.d.ts +20 -0
  21. package/dist/auth-bridge.js +9 -0
  22. package/dist/background-session-reap.d.ts +1 -1
  23. package/dist/bake-runner/main.d.ts +4 -2
  24. package/dist/bind-host.d.ts +2 -3
  25. package/dist/bind-host.js +2 -4
  26. package/dist/boot/budget-tracing.js +1 -1
  27. package/dist/boot/engine-lease.d.ts +5 -0
  28. package/dist/boot/engine-lease.js +1 -0
  29. package/dist/boot/execution-env.d.ts +8 -14
  30. package/dist/boot/execution-env.js +54 -17
  31. package/dist/boot/execution-lane.js +32 -5
  32. package/dist/boot/late-binds.d.ts +8 -4
  33. package/dist/boot/late-binds.js +7 -1
  34. package/dist/boot/memory-consolidation.d.ts +3 -2
  35. package/dist/boot/memory-consolidation.js +6 -6
  36. package/dist/boot/reapers.d.ts +5 -0
  37. package/dist/boot/reapers.js +12 -1
  38. package/dist/boot/resolve-spec.d.ts +3 -3
  39. package/dist/boot/resolve-spec.js +31 -22
  40. package/dist/boot/runner-deps.d.ts +9 -2
  41. package/dist/boot/runner-deps.js +3 -3
  42. package/dist/boot/session-age-out.d.ts +36 -0
  43. package/dist/boot/session-age-out.js +101 -0
  44. package/dist/boot/session-faces.d.ts +5 -14
  45. package/dist/boot/session-faces.js +9 -5
  46. package/dist/boot/stage-01-config.js +2 -0
  47. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  48. package/dist/boot/stage-05-execution-env.js +2 -2
  49. package/dist/boot/stage-06-runners.d.ts +7 -4
  50. package/dist/boot/stage-06-runners.js +15 -6
  51. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  52. package/dist/boot/stage-07-capability-layer.js +2 -1
  53. package/dist/boot/stage-08-reapers.d.ts +7 -4
  54. package/dist/boot/stage-08-reapers.js +4 -2
  55. package/dist/boot/stage-09-leader.d.ts +11 -5
  56. package/dist/boot/stage-09-leader.js +12 -3
  57. package/dist/boot/stage-10-http-server.d.ts +11 -5
  58. package/dist/boot/stage-10-http-server.js +3 -6
  59. package/dist/boot/web-admin.js +3 -1
  60. package/dist/boot/workflow-orchestration.d.ts +7 -2
  61. package/dist/boot/workflow-orchestration.js +24 -7
  62. package/dist/bounded-rotation.d.ts +29 -0
  63. package/dist/bounded-rotation.js +24 -0
  64. package/dist/capability-facts.d.ts +22 -13
  65. package/dist/capability-facts.js +28 -18
  66. package/dist/center-credential.d.ts +25 -6
  67. package/dist/center-credential.js +25 -5
  68. package/dist/config-catalog.d.ts +26 -7
  69. package/dist/config-catalog.js +14 -11
  70. package/dist/config-invariants.d.ts +2 -1
  71. package/dist/config-invariants.js +4 -4
  72. package/dist/config-types.d.ts +10 -4
  73. package/dist/config.d.ts +51 -2
  74. package/dist/config.js +35 -24
  75. package/dist/deployment-shape.d.ts +9 -1
  76. package/dist/deployment-shape.js +2 -1
  77. package/dist/device-protocol.d.ts +5 -4
  78. package/dist/device-protocol.js +1 -1
  79. package/dist/device-ws-hub.js +3 -2
  80. package/dist/device-ws-protocol.d.ts +63 -10
  81. package/dist/device-ws-protocol.js +56 -25
  82. package/dist/engine-identity.d.ts +19 -7
  83. package/dist/engine-identity.js +10 -2
  84. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  85. package/dist/execution-env-refusal.d.ts +23 -0
  86. package/dist/execution-env-refusal.js +5 -0
  87. package/dist/execution-lane-caps.d.ts +3 -1
  88. package/dist/field-conflict.d.ts +1 -1
  89. package/dist/file-history-capture.d.ts +11 -4
  90. package/dist/file-history-capture.js +9 -6
  91. package/dist/file-history-disclosure.d.ts +7 -5
  92. package/dist/file-history-disclosure.js +7 -5
  93. package/dist/fleet/drain-entry.d.ts +4 -0
  94. package/dist/hooks/hook-runner.js +2 -2
  95. package/dist/host-lsp-manager.d.ts +2 -2
  96. package/dist/http/admission.js +17 -2
  97. package/dist/http/approval-sweeps.js +5 -5
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +2 -1
  101. package/dist/http/dispatch.js +34 -27
  102. package/dist/http/durable-run-leg.js +16 -16
  103. package/dist/http/health-self-report.d.ts +9 -1
  104. package/dist/http/health-self-report.js +2 -0
  105. package/dist/http/owner-access.d.ts +31 -11
  106. package/dist/http/owner-access.js +37 -6
  107. package/dist/http/resume-ctx-read.js +25 -3
  108. package/dist/http/resume-legs.d.ts +2 -1
  109. package/dist/http/resume-legs.js +259 -79
  110. package/dist/http/route-ctx.d.ts +18 -8
  111. package/dist/http/route-table.d.ts +1 -1
  112. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  113. package/dist/http/routes/agents-roster.js +2 -3
  114. package/dist/http/routes/approvals-assistant.js +4 -3
  115. package/dist/http/routes/background.d.ts +3 -3
  116. package/dist/http/routes/capabilities.js +8 -5
  117. package/dist/http/routes/fleet.js +10 -8
  118. package/dist/http/routes/projects.d.ts +3 -2
  119. package/dist/http/routes/projects.js +4 -5
  120. package/dist/http/routes/runs.js +102 -36
  121. package/dist/http/routes/session-sync.d.ts +4 -3
  122. package/dist/http/routes/session-sync.js +172 -77
  123. package/dist/http/routes/sessions-list.js +13 -7
  124. package/dist/http/routes/sessions.js +10 -6
  125. package/dist/http/routes/tasks.js +30 -16
  126. package/dist/http/routes/trace-usage.js +2 -2
  127. package/dist/http/server.d.ts +21 -27
  128. package/dist/http/server.js +2 -2
  129. package/dist/http/submit-ctx.js +5 -4
  130. package/dist/http/wire-types.d.ts +14 -4
  131. package/dist/identity-source.d.ts +20 -3
  132. package/dist/identity-source.js +14 -0
  133. package/dist/index.d.ts +4 -0
  134. package/dist/lane-home-probe.d.ts +3 -7
  135. package/dist/lane-home-probe.js +0 -1
  136. package/dist/leader/leader.d.ts +2 -1
  137. package/dist/leg-facts.d.ts +13 -5
  138. package/dist/leg-facts.js +2 -1
  139. package/dist/memory-scope.d.ts +5 -4
  140. package/dist/memory-scope.js +4 -4
  141. package/dist/memory-sync-client.d.ts +30 -2
  142. package/dist/memory-sync-client.js +16 -9
  143. package/dist/observability/err-text.d.ts +7 -0
  144. package/dist/observability/err-text.js +5 -0
  145. package/dist/observability/fail-open.d.ts +7 -3
  146. package/dist/observability/fail-open.js +7 -3
  147. package/dist/observability/metrics.js +1 -0
  148. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  149. package/dist/orchestration/workflow-notify-journal.js +12 -1
  150. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  151. package/dist/orchestration/workflow-park-decision.js +49 -10
  152. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  153. package/dist/orchestration/workflow-park-holder.js +98 -0
  154. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  155. package/dist/orchestration/workflow-record-fence.js +42 -0
  156. package/dist/owner-faces.d.ts +1 -1
  157. package/dist/parked-decide.d.ts +104 -53
  158. package/dist/parked-decide.js +153 -117
  159. package/dist/peer-directory.d.ts +2 -2
  160. package/dist/peer-inbound-face.d.ts +2 -6
  161. package/dist/peer-inbound-face.js +0 -3
  162. package/dist/permission-mode-admission.d.ts +41 -5
  163. package/dist/permission-mode-admission.js +29 -3
  164. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  165. package/dist/plugins/adoption-log-sql.js +7 -1
  166. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  167. package/dist/plugins/approval-ask-store-file.js +8 -0
  168. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  169. package/dist/plugins/approval-ask-store-memory.js +28 -9
  170. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  171. package/dist/plugins/approval-ask-store-sql.js +34 -5
  172. package/dist/plugins/approval-exemption-store.js +3 -0
  173. package/dist/plugins/approval-nonce-store.js +2 -0
  174. package/dist/plugins/breaker-state-sql.js +2 -0
  175. package/dist/plugins/caching-session-store.d.ts +9 -15
  176. package/dist/plugins/caching-session-store.js +6 -0
  177. package/dist/plugins/device-store-sql.js +6 -0
  178. package/dist/plugins/error-origin.d.ts +18 -0
  179. package/dist/plugins/error-origin.js +10 -0
  180. package/dist/plugins/execution-placement-sql.js +6 -6
  181. package/dist/plugins/image-bake-store-sql.js +6 -0
  182. package/dist/plugins/image-index-sql.js +4 -0
  183. package/dist/plugins/landed-attendants.d.ts +212 -0
  184. package/dist/plugins/landed-attendants.js +161 -0
  185. package/dist/plugins/leader-run-store-sql.js +4 -0
  186. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  187. package/dist/plugins/local-session-live-reference.js +39 -0
  188. package/dist/plugins/local-session-store.d.ts +71 -33
  189. package/dist/plugins/local-session-store.js +129 -27
  190. package/dist/plugins/memory-engine-pg.js +4 -0
  191. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  192. package/dist/plugins/memory-engine-tidb.js +4 -0
  193. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  194. package/dist/plugins/memory-sync-store-pg.js +5 -0
  195. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  196. package/dist/plugins/one-time-migrations.d.ts +57 -63
  197. package/dist/plugins/one-time-migrations.js +162 -90
  198. package/dist/plugins/org-delivery-quota-store.js +4 -1
  199. package/dist/plugins/outcome-ledger-sql.js +2 -0
  200. package/dist/plugins/permission-rule-store-sql.js +11 -4
  201. package/dist/plugins/pg-cost-quota.js +4 -0
  202. package/dist/plugins/pg-pool.js +24 -1
  203. package/dist/plugins/pg-rate-limiter.js +2 -0
  204. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  205. package/dist/plugins/posix-shell-fs.js +4 -1
  206. package/dist/plugins/remote-env-adb.d.ts +19 -15
  207. package/dist/plugins/remote-env-adb.js +41 -15
  208. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  209. package/dist/plugins/remote-env-connect-seat.js +72 -0
  210. package/dist/plugins/remote-env-device.d.ts +22 -5
  211. package/dist/plugins/remote-env-device.js +106 -8
  212. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  213. package/dist/plugins/remote-env-e2b.js +42 -32
  214. package/dist/plugins/remote-env-file-error.js +6 -2
  215. package/dist/plugins/remote-env-host.d.ts +17 -0
  216. package/dist/plugins/remote-env-host.js +30 -38
  217. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  218. package/dist/plugins/remote-env-k8s.js +53 -30
  219. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  220. package/dist/plugins/remote-env-local-docker.js +34 -41
  221. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  222. package/dist/plugins/remote-env-ssh.js +60 -59
  223. package/dist/plugins/remote-shell.d.ts +98 -1
  224. package/dist/plugins/remote-shell.js +218 -0
  225. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  226. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  227. package/dist/plugins/retention-lane-store-sql.js +4 -0
  228. package/dist/plugins/retention-sql.d.ts +20 -2
  229. package/dist/plugins/retention-sql.js +11 -0
  230. package/dist/plugins/run-store-sql.d.ts +21 -2
  231. package/dist/plugins/run-store-sql.js +31 -7
  232. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  233. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  234. package/dist/plugins/schema-consistency.d.ts +4 -2
  235. package/dist/plugins/schema-consistency.js +19 -2
  236. package/dist/plugins/send-file-ledger.js +3 -0
  237. package/dist/plugins/serving-requirement.d.ts +26 -7
  238. package/dist/plugins/serving-requirement.js +11 -4
  239. package/dist/plugins/session-birth.d.ts +6 -2
  240. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  241. package/dist/plugins/session-ext-table.d.ts +14 -1
  242. package/dist/plugins/session-ext-table.js +5 -1
  243. package/dist/plugins/session-row-sql.d.ts +49 -2
  244. package/dist/plugins/session-row-sql.js +56 -13
  245. package/dist/plugins/session-store-sql.d.ts +34 -18
  246. package/dist/plugins/session-store-sql.js +98 -17
  247. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  248. package/dist/plugins/shared-memory-store-sql.js +6 -2
  249. package/dist/plugins/sql-driver.d.ts +2 -1
  250. package/dist/plugins/sql-driver.js +18 -3
  251. package/dist/plugins/sql-errors.d.ts +11 -6
  252. package/dist/plugins/sql-errors.js +5 -6
  253. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  254. package/dist/plugins/stop-then-start-sentence.js +14 -10
  255. package/dist/plugins/stop-then-start.d.ts +109 -0
  256. package/dist/plugins/stop-then-start.js +104 -0
  257. package/dist/plugins/store-backend.d.ts +40 -13
  258. package/dist/plugins/store-backend.js +169 -27
  259. package/dist/plugins/store-boundary.d.ts +57 -0
  260. package/dist/plugins/store-boundary.js +139 -0
  261. package/dist/plugins/store-contracts.d.ts +17 -0
  262. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  263. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  264. package/dist/plugins/store-fault.d.ts +26 -7
  265. package/dist/plugins/store-fault.js +18 -2
  266. package/dist/plugins/task-attachment-store.js +9 -0
  267. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  268. package/dist/plugins/task-list-store-sql.js +10 -8
  269. package/dist/plugins/tidb-cost-quota.js +4 -0
  270. package/dist/plugins/tidb-pool.js +2 -1
  271. package/dist/plugins/tidb-rate-limiter.js +4 -0
  272. package/dist/plugins/user-input-ledger-sql.js +4 -0
  273. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  274. package/dist/plugins/workflow-run-store-sql.js +64 -4
  275. package/dist/plugins/workspace-fill.d.ts +4 -7
  276. package/dist/plugins/workspace-fill.js +2 -1
  277. package/dist/plugins/write-behind-counter.js +5 -0
  278. package/dist/project-context.d.ts +0 -2
  279. package/dist/project-context.js +4 -8
  280. package/dist/project-registry.d.ts +46 -0
  281. package/dist/project-registry.js +44 -0
  282. package/dist/request-key-closure.js +1 -1
  283. package/dist/request-value-closure.d.ts +4 -0
  284. package/dist/request-value-closure.js +10 -0
  285. package/dist/run-acceptance.d.ts +3 -2
  286. package/dist/run-acceptance.js +4 -6
  287. package/dist/run-local.js +1 -0
  288. package/dist/runs.d.ts +37 -8
  289. package/dist/runs.js +53 -12
  290. package/dist/runtime-governance.d.ts +5 -4
  291. package/dist/security.d.ts +30 -32
  292. package/dist/security.js +41 -7
  293. package/dist/server-secret-env.d.ts +12 -10
  294. package/dist/server-secret-env.js +24 -15
  295. package/dist/session-bindings.d.ts +82 -0
  296. package/dist/session-bindings.js +18 -0
  297. package/dist/session-children-retire.d.ts +2 -2
  298. package/dist/session-children-retire.js +2 -6
  299. package/dist/session-deletion.d.ts +16 -1
  300. package/dist/session-error-wire.d.ts +3 -3
  301. package/dist/session-error-wire.js +3 -3
  302. package/dist/session-incarnation.d.ts +10 -0
  303. package/dist/session-incarnation.js +10 -1
  304. package/dist/session-kind.d.ts +57 -0
  305. package/dist/session-kind.js +22 -0
  306. package/dist/session-row.d.ts +21 -1
  307. package/dist/session-row.js +7 -1
  308. package/dist/session-sync-kernel.d.ts +24 -14
  309. package/dist/session-sync-kernel.js +3 -6
  310. package/dist/session-sync.d.ts +4 -98
  311. package/dist/session-sync.js +1 -71
  312. package/dist/session-titler.d.ts +10 -3
  313. package/dist/session-titler.js +19 -6
  314. package/dist/ssh-host-key.d.ts +5 -0
  315. package/dist/ssh-host-key.js +3 -0
  316. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  317. package/dist/stores-sql/file-history-store-sql.js +108 -70
  318. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  319. package/dist/stores-sql/pg-session-storage.js +37 -13
  320. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  321. package/dist/stores-sql/retention-store-sql.js +2 -10
  322. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  323. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  324. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  325. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  326. package/dist/stores-sql/tidb-session-store.js +37 -13
  327. package/dist/submission-exit.d.ts +3 -2
  328. package/dist/submission-exit.js +9 -1
  329. package/dist/task-cwd.d.ts +2 -2
  330. package/dist/task-settings.d.ts +6 -5
  331. package/dist/terminal.d.ts +33 -2
  332. package/dist/terminal.js +139 -9
  333. package/dist/tool-approval.d.ts +140 -6
  334. package/dist/tool-approval.js +165 -72
  335. package/dist/trace/self-minted-wire.d.ts +290 -27
  336. package/dist/trace/self-minted-wire.js +101 -13
  337. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
  338. package/package.json +2 -2
  339. package/wire-changes/server@7.151.0.json +762 -0
  340. package/dist/http/session-inherit-env.d.ts +0 -36
  341. package/dist/http/session-inherit-env.js +0 -6
@@ -1,16 +1,18 @@
1
1
  import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { uuidv7, resumeWithVerification, resumeCascade, CheckpointError, judgeCheckpointRow, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY } from "@sema-agent/core";
4
- import { decideParkedAgent, deliverToHoldingRun, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
4
+ import { answerOfHolderFence, answerOfHolderRedeem, decideParkedAgent, deliverableEntryOf, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, holderFenceFaultAnswer, planWorkflowParkedDecide, redeemHere, runChannelEnded, workflowParkGuidance } from "../parked-decide.js";
5
5
  import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
6
- import { runFactsOf, sealRouteDeps, sessionFactsReadOf } from "./owner-access.js";
6
+ import { heldParksOf, readRunParks } from "../orchestration/workflow-agent-session-index.js";
7
+ import { isAdoptionQuiescing, trackMaintenanceLeg } from "../adoption/quiesce.js";
8
+ import { runFactsOf, sealRouteDeps, sessionFactsReadOf, sessionOccupantReadOf } from "./owner-access.js";
7
9
  import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
8
10
  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
11
  import { HttpError, checkpointPartsOf, decodeCheckpointScope, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
10
12
  import { keyAdmits } from "../owner-faces.js";
11
13
  import { personOf } from "../owner-brands.js";
12
14
  import { recoveredSubject } from "../owner-faces.js";
13
- import { beatRunHeartbeat, graftParkToolCallId, heldParkOf, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort, voidHeldParkCard } from "../runs.js";
15
+ import { beatRunHeartbeat, createRunAnchorWriter, graftParkToolCallId, heldParkOf, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort, voidHeldParkCard } from "../runs.js";
14
16
  import { commitParkRow } from "../park-row-commit.js";
15
17
  import { atHostBoundary, failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane, outwardThrownMessage } from "../terminal.js";
16
18
  import { voidVerifierPauseCard } from "../verifier-pause-card.js";
@@ -60,7 +62,7 @@ import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflic
60
62
  import { refusalAfterLostClaim } from "./claim-heartbeat.js";
61
63
  import { CONTINUATION_CLAIM_FACE, ClaimFaultAnswer, commitExecutionContext, faultedAnswerOf, isRootSessionGoneError, resolveBeforeClaim, rootSessionAbsentAnswer, stampSpecRoot, withdrawClaim } from "./execution-context.js";
62
64
  import { timeClaim } from "./claim-latency.js";
63
- import { commitSessionInheritEnv } from "./session-inherit-env.js";
65
+ import { commitLegBindings } from "../session-bindings.js";
64
66
  import { ASK_USER_QUESTION_TOOL_NAME, isApprovalGateKind, isEngineQuestionRow } from "../approval-content-kind.js";
65
67
  import { deploymentListAsks } from "../approval.js";
66
68
  import { isParkLedgerEventType } from "../trace/ledger-events.js";
@@ -70,11 +72,18 @@ import { buildCheckpointVersionKeys, resumeAssemblyRefusalAnswer, resumeRefusalW
70
72
  import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
71
73
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
72
74
  import { restateResumeCtx } from "./resume-restatement.js";
75
+ import { stampReplayEntryMode } from "../permission-mode-admission.js";
73
76
  import { httpErrorRefusal } from "./send.js";
74
77
  import { createRunAcceptance } from "../run-acceptance.js";
75
78
  import { createSubmissionSeat } from "../submission-exit.js";
76
79
  import { fieldConflictFacts } from "../field-conflict.js";
77
80
  import { createSessionNotifyDoor } from "../session-notify-door.js";
81
+ const HANDOFF_RETRY_CODES = new Set(["conflict.session_active_run", "not_found.parked_checkpoint", "conflict.claim_lost"]);
82
+ function handoffResultOf(refusal, refusedWhy) {
83
+ const errorCode = "errorCode" in refusal.body ? refusal.body.errorCode : undefined;
84
+ const code = typeof errorCode === "string" ? errorCode : `status_${refusal.status}`;
85
+ return refusal.status >= 500 || HANDOFF_RETRY_CODES.has(code) ? { kind: "retry", why: code } : { kind: "refused", why: refusedWhy ?? code };
86
+ }
78
87
  function compositionUnlinkedAnswer() {
79
88
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context does not match this card — the run is a cascade but the card records no ladder rung, so the ladder cannot be continued" }) };
80
89
  }
@@ -302,6 +311,13 @@ export function decidedActionOf(binding) {
302
311
  export function createResumeLegs(ctx) {
303
312
  const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, admitRun, safeDecode, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
304
313
  const ctxOpening = (sessionId) => ({ sessionId, opener: deps.checkpointCtxSealing });
314
+ const replayCtxOf = (stored, restatement) => {
315
+ const restated = restateResumeCtx(stored, restatement);
316
+ if (restated.unattendedLeg === true)
317
+ return restated;
318
+ const body = stampReplayEntryMode(restated.body, deps.config);
319
+ return body === restated.body ? restated : { ...restated, body };
320
+ };
305
321
  const resumeFleetScope = (req, auth) => req ? (gatedSubject(req, deps.config) ?? "default") : (auth.subject ?? "default");
306
322
  function createRememberIntent(sessionId, remember) {
307
323
  let pending;
@@ -569,73 +585,56 @@ export function createResumeLegs(ctx) {
569
585
  if (!plan.ok)
570
586
  return plan.deny;
571
587
  const fingerprint = parkDecisionFingerprint(plan.outcome, restatement);
572
- const live = await deliverToHoldingRun(origin, { token, outcome: plan.outcome, decision: parkedReq.decision, fingerprint, restated: restatement !== undefined });
573
- if (live.kind === "answered")
574
- return live.answer;
575
588
  const decisions = deps.workflowParkDecisions;
576
- const runStore = deps.runStore;
577
- if (decisions === undefined || runStore === undefined) {
589
+ if (decisions === undefined || deps.runStore === undefined) {
578
590
  return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
579
591
  }
580
- const liveReceiptOf = async (held) => {
581
- if (held === null || held.fingerprint !== fingerprint)
582
- return undefined;
583
- const run = await runFactsOf(runStore, held.deliveredTaskId);
584
- return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId, NO_SETTLEMENT_EVIDENCE) : undefined;
585
- };
586
- const replay = await liveReceiptOf(await decisions.get(token));
587
- if (replay !== undefined)
588
- return replay;
589
- let recordFailure;
590
- const answerOfRecordFailure = async (failed) => {
591
- if (failed.kind === "faulted")
592
- return faultedAnswerOf(failed.err, CONTINUATION_CLAIM_FACE.faulted);
592
+ if (origin.run.status === "running") {
593
+ let verdict;
593
594
  try {
594
- return await refusalAfterLostClaim(deps, runStore, plan.hostSessionId, failed.deliveredTaskId);
595
+ verdict = await decisions.recordForRunningHolder({ token, runId: plan.runId, hostSessionId: plan.hostSessionId, outcome: plan.outcome, fingerprint, deliveredTaskId: plan.runId, acceptedAtMs: Date.now() }, { restated: restatement !== undefined });
595
596
  }
596
- catch (e) {
597
- return faultedAnswerOf(e, CONTINUATION_CLAIM_FACE.faulted);
597
+ catch (err) {
598
+ return faultedAnswerOf(err, () => holderFenceFaultAnswer(plan.runId));
598
599
  }
599
- };
600
- const delivery = {
601
- carried: [{ runId: plan.runId, token, outcome: plan.outcome }],
602
- commitBeforeLaunch: async (deliveredTaskId) => {
603
- let recorded;
604
- try {
605
- recorded = await decisions.record({ token, runId: plan.runId, hostSessionId: plan.hostSessionId, outcome: plan.outcome, fingerprint, deliveredTaskId, acceptedAtMs: Date.now() });
606
- }
607
- catch (err) {
608
- recordFailure = { kind: "faulted", err };
609
- throw err;
610
- }
611
- if (!recorded) {
612
- recordFailure = { kind: "not_recorded", deliveredTaskId };
613
- throw new Error(DELIVERY_NOT_RECORDED);
600
+ switch (verdict) {
601
+ case "not_running":
602
+ break;
603
+ case "not_held":
604
+ case "restated":
605
+ case "other_decision":
606
+ return answerOfHolderFence(verdict, plan.runId);
607
+ case "stands":
608
+ return answerOfHolderRedeem(await redeemHere(plan.runId, origin.run.scope, { token, outcome: plan.outcome }), plan.runId, parkedReq.decision);
609
+ default: {
610
+ const unhandled = verdict;
611
+ throw new Error(`unhandled workflow holder fence verdict: ${String(unhandled)}`);
614
612
  }
615
- },
616
- };
617
- const hostToken = await cs
618
- .findPendingTokenBySession(plan.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" })
619
- .catch(() => undefined);
620
- const admittedPayer = decodeCheckpointScope(cp.scope);
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);
613
+ }
626
614
  }
627
- catch (err) {
628
- const failed = recordFailure;
629
- if (failed === undefined)
630
- throw err;
631
- return await answerOfRecordFailure(failed);
615
+ const laned = await deliverViaResumeLane({
616
+ hostSessionId: plan.hostSessionId,
617
+ runId: plan.runId,
618
+ runScope: origin.run.scope,
619
+ token,
620
+ outcome: plan.outcome,
621
+ fingerprint,
622
+ message: plan.message,
623
+ ...(restatement !== undefined ? { restatement } : {}),
624
+ initiator: { kind: "decide", req, caller: decider?.subject, ...(acceptEarly === true ? { acceptEarly: true } : {}), admittedPayer: decodeCheckpointScope(cp.scope) },
625
+ });
626
+ switch (laned.kind) {
627
+ case "launched":
628
+ case "replayed":
629
+ case "refused":
630
+ return laned.out;
631
+ case "record_failed":
632
+ return await answerOfRecordFailure(laned.failure, plan.hostSessionId);
633
+ default: {
634
+ const unhandled = laned;
635
+ throw new Error(`unhandled resume lane delivery: ${JSON.stringify(unhandled)}`);
636
+ }
632
637
  }
633
- const failed = recordFailure;
634
- if (failed !== undefined)
635
- return await answerOfRecordFailure(failed);
636
- if (acceptedTaskIdOf(out) === undefined)
637
- return (await liveReceiptOf(await decisions.get(token))) ?? out;
638
- return out;
639
638
  }
640
639
  }
641
640
  const runCtx = await ctxPromise;
@@ -644,7 +643,7 @@ export function createResumeLegs(ctx) {
644
643
  if (runCtx === undefined || runCtx.ctx === null)
645
644
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
646
645
  const { runSessionId, ctx: storedCtx } = runCtx;
647
- const ctx = restateResumeCtx(storedCtx, restatement);
646
+ const ctx = replayCtxOf(storedCtx, restatement);
648
647
  const auth = { sessionId: runSessionId, subject: checkpointPartsOf(cp.scope)?.subject, memoryScope: ctx.memoryScope };
649
648
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
650
649
  if (!resolved.ok)
@@ -691,6 +690,7 @@ export function createResumeLegs(ctx) {
691
690
  onNotice: (n) => engineNotices.push(n),
692
691
  ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
693
692
  ...(ctx.projectContext !== undefined ? { projectContext: ctx.projectContext } : {}),
693
+ ...(ctx.memorySeed !== undefined ? { memorySeed: ctx.memorySeed } : {}),
694
694
  lease,
695
695
  execution,
696
696
  });
@@ -979,7 +979,7 @@ export function createResumeLegs(ctx) {
979
979
  ...heldDecisions,
980
980
  ...(workflowDecision !== undefined && taskId !== undefined ? { afterCheckpointResolve: () => workflowDecision.commitBeforeLaunch(taskId) } : {}),
981
981
  };
982
- commitSessionInheritEnv(deps, sessionId, resumeCtx.ctx.body);
982
+ commitLegBindings(deps, undefined, sessionId, args.legFacts?.sessionBindings);
983
983
  if (args.onResumeCommitted)
984
984
  await args.onResumeCommitted();
985
985
  let unregisterEngineNotice;
@@ -1036,7 +1036,7 @@ export function createResumeLegs(ctx) {
1036
1036
  const verifyTailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
1037
1037
  const verifyInternals = {
1038
1038
  ...launchInternals,
1039
- ...compositionLegRoot(args.legFacts?.registeredCwd),
1039
+ ...compositionLegRoot(args.legFacts?.sessionBindings.cwd),
1040
1040
  ...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
1041
1041
  onForwardEvent: (e) => {
1042
1042
  fleetPub?.onForwardEvent(e);
@@ -1148,15 +1148,18 @@ export function createResumeLegs(ctx) {
1148
1148
  const append = (type, data) => (rs && taskId ? serialAppend(type, data) : Promise.resolve());
1149
1149
  cancelLedgerAppend = append;
1150
1150
  await openEngineNoticePort(append);
1151
- const anchorOwner = subject ?? null;
1152
1151
  const sessionFactsOf = sessionFactsReadOf(deps.sessionStorage);
1153
- const captureTurnAnchor = deps.resumeAnchorStore && sessionFactsOf
1154
- ? async (eventId) => { const leaf = (await sessionFactsOf(sessionId))?.leafId; if (leaf)
1155
- await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner); }
1156
- : undefined;
1157
- const captureUserMessageAnchor = deps.resumeAnchorStore && taskId && freshRunClaimed
1158
- ? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
1159
- : undefined;
1152
+ const anchors = createRunAnchorWriter({
1153
+ seat: deps.backend?.landedAttendants(),
1154
+ sessionId,
1155
+ owner: subject ?? null,
1156
+ promptKey: taskId && freshRunClaimed ? taskId : undefined,
1157
+ witness: args.legFacts?.sessionWitness,
1158
+ occupantOf: sessionOccupantReadOf(deps.sessionStorage),
1159
+ leafOf: sessionFactsOf === undefined ? undefined : async (id) => (await sessionFactsOf(id))?.leafId,
1160
+ });
1161
+ const captureTurnAnchor = anchors.turn;
1162
+ const captureUserMessageAnchor = anchors.prompt;
1160
1163
  const anchor = new TurnAnchorCapture(captureTurnAnchor, "resume", captureUserMessageAnchor);
1161
1164
  const flush = async () => {
1162
1165
  if (reasoning) {
@@ -1623,7 +1626,7 @@ export function createResumeLegs(ctx) {
1623
1626
  return resumeCtxUnreadableAnswer();
1624
1627
  if (ctxRead.kind === "absent")
1625
1628
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
1626
- const ctx = restateResumeCtx(ctxRead.ctx, restatement);
1629
+ const ctx = replayCtxOf(ctxRead.ctx, restatement);
1627
1630
  const auth = { sessionId, subject: checkpointPartsOf(cp.scope)?.subject, memoryScope: ctx.memoryScope };
1628
1631
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1629
1632
  if (!resolved.ok)
@@ -1668,7 +1671,7 @@ export function createResumeLegs(ctx) {
1668
1671
  }),
1669
1672
  };
1670
1673
  }
1671
- const ctx = restateResumeCtx(storedCtx, restatement);
1674
+ const ctx = replayCtxOf(storedCtx, restatement);
1672
1675
  const windowDenial = await usageWindowDenialFor(subject === undefined ? undefined : personOf(recoveredSubject(subject)));
1673
1676
  if (windowDenial)
1674
1677
  return { status: windowDenial.status, body: windowDenial.body };
@@ -1757,7 +1760,7 @@ export function createResumeLegs(ctx) {
1757
1760
  return resumeCtxUnreadableAnswer();
1758
1761
  if (ctxRead.kind === "absent")
1759
1762
  return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
1760
- const ctx = restateResumeCtx(ctxRead.ctx, restatement);
1763
+ const ctx = replayCtxOf(ctxRead.ctx, restatement);
1761
1764
  const auth = { sessionId: cp.sessionId, subject: checkpointPartsOf(cp.scope)?.subject, memoryScope: ctx.memoryScope };
1762
1765
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1763
1766
  if (!resolved.ok)
@@ -1784,7 +1787,7 @@ export function createResumeLegs(ctx) {
1784
1787
  ...(workflowDecision !== undefined ? { workflowDecision } : {}),
1785
1788
  ...(engineNotices.length > 0 ? { engineNotices } : {}),
1786
1789
  ...(acceptEarly === true ? { acceptEarly: true } : {}),
1787
- ...(req !== undefined ? { callerInitiated: true } : {}),
1790
+ ...(req !== undefined || workflowDecision !== undefined ? { callerInitiated: true } : {}),
1788
1791
  ...(ownerLeaseAdmittedFor !== undefined && auth.subject === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
1789
1792
  });
1790
1793
  }
@@ -1792,6 +1795,183 @@ export function createResumeLegs(ctx) {
1792
1795
  resolved.peerLease.release();
1793
1796
  }
1794
1797
  }
1798
+ async function answerOfRecordFailure(failed, hostSessionId) {
1799
+ if (failed.kind === "faulted")
1800
+ return faultedAnswerOf(failed.err, CONTINUATION_CLAIM_FACE.faulted);
1801
+ const runStore = deps.runStore;
1802
+ if (runStore === undefined)
1803
+ throw new Error("workflow park decide: a decision row failed to land without a run store — the claim fence it was written under is the run store");
1804
+ try {
1805
+ return await refusalAfterLostClaim(deps, runStore, hostSessionId, failed.deliveredTaskId);
1806
+ }
1807
+ catch (e) {
1808
+ return faultedAnswerOf(e, CONTINUATION_CLAIM_FACE.faulted);
1809
+ }
1810
+ }
1811
+ async function deliverViaResumeLane(d) {
1812
+ const decisions = deps.workflowParkDecisions;
1813
+ const runStore = deps.runStore;
1814
+ const cs = deps.checkpointStore;
1815
+ if (decisions === undefined || runStore === undefined || cs === undefined) {
1816
+ return { kind: "refused", out: { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) } };
1817
+ }
1818
+ const liveReceiptOf = async (held) => {
1819
+ if (held === null || held.fingerprint !== d.fingerprint)
1820
+ return undefined;
1821
+ const run = await runFactsOf(runStore, held.deliveredTaskId);
1822
+ return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId, NO_SETTLEMENT_EVIDENCE) : undefined;
1823
+ };
1824
+ const replay = await liveReceiptOf(await decisions.get(d.token));
1825
+ if (replay !== undefined)
1826
+ return { kind: "replayed", out: replay };
1827
+ await runChannelEnded(d.runId, d.runScope);
1828
+ const condition = d.initiator.kind === "run_ended" ? { expect: d.fingerprint } : undefined;
1829
+ let recordFailure;
1830
+ const delivery = {
1831
+ carried: [{ runId: d.runId, token: d.token, outcome: d.outcome }],
1832
+ commitBeforeLaunch: async (deliveredTaskId) => {
1833
+ let recorded;
1834
+ try {
1835
+ recorded = await decisions.record({ token: d.token, runId: d.runId, hostSessionId: d.hostSessionId, outcome: d.outcome, fingerprint: d.fingerprint, deliveredTaskId, acceptedAtMs: Date.now() }, condition);
1836
+ }
1837
+ catch (err) {
1838
+ recordFailure = { kind: "faulted", err };
1839
+ throw err;
1840
+ }
1841
+ if (!recorded) {
1842
+ recordFailure = { kind: "not_recorded", deliveredTaskId };
1843
+ throw new Error(DELIVERY_NOT_RECORDED);
1844
+ }
1845
+ },
1846
+ };
1847
+ const hostToken = await cs.findPendingTokenBySession(d.hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" }).catch(() => undefined);
1848
+ const req = d.initiator.kind === "decide" ? d.initiator.req : undefined;
1849
+ const acceptEarly = d.initiator.kind === "decide" ? d.initiator.acceptEarly : undefined;
1850
+ let out;
1851
+ try {
1852
+ out = hostToken
1853
+ ? await resumeWake(d.hostSessionId, d.message, d.initiator.caller, req, acceptEarly, delivery, d.initiator.admittedPayer, d.restatement)
1854
+ : await startFreshHostTurn(d.hostSessionId, d.message.text, d.initiator.admittedPayer, req, delivery, d.runId, d.restatement);
1855
+ }
1856
+ catch (err) {
1857
+ const failed = recordFailure;
1858
+ if (failed === undefined)
1859
+ throw err;
1860
+ return { kind: "record_failed", failure: failed };
1861
+ }
1862
+ const failed = recordFailure;
1863
+ if (failed !== undefined)
1864
+ return { kind: "record_failed", failure: failed };
1865
+ if (acceptedTaskIdOf(out) === undefined) {
1866
+ const raced = await liveReceiptOf(await decisions.get(d.token));
1867
+ return raced !== undefined ? { kind: "replayed", out: raced } : { kind: "refused", out };
1868
+ }
1869
+ return { kind: "launched", out };
1870
+ }
1871
+ async function handOverRunEndedDecisions(runId) {
1872
+ if (isAdoptionQuiescing())
1873
+ throw new Error(`workflow run ${runId}: an adoption arc is running in this process — the decision handoff yields and is retried on the next recovery sweep`);
1874
+ const handing = handOverHeldRows(runId);
1875
+ trackMaintenanceLeg(handing);
1876
+ await handing;
1877
+ }
1878
+ async function handOverHeldRows(runId) {
1879
+ const decisions = deps.workflowParkDecisions;
1880
+ const records = deps.workflowRunStore;
1881
+ const cs = deps.checkpointStore;
1882
+ const index = deps.workflowAgentSessionIndex;
1883
+ if (decisions === undefined || records === undefined || cs === undefined || index === undefined)
1884
+ return;
1885
+ const run = await records.get(runId);
1886
+ if (run === null)
1887
+ return;
1888
+ const parks = readRunParks(run);
1889
+ if (parks === undefined) {
1890
+ deps.logger?.warn?.("workflow_park_handoff_refused", { runId, why: "parks_unreadable" });
1891
+ return;
1892
+ }
1893
+ const held = heldParksOf(runId, parks);
1894
+ if (run.status === "running") {
1895
+ for (const park of held) {
1896
+ if ((await decisions.get(park.token)) !== null) {
1897
+ throw new Error(`workflow run ${runId}: its completion arrived while its record still reads running (the terminal record write did not land) and it holds an accepted decision — leaving the completion pending until recovery ends the record`);
1898
+ }
1899
+ }
1900
+ return;
1901
+ }
1902
+ for (const park of held) {
1903
+ const row = await decisions.get(park.token);
1904
+ if (row === null)
1905
+ continue;
1906
+ const deliverable = await deliverableEntryOf({ checkpointStore: cs, workflowAgentSessionIndex: index, workflowRunStore: records }, row);
1907
+ if (deliverable.kind === "unreadable")
1908
+ throw new Error(`workflow run ${runId}: the card or the holder of a recorded decision could not be read — leaving the completion pending for the next recovery sweep`);
1909
+ if (deliverable.kind === "not_deliverable" || deliverable.entry.runId !== runId)
1910
+ continue;
1911
+ const result = await handOverOne(runId, run.scope, deliverable, row);
1912
+ deps.metrics?.inc("workflow_park_handoff_total", { result: result.kind });
1913
+ switch (result.kind) {
1914
+ case "delivered":
1915
+ continue;
1916
+ case "retry":
1917
+ throw new Error(`workflow run ${runId}: the decision handoff must be retried (${result.why}) — leaving the completion pending for the next recovery sweep`);
1918
+ case "refused":
1919
+ deps.logger?.warn?.("workflow_park_handoff_refused", { runId, why: result.why });
1920
+ continue;
1921
+ default: {
1922
+ const unhandled = result;
1923
+ throw new Error(`unhandled workflow park handoff result: ${JSON.stringify(unhandled)}`);
1924
+ }
1925
+ }
1926
+ }
1927
+ }
1928
+ async function handOverOne(runId, runScope, parkEntry, row) {
1929
+ const cs = deps.checkpointStore;
1930
+ const hostSessionId = parkEntry.origin.run.originatingSessionId;
1931
+ if (hostSessionId === undefined || hostSessionId === "")
1932
+ return { kind: "refused", why: "host_unknown" };
1933
+ const ownerScope = parkEntry.card.scope;
1934
+ const hostToken = await cs.findPendingTokenBySession(hostSessionId, undefined, { gateKinds: TERMINAL_PARK_GATE_KINDS, pick: "oldest" });
1935
+ if (hostToken) {
1936
+ const hostCard = await cs.get(hostToken);
1937
+ if (hostCard !== null && hostCard.scope !== ownerScope)
1938
+ return { kind: "refused", why: "owner_mismatch" };
1939
+ }
1940
+ const parts = checkpointPartsOf(ownerScope);
1941
+ const quota = ownerCostQuotaDenied(parts?.person);
1942
+ if (quota !== undefined)
1943
+ return handoffResultOf(quota, "payer_cost_quota");
1944
+ const lease = await ownerFleetLeaseDenied(parts?.person);
1945
+ if (lease !== undefined)
1946
+ return handoffResultOf(lease, "payer_fleet_lease");
1947
+ const laned = await deliverViaResumeLane({
1948
+ hostSessionId,
1949
+ runId,
1950
+ runScope,
1951
+ token: row.token,
1952
+ outcome: row.outcome,
1953
+ fingerprint: row.fingerprint,
1954
+ message: workflowParkGuidance(runId, parkEntry.origin.park.callKey, row.outcome.decision === "allow" ? "approve" : "deny"),
1955
+ initiator: { kind: "run_ended", caller: parts?.subject, admittedPayer: decodeCheckpointScope(ownerScope) },
1956
+ });
1957
+ switch (laned.kind) {
1958
+ case "launched":
1959
+ case "replayed":
1960
+ return { kind: "delivered" };
1961
+ case "refused":
1962
+ return handoffResultOf(laned.out);
1963
+ case "record_failed": {
1964
+ if (laned.failure.kind === "faulted")
1965
+ return { kind: "retry", why: "store_fault" };
1966
+ const now = await deps.workflowParkDecisions.get(row.token);
1967
+ return now?.fingerprint === row.fingerprint ? { kind: "retry", why: "claim_lost" } : { kind: "refused", why: "superseded" };
1968
+ }
1969
+ default: {
1970
+ const unhandled = laned;
1971
+ throw new Error(`unhandled resume lane delivery: ${JSON.stringify(unhandled)}`);
1972
+ }
1973
+ }
1974
+ }
1795
1975
  async function preparePlanReview(sessionId, log, decider, sighting, restatement) {
1796
1976
  const cs = deps.checkpointStore;
1797
1977
  const refuse = (answer) => ({ ok: false, answer });
@@ -1865,7 +2045,7 @@ export function createResumeLegs(ctx) {
1865
2045
  return refuse(resumeCtxUnreadableAnswer());
1866
2046
  if (ctxRead.kind === "absent")
1867
2047
  return refuse({ status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) });
1868
- const ctx = restateResumeCtx(ctxRead.ctx, restatement);
2048
+ const ctx = replayCtxOf(ctxRead.ctx, restatement);
1869
2049
  const replay = storedBodyReplayRefusal(ctx, deps.config);
1870
2050
  if (replay !== undefined)
1871
2051
  return refuse(refuseStoredBodyReplay(sessionId, replay));
@@ -1920,6 +2100,6 @@ export function createResumeLegs(ctx) {
1920
2100
  local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },
1921
2101
  legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, preparePlanReview, drivePlanReview, sessionNotify: sessionNotifyDoor },
1922
2102
  };
1923
- return { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, decisionRowAdmits, resumePreempted, startFreshHostTurn, resumeWake, preparePlanReview, drivePlanReview, sessionNotifyDoor };
2103
+ return { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, decisionRowAdmits, resumePreempted, startFreshHostTurn, resumeWake, preparePlanReview, drivePlanReview, sessionNotifyDoor, handOverRunEndedDecisions };
1924
2104
  }
1925
2105
  //# sourceMappingURL=resume-legs.js.map
@@ -227,6 +227,16 @@ export interface CheckpointCtx {
227
227
  * 缺席 = 没继承(含 7.150.0 之前写下的行:不回填,续跑不继承 = 那时的行为)。
228
228
  */
229
229
  projectContext?: import("../project-context.js").ProjectContext;
230
+ /**
231
+ * 这条 run 的**登记簿默认读层种子**(`project-registry.ts` 的 {@link import("../project-registry.js").MemorySeed}:种子项目 + 判出它的那一位观者)—— 新提交腿判一次:
232
+ * 这次提交带的项目对提交者可见(与 `GET /v1/projects/:id` 同一只判,观者 = 读面同一只推导)⇒ 种子;带了而判不出 ⇒ `null`(判过,无);没带项目 ⇒ 缺席。**服务端写、不来自请求体**(与 {@link memoryScope} 同为
233
+ * `body` 的兄弟键:提交腿推导、落盘,续跑原样读回)。冻结的是**观者**不是词:续跑以这一位观者经同一只可见性判重读此刻的登记(停驻期间改了词 ⇒ 新词;删了 / 换了主人 /
234
+ * 不再对他可见 ⇒ 没有种子)。写点:`buildCheckpointCtx`(提交腿那一笔,经 `persistSubmissionCtx` 读这条腿的部署事实);leader worker 的写者不带(它的体不带项目)。
235
+ * 续跑腿的写回传原 ctx / 其展开 ⇒ 随行。读者只有一只:`resume-legs.ts` `resolveResumeSpec` 把它原样递给装配器(`opts.memorySeed`)—— 续跑腿不推观者、不按 `body.projectId` 现查。
236
+ * 缺席而存体带了项目 = 本格出现之前写下的行(不回填:那时的提交腿没有判过可见性)⇒ 续跑不取种子,装配器记运维一行 `resume_memory_seed_unjudged`。
237
+ * 形只有一种读法(`frozenMemorySeedOf`,读口 `resume-ctx-read.ts`)。
238
+ */
239
+ memorySeed?: import("../project-registry.js").MemorySeed | null;
230
240
  }
231
241
  /**
232
242
  * `/decide` 第三条车道交给宿主投递腿(wake:`DriveResumeArgs` / fresh:`DurableRunLegArgs`)的**一件**:这一跑在内存里带着的决定,
@@ -378,7 +388,8 @@ export interface DriveResumeArgs {
378
388
  /**
379
389
  * 🔴 **[ref](codex 交叉复审 r2-[medium] 验真后修):这条腿是**调用方发起**的吗?**
380
390
  *
381
- * `true` = 有人在 HTTP 上点了按钮(resume / plan_review / wake / decide / 赎回席)⇒ 吃按**付费方**
391
+ * `true` = 有人在 HTTP 上点了按钮(resume / plan_review / wake / decide / 赎回席),或这一跑交付的是一条**已受理的人的决定**
392
+ * (workflow 停驻卡的 run 收场送达把它交给宿主会话的下一跑 —— 没有请求,但花的是卡属主的钱)⇒ 吃按**付费方**
382
393
  * (卡属主)判的成本配额准入(`ownerCostQuotaDenied`)。缺席/`false` = **部署自己的结算腿**
383
394
  * (内部 D-D SLA deny-sweep)⇒ **不吃**那道门。
384
395
  *
@@ -392,9 +403,8 @@ export interface DriveResumeArgs {
392
403
  * 并且整段时间攥着会话 claim。**方向也自洽**:deny 是**停下**这条 run 的动作,为了省钱而拒绝执行「停」
393
404
  * 与 preempt 路由早就成文的那句同一条(「否则超顶租户连自己的花费都停不下来」)。
394
405
  *
395
- * 判别子取 `req` 的在场与否 —— 这在 `resumeCheckpoint` 里**已经是**「系统腿 vs 调用方腿」的既有判据
396
- * (绑定强制、parked 分流两处都写着 `req !== undefined` 与「内部 D-D SLA deny-sweep(req 缺席)豁免」),
397
- * 本位只是把同一条判据显式化成一个有名字的位,而不是让下游去猜。
406
+ * 判别子**不再**只是 `req` 的在场与否:`req` 缺席的腿有两种 —— SLA 拒扫(结算,不吃门)与 workflow 停驻卡的 run 收场送达(交付一条人的决定,
407
+ * 吃门,fail-closed)。所以本位是一个**有名字的事实**,由起腿的那一处写明(wake 腿:`req` 在场 ∨ 这一跑带着一件决定投递),下游只读这一位、不去猜 `req`。
398
408
  * ⚠️ **射程覆盖两道门**:紧邻的 fleet-lease 准入(`ownerFleetLeaseDenied`)读**同一位** —— [ref] 件⑤②
399
409
  * (codex r3-[high])把它一并改吃了 `callerInitiated`,理由与三问逐字同 cost 半场。
400
410
  * 🔴 本句是 [ref] R4 改写的(2026-09-03,7.57.0 发车前合并重扫 wf_1abaaa9a):此处原文写着「紧邻的
@@ -417,7 +427,7 @@ export interface DriveResumeArgs {
417
427
  *
418
428
  * 所以由上游把「已受理」这个事实传下来,`driveResumeLeg` 据此跳过重复协商。**安全性**:两处的键是
419
429
  * **同一个表达式**(`decodeCheckpointScope(cp.scope)`,上游门与下游 `auth.principal` 同源),射程条件也
420
- * 同一条(`req !== undefined` ⇔ `callerInitiated`)⇒ 跳过的是一次**逐字相同**的判定,不是一道门。
430
+ * 同一条(上游判过两门 ⇔ 下游 `callerInitiated`:`/decide` 与 workflow 停驻卡的 run 收场送达都在交下来之前按卡属主判过)⇒ 跳过的是一次**逐字相同**的判定,不是一道门。
421
431
  * 缺席/`false` ⇒ 照常自己判(其余三条驱动腿与 sweep 一字不变)。
422
432
  */
423
433
  ownerLeaseAdmitted?: boolean;
@@ -675,15 +685,15 @@ export interface RouteDeclFacts {
675
685
  * 编排器片 2 件 2:值 = 这一种计费请求的**种类**(`submit` 新工作 / `hitl-answer` 对等人的卡作答 / `resume` 续跑;闭集属主
676
686
  * `http/readiness.ts` 的 `BILLABLE_KINDS`)—— 排空与租约过期只拒 `submit`(放行闭集表 `ADMISSION_TABLE`),无凭证门不看种类。 */
677
687
  readonly billable?: import("./readiness.js").BillableKind;
678
- /** 「本条 url 在**没有任何 service credential** 的部署形下,这几个方法必须 fail-closed」。
688
+ /** 「本条 url 上这几个方法在**请求没带凭据门认的凭据**(service 凭据 / 鉴权桥验过的登录令牌;`identity-source.ts` 头注律 5)时必须 fail-closed」。
679
689
  * 判据(两个合取项)= 「授权的唯一输入是 principal 头」∧「持久改写,或爆炸半径跨租户的治理读」;
680
690
  * 逐口论证在各 `routes/*.ts` 的头注里,声明行只记结论。 */
681
691
  readonly credentialGated?: readonly RouteMethod[];
682
692
  /** 「这条 url **是**计费提交面,但它到底提不提交由**请求体里的方法**决定」—— 单 URL 多方法的
683
693
  * JSON-RPC 端点。两道**可用性** 503(drain / roster-pending)按 url 一刀切会把同端点上的纯读一起
684
694
  * 关掉,所以分派器对本行**跳过**那两道,改由域模块在写分支里施加同样的两道(文案逐字同源)。
685
- * 🔴 只豁免那两道,**不豁免安全轴**:`billable` 本身仍为 true,无 service token 那道 fail-closed
686
- * 503 照旧罩着(它判的是「这台机能不能收写」,与方法无关,且必须在解析请求体之前)。 */
695
+ * 🔴 只豁免那两道,**不豁免安全轴**:`billable` 本身仍为 true,写门那道 fail-closed
696
+ * 503 照旧罩着(它判的是「这次请求带没带过门的凭据」,与方法无关,且必须在解析请求体之前)。 */
687
697
  readonly methodDispatched?: true;
688
698
  }
689
699
  /** **可分派行**多出来的列([ref] 第三刀):身份 + 它真认的方法(+ [ref] 的订阅类)。纯门行一列都没有(见下)。 */
@@ -136,7 +136,7 @@ export declare function routeLabel(_method: string, url: string): string;
136
136
  export declare function isBillableSubmitPath(url: string): boolean;
137
137
  /** 编排器片 2 件 2:这条 url 是哪一**种**计费请求(`undefined` = 不在计费面)。声明序第一条命中的行定种类(今天每条 url 至多命中一行)。 */
138
138
  export declare function billableKindOf(url: string): BillableKind | undefined;
139
- /** [ref]([ref] 件5)—— **改写门**:不烧模型、但在没有任何 service credential 的部署形下必须
139
+ /** [ref]([ref] 件5)—— **改写门**:不烧模型、但在请求没带凭据门认的凭据时(`identity-source.ts` 头注律 5)必须
140
140
  * 与 billable / bake 两族一样 fail-closed 的写门。与 `isBillableSubmitPath` **刻意分开**:这几扇门
141
141
  * `billable=false`(零模型工作),塞进那张名单会让它们连带吃 drain 与 roster-pending 两道 503 ——
142
142
  * 而收编与规则读写在排空期/roster 未落时做完全无害。 */
@@ -26,7 +26,7 @@
26
26
  * 的调用方不该从「这台有没有授权表」上读出部署形态。
27
27
  * 🔴 四口都在 `isCredentialGatedRewrite`(server.ts)门内(codex R1-[high] 验真后补):授权的**唯一**输入是
28
28
  * principal 头 ∧ 一次调用的爆炸半径跨租户(改缺省行 = 改整台部署每个 principal 的 verdict;读面 = 全部授权行 +
29
- * operator 手写的 note)—— 无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的 worker 上那个头**可自造**,与
29
+ * operator 手写的 note)—— 请求没带凭据门认的凭据(且部署未开 `ALLOW_UNAUTHED_WRITES`)时那个头**可自造**,与
30
30
  * `/v1/memory/erase` / `POST /v1/memory/export` 同一条论证。
31
31
  *
32
32
  * ── 为什么缺席形是 **501 `capability.*`** 而不是 404(与 consolidation 阀门刻意分家)──────────────────
@@ -2,8 +2,7 @@ import { chosenAgentName } from "@sema-agent/core";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { gatedSubject, sendUnauthorized } from "../principal-gate.js";
4
4
  import { PRINCIPAL_MAX_BYTES } from "../../principal-shape.js";
5
- import { partsOf } from "../../owner-brands.js";
6
- import { taskSpecIdentityOf } from "../../engine-identity.js";
5
+ import { engineScopeOf } from "../../engine-identity.js";
7
6
  import { routeListFilter } from "../owner-access.js";
8
7
  import { assertNever } from "../route-ctx.js";
9
8
  import * as facts from "../../capability-facts.js";
@@ -99,7 +98,7 @@ export async function handleAgentRoster(req, res, match, ctx) {
99
98
  }
100
99
  const status = statusRaw === null ? undefined : statusRaw;
101
100
  const subject = gatedSubject(req, deps.config);
102
- const ownScope = taskSpecIdentityOf(subject === undefined ? undefined : partsOf(subject)).principal ?? "default";
101
+ const ownScope = engineScopeOf(subject);
103
102
  let scope;
104
103
  if (gateOwner !== null) {
105
104
  scope = ownScope;
@@ -38,7 +38,8 @@ import { fieldConflictFacts } from "../../field-conflict.js";
38
38
  import { buildSettlementEvidence } from "../../gate-settlement.js";
39
39
  import { seatText } from "../../trace/exit-seat.js";
40
40
  import { approvalQueueKindOfDoorId } from "../../approval-queue-kind.js";
41
- import { defaultPeerInboundFace, parsePeerInboundId, peerBoxScope } from "../../peer-inbound-face.js";
41
+ import { defaultPeerInboundFace, parsePeerInboundId } from "../../peer-inbound-face.js";
42
+ import { engineScopeOfOwner } from "../../engine-identity.js";
42
43
  import { RESUME_RESTATABLE_KEYS, readResumeRestatement } from "../resume-restatement.js";
43
44
  const DECIDE_BODY_DECLARED_KEYS = [
44
45
  "answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decidedVia", "decision", "feedback", "reason", "remember", "updatedInput",
@@ -949,7 +950,7 @@ async function decidePeerInbound(req, res, id, env) {
949
950
  decider = proof.principal;
950
951
  }
951
952
  const coord = parsePeerInboundId(id);
952
- const outcome = coord === undefined ? { kind: "not_found" } : await env.face.decide(peerBoxScope(decider), coord, decision);
953
+ const outcome = coord === undefined ? { kind: "not_found" } : await env.face.decide(engineScopeOfOwner(decider), coord, decision);
953
954
  switch (outcome.kind) {
954
955
  case "not_found":
955
956
  sendError(res, 404, "not_found.approval", "approval not found");
@@ -1016,7 +1017,7 @@ function redactPendingDisclosures(row) {
1016
1017
  function peerInboundRowsFor(face, principal, operatorOwnerParam) {
1017
1018
  if (operatorOwnerParam !== undefined && operatorOwnerParam !== principal)
1018
1019
  return () => [];
1019
- const scope = peerBoxScope(principal);
1020
+ const scope = engineScopeOfOwner(principal);
1020
1021
  return () => face.list(scope);
1021
1022
  }
1022
1023
  function projectPendingForWire(row, deployment, agent) {