@north-light/crouter 0.3.312 → 0.3.314

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 (497) hide show
  1. package/dist/api/client.d.ts +27 -30
  2. package/dist/api/client.js +80 -67
  3. package/dist/api/dto/broker-ops.d.ts +10 -20
  4. package/dist/api/dto/canvas.d.ts +1 -10
  5. package/dist/api/dto/health.d.ts +113 -0
  6. package/dist/api/dto/human-requests.d.ts +14 -3
  7. package/dist/api/dto/human.d.ts +2 -48
  8. package/dist/api/dto/human.js +3 -2
  9. package/dist/api/dto/inbox.d.ts +14 -0
  10. package/dist/api/dto/mail.d.ts +43 -0
  11. package/dist/api/dto/nodes.d.ts +11 -8
  12. package/dist/api/dto/recovery.d.ts +96 -0
  13. package/dist/api/dto/reviews.d.ts +2 -2
  14. package/dist/api/routes.d.ts +9 -6
  15. package/dist/api/routes.js +11 -11
  16. package/dist/build-root.d.ts +4 -0
  17. package/dist/build-root.js +11 -1
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +35 -47
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +1 -1
  20. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +12 -5
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +12 -5
  22. package/dist/cli.js +10 -1
  23. package/dist/clients/__tests__/helpers/human-daemon.d.ts +11 -0
  24. package/dist/clients/__tests__/helpers/human-daemon.js +60 -0
  25. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  26. package/dist/clients/attach/chrome/inbox-strip.d.ts +3 -3
  27. package/dist/clients/attach/chrome/inbox-strip.js +17 -9
  28. package/dist/clients/attach/chrome/ticket-panel.d.ts +2 -2
  29. package/dist/clients/attach/chrome/ticket-panel.js +77 -46
  30. package/dist/clients/attach/overlays/graph.js +6 -9
  31. package/dist/clients/attach/render/chat-view.d.ts +5 -4
  32. package/dist/clients/attach/render/chat-view.js +22 -13
  33. package/dist/clients/attach/render/page-block.d.ts +11 -7
  34. package/dist/clients/attach/render/page-block.js +64 -55
  35. package/dist/clients/attach/viewer.js +757 -746
  36. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +56 -56
  37. package/dist/clients/inbox/__tests__/integration/stale-row-activation.test.js +45 -32
  38. package/dist/clients/inbox/controller.d.ts +42 -14
  39. package/dist/clients/inbox/controller.js +240 -124
  40. package/dist/clients/inbox/dto-adapt.d.ts +28 -0
  41. package/dist/clients/inbox/dto-adapt.js +93 -0
  42. package/dist/clients/inbox/page-adapter.d.ts +5 -4
  43. package/dist/clients/inbox/page-adapter.js +12 -12
  44. package/dist/clients/inbox/resolve.d.ts +10 -5
  45. package/dist/clients/inbox/resolve.js +13 -12
  46. package/dist/clients/inbox/review/review-client.d.ts +0 -1
  47. package/dist/clients/inbox/review/review-client.js +0 -4
  48. package/dist/clients/inbox/review-adapter.d.ts +2 -1
  49. package/dist/clients/inbox/review-adapter.js +3 -5
  50. package/dist/clients/inbox/surface.d.ts +2 -2
  51. package/dist/clients/inbox/surface.js +19 -8
  52. package/dist/clients/inbox/tui.d.ts +3 -3
  53. package/dist/commands/__tests__/human.test.js +31 -15
  54. package/dist/commands/__tests__/integration/dashboard-snapshot.test.js +36 -11
  55. package/dist/commands/{sys/__tests__ → __tests__/integration}/migrate.test.js +142 -36
  56. package/dist/commands/__tests__/revive-now-gate.test.js +2 -0
  57. package/dist/commands/api-client.d.ts +5 -4
  58. package/dist/commands/api-client.js +13 -7
  59. package/dist/commands/canvas-history/shared.js +1 -1
  60. package/dist/commands/canvas-history.js +1 -1
  61. package/dist/commands/canvas-roster.js +1 -1
  62. package/dist/commands/canvas.js +1 -2
  63. package/dist/commands/human/feedback.js +20 -17
  64. package/dist/commands/human/prompts.js +45 -36
  65. package/dist/commands/human/queue.js +73 -123
  66. package/dist/commands/human/review.js +6 -16
  67. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +2 -0
  68. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +2 -0
  69. package/dist/commands/memory/__tests__/repository-root-lint.test.js +2 -0
  70. package/dist/commands/node-inspect-artifacts.js +1 -1
  71. package/dist/commands/node-lifecycle-revive.d.ts +2 -2
  72. package/dist/commands/node-lifecycle-revive.js +9 -19
  73. package/dist/commands/pkg/plugin-manage.js +0 -1
  74. package/dist/commands/profile/show.js +4 -12
  75. package/dist/commands/surface-tmux-spread.js +1 -5
  76. package/dist/commands/sys/__tests__/config-model-ladders.test.js +23 -30
  77. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +37 -11
  78. package/dist/commands/sys/branch.d.ts +2 -0
  79. package/dist/commands/sys/branch.js +19 -0
  80. package/dist/commands/sys/config.js +106 -33
  81. package/dist/commands/sys/daemon.d.ts +2 -0
  82. package/dist/commands/sys/daemon.js +130 -7
  83. package/dist/commands/sys/doctor.js +5 -15
  84. package/dist/commands/sys/migrate.js +21 -92
  85. package/dist/commands/sys/setup-settings-runner.js +4 -2
  86. package/dist/commands/sys.js +2 -15
  87. package/dist/core/__tests__/boot-fault-attempt.test.js +23 -1
  88. package/dist/core/__tests__/boot.test.js +32 -25
  89. package/dist/core/__tests__/broker-launch-candidates.test.js +49 -0
  90. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +149 -176
  91. package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +59 -10
  92. package/dist/core/__tests__/canvas-inbox-watcher.test.js +76 -20
  93. package/dist/core/__tests__/canvas.test.js +18 -47
  94. package/dist/core/__tests__/child-death-wake.test.js +29 -7
  95. package/dist/core/__tests__/cli-help-contract.test.js +2 -0
  96. package/dist/core/__tests__/close.test.js +29 -10
  97. package/dist/core/__tests__/collectible.test.js +2 -1
  98. package/dist/core/__tests__/config-change-delta.test.js +2 -0
  99. package/dist/core/__tests__/context-intro.test.js +13 -1
  100. package/dist/core/__tests__/cron-armed-context-failure.test.js +17 -12
  101. package/dist/core/__tests__/cron-held-settlement.test.js +21 -3
  102. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +29 -10
  103. package/dist/core/__tests__/cron-self-cancel-delivery.test.js +14 -2
  104. package/dist/core/__tests__/cron-timeout-descendants.test.js +13 -1
  105. package/dist/core/__tests__/daemon-boot.test.js +51 -14
  106. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  107. package/dist/core/__tests__/fixtures/fake-engine.js +8 -3
  108. package/dist/core/__tests__/focuses.test.js +10 -5
  109. package/dist/core/__tests__/helpers/harness.js +50 -18
  110. package/dist/core/__tests__/history-inbox.test.js +12 -16
  111. package/dist/core/__tests__/human-action-delivery.test.js +48 -28
  112. package/dist/core/__tests__/human-actions.test.js +7 -3
  113. package/dist/core/__tests__/inline-memory-refs.test.js +7 -2
  114. package/dist/core/__tests__/integration/broker-fork-seam.test.js +37 -10
  115. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +42 -0
  116. package/dist/core/__tests__/integration/broker-snapshot-history.test.js +42 -29
  117. package/dist/core/__tests__/integration/broker-wedge-supervision.test.js +36 -35
  118. package/dist/core/__tests__/integration/command-plugins.test.js +15 -6
  119. package/dist/core/__tests__/integration/deferred-no-wake.test.js +113 -92
  120. package/dist/core/__tests__/integration/fresh-home-commands.test.js +49 -0
  121. package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
  122. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +16 -3
  123. package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
  124. package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
  125. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
  126. package/dist/core/__tests__/integration/revive.test.js +67 -21
  127. package/dist/core/__tests__/integration/spawn-root.test.js +51 -8
  128. package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
  129. package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
  130. package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
  131. package/dist/core/__tests__/kickoff.test.js +36 -39
  132. package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
  133. package/dist/core/__tests__/lifecycle.test.js +2 -1
  134. package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
  135. package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
  136. package/dist/core/__tests__/migration.test.js +145 -31
  137. package/dist/core/__tests__/model-pin-durability.test.js +4 -1
  138. package/dist/core/__tests__/model-routes-config.test.js +13 -5
  139. package/dist/core/__tests__/node-outcome.test.js +93 -26
  140. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  141. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -1
  142. package/dist/core/__tests__/on-read-nested-store.test.js +2 -1
  143. package/dist/core/__tests__/passive-subscription.test.js +50 -157
  144. package/dist/core/__tests__/plugin-kinds.test.js +6 -4
  145. package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
  146. package/dist/core/__tests__/plugin-page-components.test.js +6 -4
  147. package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
  148. package/dist/core/__tests__/profile-project-memory-delivery.test.js +4 -1
  149. package/dist/core/__tests__/prune.test.js +11 -5
  150. package/dist/core/__tests__/push-final-guard.test.js +14 -7
  151. package/dist/core/__tests__/relaunch-root.test.js +30 -2
  152. package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
  153. package/dist/core/__tests__/repository-root-identity.test.js +4 -0
  154. package/dist/core/__tests__/review-model-floor.test.js +6 -3
  155. package/dist/core/__tests__/revive-all.test.js +13 -3
  156. package/dist/core/__tests__/revive-capacity.test.js +26 -4
  157. package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
  158. package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
  159. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
  160. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
  161. package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
  162. package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
  163. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
  164. package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
  165. package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
  166. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +2 -1
  167. package/dist/core/__tests__/stop-guard.test.js +2 -1
  168. package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
  169. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +18 -4
  170. package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
  171. package/dist/core/bootstrap.js +2 -0
  172. package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
  173. package/dist/core/broker-client/transport-socket.d.ts +1 -0
  174. package/dist/core/broker-client/transport-socket.js +13 -5
  175. package/dist/core/canvas/__tests__/attention.test.js +29 -35
  176. package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
  177. package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
  178. package/dist/core/canvas/attention.d.ts +2 -5
  179. package/dist/core/canvas/attention.js +15 -64
  180. package/dist/core/canvas/boot.d.ts +6 -7
  181. package/dist/core/canvas/boot.js +5 -9
  182. package/dist/core/canvas/canvas.d.ts +13 -109
  183. package/dist/core/canvas/canvas.js +140 -401
  184. package/dist/core/canvas/crons.d.ts +2 -0
  185. package/dist/core/canvas/crons.js +16 -15
  186. package/dist/core/canvas/daemon-owner.js +10 -0
  187. package/dist/core/canvas/db.d.ts +2 -2
  188. package/dist/core/canvas/db.js +2 -18
  189. package/dist/core/canvas/deliveries.d.ts +104 -0
  190. package/dist/core/canvas/deliveries.js +354 -0
  191. package/dist/core/canvas/executions.d.ts +29 -0
  192. package/dist/core/canvas/executions.js +68 -0
  193. package/dist/core/canvas/history.js +35 -35
  194. package/dist/core/canvas/index.d.ts +3 -1
  195. package/dist/core/canvas/index.js +3 -1
  196. package/dist/core/canvas/migrations.js +20 -12
  197. package/dist/core/canvas/nav-model.js +1 -3
  198. package/dist/core/canvas/nav-render.d.ts +4 -4
  199. package/dist/core/canvas/nav-render.js +11 -21
  200. package/dist/core/canvas/node-sql-migration.d.ts +3 -0
  201. package/dist/core/canvas/node-sql-migration.js +261 -0
  202. package/dist/core/canvas/paths.d.ts +2 -16
  203. package/dist/core/canvas/paths.js +2 -31
  204. package/dist/core/canvas/recovery.d.ts +76 -0
  205. package/dist/core/canvas/recovery.js +588 -0
  206. package/dist/core/canvas/remote-canvas-source.js +4 -0
  207. package/dist/core/canvas/render-source.d.ts +16 -27
  208. package/dist/core/canvas/render-source.js +16 -45
  209. package/dist/core/canvas/sessions.d.ts +33 -0
  210. package/dist/core/canvas/sessions.js +78 -0
  211. package/dist/core/canvas/types.d.ts +33 -33
  212. package/dist/core/canvas/types.js +2 -5
  213. package/dist/core/canvas/waits.d.ts +18 -0
  214. package/dist/core/canvas/waits.js +70 -0
  215. package/dist/core/canvas/worktrees.d.ts +40 -0
  216. package/dist/core/canvas/worktrees.js +93 -0
  217. package/dist/core/command.js +11 -0
  218. package/dist/core/config.d.ts +17 -12
  219. package/dist/core/config.js +106 -155
  220. package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
  221. package/dist/core/feed/feed.d.ts +0 -6
  222. package/dist/core/feed/feed.js +68 -56
  223. package/dist/core/feed/inbox.d.ts +7 -81
  224. package/dist/core/feed/inbox.js +46 -378
  225. package/dist/core/feed/messages.d.ts +58 -0
  226. package/dist/core/feed/messages.js +217 -0
  227. package/dist/core/fs-utils.d.ts +4 -0
  228. package/dist/core/fs-utils.js +21 -1
  229. package/dist/core/human/answer.d.ts +1 -1
  230. package/dist/core/human/answer.js +1 -1
  231. package/dist/core/human/claim.d.ts +1 -1
  232. package/dist/core/human/claim.js +2 -4
  233. package/dist/core/human/component-docs.js +1 -1
  234. package/dist/core/human/feedback-companion.js +3 -2
  235. package/dist/core/human/feedback.d.ts +5 -8
  236. package/dist/core/human/feedback.js +57 -85
  237. package/dist/core/human/page-authoring.d.ts +30 -0
  238. package/dist/core/human/page-authoring.js +19 -0
  239. package/dist/core/human/requests.d.ts +136 -0
  240. package/dist/core/human/requests.js +515 -0
  241. package/dist/core/human/root.d.ts +0 -7
  242. package/dist/core/human/root.js +0 -17
  243. package/dist/core/human/scan.d.ts +2 -7
  244. package/dist/core/human/scan.js +48 -132
  245. package/dist/core/human/summary.d.ts +1 -1
  246. package/dist/core/human/summary.js +1 -1
  247. package/dist/core/human/tickets.d.ts +23 -83
  248. package/dist/core/human/tickets.js +92 -243
  249. package/dist/core/human/types.d.ts +5 -1
  250. package/dist/core/installed-plugins.js +8 -3
  251. package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
  252. package/dist/core/locked-file.d.ts +6 -3
  253. package/dist/core/locked-file.js +36 -9
  254. package/dist/core/memory/extensions.js +2 -0
  255. package/dist/core/preview-registry.js +0 -1
  256. package/dist/core/profiles/__tests__/env-store.test.js +2 -0
  257. package/dist/core/profiles/default-binding.d.ts +0 -4
  258. package/dist/core/profiles/default-binding.js +12 -58
  259. package/dist/core/profiles/env-store.js +8 -3
  260. package/dist/core/profiles/manifest.d.ts +2 -37
  261. package/dist/core/profiles/manifest.js +35 -161
  262. package/dist/core/profiles/select.js +3 -0
  263. package/dist/core/review/__tests__/capture-origin.test.js +13 -2
  264. package/dist/core/review/__tests__/comments.test.js +28 -10
  265. package/dist/core/review/__tests__/remap.test.js +28 -10
  266. package/dist/core/review/__tests__/stage-identity.test.js +17 -7
  267. package/dist/core/review/birth.d.ts +1 -1
  268. package/dist/core/review/birth.js +34 -5
  269. package/dist/core/review/comments.js +1 -1
  270. package/dist/core/review/document.js +1 -1
  271. package/dist/core/review/project.d.ts +5 -1
  272. package/dist/core/review/project.js +14 -36
  273. package/dist/core/review/realize.d.ts +3 -6
  274. package/dist/core/review/realize.js +51 -168
  275. package/dist/core/review/remap.js +1 -1
  276. package/dist/core/review/stage.d.ts +3 -2
  277. package/dist/core/review/stage.js +2 -3
  278. package/dist/core/review/store.d.ts +3 -21
  279. package/dist/core/review/store.js +140 -186
  280. package/dist/core/review/types.d.ts +0 -1
  281. package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
  282. package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
  283. package/dist/core/runtime/broker/auth-reload.js +9 -10
  284. package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
  285. package/dist/core/runtime/broker/daemon-ops.js +42 -9
  286. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  287. package/dist/core/runtime/broker/extension-abort.js +11 -0
  288. package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
  289. package/dist/core/runtime/broker/fault-retry.js +142 -95
  290. package/dist/core/runtime/broker/inbox.d.ts +4 -32
  291. package/dist/core/runtime/broker/inbox.js +4 -92
  292. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  293. package/dist/core/runtime/broker/rebind.js +21 -12
  294. package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
  295. package/dist/core/runtime/broker-persona-guidance.js +2 -13
  296. package/dist/core/runtime/broker.js +28 -22
  297. package/dist/core/runtime/close.d.ts +3 -9
  298. package/dist/core/runtime/close.js +16 -27
  299. package/dist/core/runtime/fault.d.ts +3 -93
  300. package/dist/core/runtime/fault.js +9 -419
  301. package/dist/core/runtime/kickoff.d.ts +3 -6
  302. package/dist/core/runtime/kickoff.js +12 -18
  303. package/dist/core/runtime/launch.js +1 -1
  304. package/dist/core/runtime/lifecycle.js +40 -7
  305. package/dist/core/runtime/placement.js +2 -2
  306. package/dist/core/runtime/recycle.js +8 -2
  307. package/dist/core/runtime/reset.js +22 -6
  308. package/dist/core/runtime/revive.js +226 -218
  309. package/dist/core/runtime/spawn.js +14 -3
  310. package/dist/core/scoped-state/db.d.ts +18 -0
  311. package/dist/core/scoped-state/db.js +135 -0
  312. package/dist/core/scoped-state/migrate.d.ts +8 -0
  313. package/dist/core/scoped-state/migrate.js +181 -0
  314. package/dist/core/scoped-state/paths.d.ts +9 -0
  315. package/dist/core/scoped-state/paths.js +27 -0
  316. package/dist/core/scoped-state/profiles.d.ts +12 -0
  317. package/dist/core/scoped-state/profiles.js +62 -0
  318. package/dist/core/scoped-state/providers.d.ts +16 -0
  319. package/dist/core/scoped-state/providers.js +8 -0
  320. package/dist/core/scoped-state/schema.d.ts +3 -0
  321. package/dist/core/scoped-state/schema.js +26 -0
  322. package/dist/core/scoped-state/settings.d.ts +28 -0
  323. package/dist/core/scoped-state/settings.js +82 -0
  324. package/dist/core/subscription-state.d.ts +29 -48
  325. package/dist/core/subscription-state.js +176 -712
  326. package/dist/core/worktree-close.js +4 -4
  327. package/dist/core/worktree-mutation-async.js +2 -2
  328. package/dist/core/worktree-sweep.js +11 -16
  329. package/dist/core/worktree.js +6 -12
  330. package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
  331. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
  332. package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
  333. package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
  334. package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
  335. package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +39 -15
  336. package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
  337. package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
  338. package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
  339. package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
  340. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
  341. package/dist/daemon/api/__tests__/model-config.test.js +8 -8
  342. package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
  343. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
  344. package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
  345. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -1
  346. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
  347. package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
  348. package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
  349. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
  350. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
  351. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +27 -41
  352. package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
  353. package/dist/daemon/api/bridge.js +1 -1
  354. package/dist/daemon/api/handlers/bash-jobs.js +6 -7
  355. package/dist/daemon/api/handlers/broker-ops.js +47 -84
  356. package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
  357. package/dist/daemon/api/handlers/broker-recovery.js +251 -0
  358. package/dist/daemon/api/handlers/canvas.js +2 -44
  359. package/dist/daemon/api/handlers/daemon.js +41 -0
  360. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  361. package/dist/daemon/api/handlers/health.js +10 -5
  362. package/dist/daemon/api/handlers/human-requests.js +168 -188
  363. package/dist/daemon/api/handlers/human.js +3 -151
  364. package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
  365. package/dist/daemon/api/handlers/inbox-page.js +89 -0
  366. package/dist/daemon/api/handlers/inbox.js +21 -120
  367. package/dist/daemon/api/handlers/messages.d.ts +1 -0
  368. package/dist/daemon/api/handlers/messages.js +73 -15
  369. package/dist/daemon/api/handlers/node-outcomes.js +58 -13
  370. package/dist/daemon/api/handlers/nodes.js +7 -13
  371. package/dist/daemon/api/handlers/reviews.js +4 -29
  372. package/dist/daemon/api/map.d.ts +25 -3
  373. package/dist/daemon/api/map.js +84 -14
  374. package/dist/daemon/api/server.js +68 -9
  375. package/dist/daemon/control.d.ts +18 -0
  376. package/dist/daemon/cron/disposition.d.ts +2 -0
  377. package/dist/daemon/cron/disposition.js +8 -5
  378. package/dist/daemon/cron/settle.js +37 -13
  379. package/dist/daemon/cron/sinks.d.ts +18 -1
  380. package/dist/daemon/cron/sinks.js +47 -93
  381. package/dist/daemon/crtrd-cli.js +4 -19
  382. package/dist/daemon/crtrd.d.ts +2 -0
  383. package/dist/daemon/crtrd.js +206 -81
  384. package/dist/daemon/fleet.js +5 -6
  385. package/dist/daemon/human/finish.d.ts +12 -24
  386. package/dist/daemon/human/finish.js +88 -285
  387. package/dist/daemon/human/settle.d.ts +40 -0
  388. package/dist/daemon/human/settle.js +89 -0
  389. package/dist/daemon/human/sweep.d.ts +20 -5
  390. package/dist/daemon/human/sweep.js +56 -60
  391. package/dist/daemon/manage.d.ts +21 -16
  392. package/dist/daemon/manage.js +85 -51
  393. package/dist/daemon/messaging/node-message.d.ts +1 -1
  394. package/dist/daemon/messaging/node-message.js +9 -12
  395. package/dist/daemon/park-activity.js +3 -3
  396. package/dist/daemon/reconcilers/bash-deadline.js +6 -7
  397. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  398. package/dist/daemon/reconcilers/broker-supervision.js +41 -19
  399. package/dist/daemon/reconcilers/controller-death.js +6 -18
  400. package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
  401. package/dist/daemon/reconcilers/delivery-lane.js +130 -0
  402. package/dist/daemon/reconcilers/live-obligation.js +3 -3
  403. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  404. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
  405. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
  406. package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
  407. package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
  408. package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
  409. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
  410. package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
  411. package/dist/daemon/review/companion.js +5 -11
  412. package/dist/daemon/review/deliver.d.ts +1 -5
  413. package/dist/daemon/review/deliver.js +3 -52
  414. package/dist/daemon/review/finish.d.ts +2 -27
  415. package/dist/daemon/review/finish.js +79 -263
  416. package/dist/daemon/review/sweep.d.ts +3 -10
  417. package/dist/daemon/review/sweep.js +13 -131
  418. package/dist/daemon/startup-policy.d.ts +0 -5
  419. package/dist/daemon/startup-policy.js +0 -5
  420. package/dist/index.d.ts +2 -1
  421. package/dist/index.js +1 -1
  422. package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
  423. package/dist/migrations/__tests__/activation.test.js +9 -6
  424. package/dist/migrations/activation.d.ts +3 -2
  425. package/dist/migrations/activation.js +2 -4
  426. package/dist/migrations/corpus.d.ts +2 -5
  427. package/dist/migrations/corpus.js +9 -20
  428. package/dist/migrations/runner.d.ts +3 -1
  429. package/dist/migrations/runner.js +6 -2
  430. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
  431. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
  432. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
  433. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
  434. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
  435. package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
  436. package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
  437. package/dist/pi-extensions/canvas-passive-context.js +85 -37
  438. package/dist/pi-extensions/canvas-stophook.js +47 -31
  439. package/dist/shared/env.d.ts +1 -0
  440. package/dist/shared/env.js +3 -0
  441. package/package.json +1 -1
  442. package/runtime.lock.json +5 -5
  443. package/dist/clients/attach/__tests__/page-block.test.js +0 -54
  444. package/dist/clients/inbox/review-visibility.d.ts +0 -5
  445. package/dist/clients/inbox/review-visibility.js +0 -19
  446. package/dist/commands/canvas-rebuild-index.d.ts +0 -2
  447. package/dist/commands/canvas-rebuild-index.js +0 -46
  448. package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
  449. package/dist/core/__tests__/human-deliver.test.js +0 -256
  450. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
  451. package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
  452. package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
  453. package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
  454. package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
  455. package/dist/core/__tests__/phase4-review-store.test.js +0 -144
  456. package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
  457. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
  458. package/dist/core/canvas/human-deliveries.d.ts +0 -39
  459. package/dist/core/canvas/human-deliveries.js +0 -75
  460. package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
  461. package/dist/core/canvas/node-outcome-deliveries.js +0 -159
  462. package/dist/core/feed/passive.d.ts +0 -3
  463. package/dist/core/feed/passive.js +0 -53
  464. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
  465. package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
  466. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
  467. package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
  468. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
  469. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
  470. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
  471. package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
  472. package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
  473. package/dist/core/human/__tests__/page-tickets.test.js +0 -161
  474. package/dist/core/review/ticket-filter.d.ts +0 -17
  475. package/dist/core/review/ticket-filter.js +0 -21
  476. package/dist/core/runtime/broker/passive.d.ts +0 -17
  477. package/dist/core/runtime/broker/passive.js +0 -44
  478. package/dist/core/runtime/busy.d.ts +0 -46
  479. package/dist/core/runtime/busy.js +0 -150
  480. package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
  481. package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
  482. package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
  483. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
  484. package/dist/daemon/human/deliver-action.d.ts +0 -3
  485. package/dist/daemon/human/deliver-action.js +0 -9
  486. package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
  487. package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
  488. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
  489. package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
  490. package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
  491. package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
  492. package/dist/daemon/startup-block-marker.d.ts +0 -25
  493. package/dist/daemon/startup-block-marker.js +0 -99
  494. /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
  495. /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
  496. /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
  497. /package/dist/core/__tests__/{human-deliver.test.d.ts → integration/fresh-home-commands.test.d.ts} +0 -0
@@ -10,9 +10,10 @@ import { canvasDbPath, ensureHome } from '../canvas/paths.js';
10
10
  import { customExtensionPaths } from '../canvas/extensions.js';
11
11
  import { assertMandatoryCanvasExtensions, CANVAS_EXTENSIONS, mergeCanvasExtensions } from '../runtime/canvas-extensions.js';
12
12
  import { updateNode } from '../canvas/canvas.js';
13
+ import { parsePage, parsePageDelivery } from '../human/page.js';
13
14
  import { spawn, spawnSync } from 'node:child_process';
14
15
  const HERE = dirname(fileURLToPath(import.meta.url));
15
- const RUNTIME_COLUMNS = ['intent', 'pi_pid', 'window', 'tmux_session'];
16
+ const RUNTIME_COLUMNS = ['intent', 'window', 'tmux_session'];
16
17
  const RACE_FIXTURE = join(HERE, 'fixtures', 'metadata-migration-race.ts');
17
18
  const PRE_TOOL_GUIDE_CANVAS_EXTENSION_NAMES = [
18
19
  'canvas-inbox-watcher', 'canvas-stophook', 'canvas-recap',
@@ -53,6 +54,11 @@ function userVersion(db) {
53
54
  return db.prepare('PRAGMA user_version').get()
54
55
  .user_version;
55
56
  }
57
+ function openMigratedDb() {
58
+ const db = openDb();
59
+ migrate(db);
60
+ return db;
61
+ }
56
62
  function nodeColumns(db) {
57
63
  return db.prepare('PRAGMA table_info(nodes)').all().map((r) => r.name);
58
64
  }
@@ -77,8 +83,11 @@ after(() => {
77
83
  rmSync(home, { recursive: true, force: true });
78
84
  delete process.env['CRTR_HOME'];
79
85
  });
80
- test('opening a fresh home migrates to the latest user_version', () => {
86
+ test('opening a fresh home leaves schema initialization explicit', () => {
81
87
  const db = openDb();
88
+ assert.equal(userVersion(db), 0);
89
+ assert.equal(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'nodes'").get(), undefined);
90
+ migrate(db);
82
91
  assert.equal(userVersion(db), MIGRATIONS.length);
83
92
  });
84
93
  test('a persisted review-boundary canvas prefix is not treated as custom extensions', () => {
@@ -120,7 +129,7 @@ test('a v14 canvas normalizes historical canvas paths while retaining custom lau
120
129
  const saved = JSON.parse(readFileSync(metaPath, 'utf8'));
121
130
  assert.deepEqual(saved.launch.extensions, ['/plugins/canvas-inbox-watcher.js']);
122
131
  });
123
- test('a v14 migration is protected against concurrent writeback by normal updateNode writes', async () => {
132
+ test('an explicit v14 migration preserves legacy metadata while node updates use SQL state', async () => {
124
133
  const metaPath = join(home, 'nodes', 'saved', 'meta.json');
125
134
  mkdirSync(join(home, 'nodes', 'saved'), { recursive: true });
126
135
  writeFileSync(metaPath, JSON.stringify({
@@ -143,16 +152,17 @@ test('a v14 migration is protected against concurrent writeback by normal update
143
152
  rmSync(marker, { force: true });
144
153
  const { done } = await spawnMigrationRace(metaPath, marker);
145
154
  await waitForPath(marker);
155
+ migrate(raw);
156
+ await done;
157
+ raw.close();
146
158
  const update = updateNode('saved', { name: 'saved-raced' });
147
159
  assert.equal(update.name, 'saved-raced');
148
- await done;
149
160
  const settled = JSON.parse(readFileSync(metaPath, 'utf8'));
150
161
  assert.equal(customExtensionPaths(settled.launch.extensions).length, settled.launch.extensions.length);
151
- assert.deepEqual(settled.launch.extensions, ['/plugins/canvas-inbox-watcher.js'], 'concurrent migration + metadata writes cannot restore retired hook prefixes');
152
- assert.equal(settled.name, 'saved-raced');
153
- raw.close();
162
+ assert.deepEqual(settled.launch.extensions, ['/plugins/canvas-inbox-watcher.js'], 'migration writeback cannot restore retired hook prefixes');
163
+ assert.equal(settled.name, 'saved', 'the legacy conversion input remains unchanged after a SQL node update');
154
164
  });
155
- test('a fresh db has the four additive runtime columns', () => {
165
+ test('a v0 db retains its runtime fields while process state moves to executions', () => {
156
166
  // Build a v0 db by hand: the baseline tables only, user_version 0, no runtime
157
167
  // columns — exactly the shape of the live fleet before this phase.
158
168
  ensureHome();
@@ -189,14 +199,14 @@ test('a fresh db has the four additive runtime columns', () => {
189
199
  assert.equal(userVersion(raw), 0);
190
200
  assert.ok(!nodeColumns(raw).includes('intent'));
191
201
  raw.close();
192
- // openDb() runs the migration runner forward.
193
- const db = openDb();
202
+ const db = openMigratedDb();
194
203
  assert.equal(userVersion(db), MIGRATIONS.length);
195
- // Runtime columns now exist.
204
+ // Node runtime columns now exist; process state is projected from executions.
196
205
  const cols = nodeColumns(db);
197
206
  for (const c of RUNTIME_COLUMNS) {
198
207
  assert.ok(cols.includes(c), `expected nodes.${c} after migration`);
199
208
  }
209
+ assert.ok(!cols.includes('pi_pid'), 'nodes no longer stores process state');
200
210
  // Pre-existing data is intact — no loss, no mutation of existing columns.
201
211
  const row = db
202
212
  .prepare('SELECT node_id, status, cwd FROM nodes WHERE node_id = ?')
@@ -208,10 +218,9 @@ test('a fresh db has the four additive runtime columns', () => {
208
218
  assert.equal(edgeCount, 1);
209
219
  // The new columns are seeded NULL for the migrated row — nothing observed.
210
220
  const rt = db
211
- .prepare('SELECT intent, pi_pid, "window", tmux_session FROM nodes WHERE node_id = ?')
221
+ .prepare('SELECT intent, "window", tmux_session FROM nodes WHERE node_id = ?')
212
222
  .get('n1');
213
223
  assert.equal(rt['intent'], null);
214
- assert.equal(rt['pi_pid'], null);
215
224
  assert.equal(rt['window'], null);
216
225
  assert.equal(rt['tmux_session'], null);
217
226
  });
@@ -240,7 +249,7 @@ test('session-file ownership migration reconciles authoritative metas before cle
240
249
  insert.run('later-claimant', 'later-claimant', 'developer', 'base', 'terminal', 'active', '/tmp', '2026-01-02T00:00:00.000Z', aliasedSessionFile);
241
250
  insert.run('recovered-claimant', 'recovered-claimant', 'developer', 'base', 'terminal', 'active', '/tmp', '2026-01-03T00:00:00.000Z', sessionFile);
242
251
  assert.doesNotThrow(() => migrate(raw), 'a real canvas with pre-existing duplicates must migrate');
243
- const rows = raw.prepare('SELECT node_id, pi_session_file FROM nodes ORDER BY created, node_id').all();
252
+ const rows = raw.prepare('SELECT node_id, pi_session_file FROM node_rows ORDER BY created, node_id').all();
244
253
  assert.deepEqual(rows.map((row) => [row.node_id, row.pi_session_file]), [
245
254
  ['first-owner', sessionFile],
246
255
  ['later-claimant', null],
@@ -252,7 +261,7 @@ test('session-file ownership migration reconciles authoritative metas before cle
252
261
  const recovered = JSON.parse(readFileSync(join(home, 'nodes', 'recovered-claimant', 'meta.json'), 'utf8'));
253
262
  assert.equal(recovered.pi_session_id, 'recovered-claimant-session', 'a stale row cannot clear the authoritative session id');
254
263
  assert.equal(recovered.pi_session_file, recoveredSessionFile, 'a stale row cannot clear the authoritative session file');
255
- assert.throws(() => raw.prepare('UPDATE nodes SET pi_session_file = ? WHERE node_id = ?').run(sessionFile, 'later-claimant'), /UNIQUE/, 'the schema enforces the repaired invariant');
264
+ assert.throws(() => raw.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES ('later-duplicate', 'later-duplicate-session', 'later-claimant', ?, NULL, NULL, '2026-01-02T00:00:00.000Z', '2026-01-02T00:00:00.000Z', NULL)").run(sessionFile), /UNIQUE/, 'the SQL session table enforces the repaired invariant');
256
265
  raw.close();
257
266
  });
258
267
  test('forward session-file repair reprojects a surviving v36 meta coordinate before restoring its UNIQUE index', () => {
@@ -280,7 +289,7 @@ test('forward session-file repair reprojects a surviving v36 meta coordinate bef
280
289
  raw.exec('DROP INDEX IF EXISTS idx_nodes_pi_session_file; CREATE UNIQUE INDEX idx_nodes_pi_session_file ON nodes(pi_session_file); PRAGMA user_version = 36;');
281
290
  assert.doesNotThrow(() => migrate(raw));
282
291
  assert.equal(userVersion(raw), MIGRATIONS.length);
283
- const rows = raw.prepare('SELECT node_id, pi_session_file FROM nodes ORDER BY created, node_id').all();
292
+ const rows = raw.prepare('SELECT node_id, pi_session_file FROM node_rows ORDER BY created, node_id').all();
284
293
  assert.deepEqual(rows.map((row) => [row.node_id, row.pi_session_file]), [
285
294
  ['surviving-owner', recoveredSessionFile],
286
295
  ['stale-row', null],
@@ -288,7 +297,7 @@ test('forward session-file repair reprojects a surviving v36 meta coordinate bef
288
297
  const survivor = JSON.parse(readFileSync(join(home, 'nodes', 'surviving-owner', 'meta.json'), 'utf8'));
289
298
  assert.equal(survivor.pi_session_id, 'surviving-session');
290
299
  assert.equal(survivor.pi_session_file, recoveredSessionFile);
291
- assert.throws(() => raw.prepare('UPDATE nodes SET pi_session_file = ? WHERE node_id = ?').run(recoveredSessionFile, 'stale-row'), /UNIQUE/);
300
+ assert.throws(() => raw.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES ('stale-duplicate', NULL, 'stale-row', ?, NULL, NULL, '2026-01-02T00:00:00.000Z', '2026-01-02T00:00:00.000Z', NULL)").run(recoveredSessionFile), /UNIQUE/);
292
301
  raw.close();
293
302
  });
294
303
  test('node lifecycle substrate migration adds six columns and backfills terminal reasons', () => {
@@ -314,7 +323,7 @@ test('node lifecycle substrate migration adds six columns and backfills terminal
314
323
  insert.run('crashed', 'crashed', 'general', 'dead', null, null, '/tmp', created);
315
324
  insert.run('active', 'active', 'general', 'active', null, null, '/tmp', created);
316
325
  raw.close();
317
- const db = openDb();
326
+ const db = openMigratedDb();
318
327
  assert.equal(userVersion(db), MIGRATIONS.length);
319
328
  for (const column of lifecycleColumns)
320
329
  assert.ok(nodeColumns(db).includes(column));
@@ -331,8 +340,8 @@ test('node lifecycle substrate migration adds six columns and backfills terminal
331
340
  // v4: edges FK ON DELETE CASCADE (the one irreversible rebuild)
332
341
  /** Build a hand-rolled db at exactly v3 (baseline tables + the four runtime
333
342
  * columns, edges with NO foreign key, `user_version = 3`) — the shape the live
334
- * fleet reaches just before v4. Caller seeds it, we close it; the next openDb()
335
- * runs only v4 over it. */
343
+ * fleet reaches just before v4. Caller seeds it, then explicitly runs v4 and
344
+ * later steps. */
336
345
  function buildV3Db() {
337
346
  ensureHome();
338
347
  const raw = new DatabaseSync(canvasDbPath());
@@ -382,8 +391,8 @@ test('v4 migrates a db with a pre-existing orphan edge without throwing', () =>
382
391
  assert.equal(userVersion(raw), 3);
383
392
  assert.equal(edgeCount(raw), 2);
384
393
  raw.close();
385
- // openDb() runs v4. It must NOT throw on the orphan, and must drop it.
386
- const db = openDb();
394
+ // Explicit migration must not throw on the orphan, and must drop it.
395
+ const db = openMigratedDb();
387
396
  assert.equal(userVersion(db), MIGRATIONS.length); // migrated forward to the head
388
397
  // The orphan edge is filtered; the non-orphan edge + its count are preserved.
389
398
  assert.equal(edgeCount(db), 1);
@@ -435,7 +444,7 @@ test('bug #99: a stale v4 db with later columns already present migrates cleanly
435
444
  assert.equal(userVersion(raw), 4);
436
445
  assert.equal(nodeColumns(raw).includes('pane'), true, 'precondition: pane already exists even though user_version is stale');
437
446
  raw.close();
438
- const db = openDb();
447
+ const db = openMigratedDb();
439
448
  assert.equal(userVersion(db), MIGRATIONS.length);
440
449
  const cols = nodeColumns(db);
441
450
  assert.ok(cols.includes('pane'), 'pane survives the migration');
@@ -536,7 +545,7 @@ test('v22 converts existing schedules into self-contained crons and removes the
536
545
  payload: { kind: 'general', mode: 'base', cwd: '/tmp/work', prompt: 'go', parent: 'owner' },
537
546
  });
538
547
  raw.close();
539
- const db = openDb();
548
+ const db = openMigratedDb();
540
549
  assert.equal(userVersion(db), MIGRATIONS.length);
541
550
  const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => row.name);
542
551
  assert.ok(!tables.includes('triggers'));
@@ -571,8 +580,7 @@ test('v22 converts existing schedules into self-contained crons and removes the
571
580
  assert.match(birth.command, /crtr node new --kind 'general'/);
572
581
  assert.match(birth.command, /go/);
573
582
  });
574
- // The v22 migration runs on EVERY openDb(), so it must never fail on data shape:
575
- // a throw would make every crtr process unable to open the canvas forever. These
583
+ // The explicit v22 migration must handle every declared-legal data shape. These
576
584
  // cases are all shapes the retired schema declared legal.
577
585
  function insertNodeAt(db, id, cwd, created, parent) {
578
586
  db.prepare('INSERT INTO nodes (node_id, name, kind, status, cwd, parent, created) VALUES (?,?,?,?,?,?,?)').run(id, id, 'general', 'active', cwd, parent, created);
@@ -671,7 +679,7 @@ test('v22 converts the legal edge rows and records the ones it cannot express',
671
679
  payload: { body: 'nope' },
672
680
  });
673
681
  raw.close();
674
- const db = openDb();
682
+ const db = openMigratedDb();
675
683
  assert.equal(userVersion(db), MIGRATIONS.length, 'the schema advances despite the unconvertible row');
676
684
  const cron = (id) => db.prepare('SELECT * FROM crons WHERE cron_id = ?').get(id);
677
685
  const pruned = cron('pruned-owner');
@@ -706,7 +714,7 @@ test('v22 re-run over already-converted crons converges instead of colliding on
706
714
  VALUES ('scheduled-message', 'stale', 'owner', 'echo stale', '2026-01-01T00:00:00.000Z', '/tmp/work', '{}', '2020-01-01T00:00:00Z', '2020-01-01T00:00:00Z')
707
715
  `).run();
708
716
  raw.close();
709
- const db = openDb();
717
+ const db = openMigratedDb();
710
718
  assert.equal(userVersion(db), MIGRATIONS.length);
711
719
  const row = db.prepare('SELECT command FROM crons WHERE cron_id = ?').get('scheduled-message');
712
720
  assert.match(row.command, /crtr node message send --to 'target'/, 'the re-run replaces the half-written row');
@@ -754,8 +762,8 @@ test('v22 recovers a db wedged by an interrupted pre-atomicity run', () => {
754
762
  `).run();
755
763
  raw.exec('DROP TABLE triggers');
756
764
  raw.close();
757
- const db = openDb();
758
- assert.equal(userVersion(db), MIGRATIONS.length, 'the schema advances rather than failing every open');
765
+ const db = openMigratedDb();
766
+ assert.equal(userVersion(db), MIGRATIONS.length, 'the schema advances rather than failing every explicit migration');
759
767
  assert.notEqual(db.prepare('SELECT 1 FROM crons WHERE cron_id = ?').get('half-written'), undefined);
760
768
  });
761
769
  // The generated cron scripts are the migration's real product, so the M4
@@ -784,6 +792,112 @@ function runMigratedScript(script, opts) {
784
792
  rmSync(dir, { recursive: true, force: true });
785
793
  return { code: result.status ?? -1, log: written };
786
794
  }
795
+ function buildV39Db() {
796
+ ensureHome();
797
+ const raw = new DatabaseSync(canvasDbPath());
798
+ migrateToVersion(raw, 39);
799
+ return raw;
800
+ }
801
+ function resetV39Home() {
802
+ closeDb();
803
+ rmSync(home, { recursive: true, force: true });
804
+ return buildV39Db();
805
+ }
806
+ function writeLegacyPage(id, source, dialect = 'jsx') {
807
+ const dir = join(home, 'nodes', id);
808
+ mkdirSync(dir, { recursive: true });
809
+ const document = dialect === 'jsx'
810
+ ? 'export default function LegacyPage() { return <Page title="The gate is gone" subtitle="This page was sent with page_surface off — the refusal that used to block it no longer exists. Nothing to answer; it is here to prove delivery." placement="panel" />; }\n'
811
+ : '---\ntitle: The gate is gone\n---\nThis is the stored Markdown page.\n';
812
+ writeFileSync(join(dir, dialect === 'jsx' ? 'page.tsx' : 'page.md'), document);
813
+ writeFileSync(join(dir, 'page.json'), JSON.stringify({
814
+ schema: 'crtr.page/v1', dialect, title: 'The gate is gone',
815
+ subtitle: 'This page was sent with page_surface off — the refusal that used to block it no longer exists. Nothing to answer; it is here to prove delivery.',
816
+ placement: 'panel', document: dialect === 'jsx' ? 'page.tsx' : 'page.md', steps: 1, slots: [], source,
817
+ }));
818
+ return dir;
819
+ }
820
+ test('v40 imports pre-envelope v1 pages and still rejects invalid v2 delivery envelopes', () => {
821
+ const raw = buildV39Db();
822
+ const blocked = writeLegacyPage('page-bba481b2', {
823
+ sessionName: 'general', blockedSince: '2026-08-11T17:32:21.979Z', profileName: 'crouter', nodeId: '3zl47w7d-msowsany-ee8e2bba',
824
+ });
825
+ const emitted = writeLegacyPage('nfy-3b21bc8a', {
826
+ sessionName: 'notify', nodeId: '3zl47w7d-msmeye7h-cb3ff1da',
827
+ }, 'md');
828
+ const legacyBytes = readFileSync(join(blocked, 'page.json'));
829
+ migrate(raw);
830
+ for (const [dir, id, delivery] of [[blocked, 'page-bba481b2', { placement: 'panel', inbox: true, reply: true }], [emitted, 'nfy-3b21bc8a', { placement: 'panel', inbox: true, reply: false }]]) {
831
+ const stored = raw.prepare('SELECT presentation_dir, specification_json FROM human_requests WHERE request_id = ?').get(id);
832
+ const manifest = parsePage(stored.presentation_dir);
833
+ assert.deepEqual(manifest.delivery, delivery);
834
+ assert.deepEqual(parsePageDelivery(stored.presentation_dir), delivery);
835
+ assert.deepEqual(JSON.parse(stored.specification_json), manifest);
836
+ }
837
+ assert.deepEqual(readFileSync(join(blocked, 'page.json')), legacyBytes, 'the source manifest remains untouched');
838
+ raw.close();
839
+ const invalid = resetV39Home();
840
+ const valid = writeLegacyPage('valid-v1-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
841
+ const validBytes = readFileSync(join(valid, 'page.json'));
842
+ const dir = writeLegacyPage('broken-v2-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
843
+ writeFileSync(join(dir, 'page.json'), JSON.stringify({ schema: 'crtr.page/v2', title: 'Broken', delivery: { placement: 'panel', inbox: true, reply: false, extra: true } }));
844
+ assert.throws(() => migrate(invalid), (error) => error instanceof Error && error.message.includes(join(dir, 'page.json')));
845
+ assert.equal(userVersion(invalid), 39, 'the failed migration rolls back');
846
+ assert.deepEqual(readFileSync(join(valid, 'page.json')), validBytes, 'a failed migration does not alter an earlier v1 input');
847
+ invalid.close();
848
+ const malformedV2 = resetV39Home();
849
+ const malformedV2Dir = writeLegacyPage('malformed-v2-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
850
+ writeFileSync(join(malformedV2Dir, 'page.json'), JSON.stringify({ schema: 'crtr.page/v2', title: 'Broken', delivery: { placement: 'panel', inbox: true, reply: false } }));
851
+ assert.throws(() => migrate(malformedV2), (error) => error instanceof Error && error.message.includes(join(malformedV2Dir, 'page.json')));
852
+ malformedV2.close();
853
+ const malformedV1 = resetV39Home();
854
+ const malformedV1Dir = writeLegacyPage('malformed-v1-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
855
+ writeFileSync(join(malformedV1Dir, 'page.json'), JSON.stringify({
856
+ schema: 'crtr.page/v1', dialect: 'jsx', title: 'Broken', placement: 'panel', document: 'page.tsx', steps: 1,
857
+ slots: [{ id: 'out-of-range', kind: 'options', step: 1, config: {} }], source: { blockedSince: '2026-08-11T17:32:21.979Z' },
858
+ }));
859
+ assert.throws(() => migrate(malformedV1), (error) => error instanceof Error && error.message.includes(join(malformedV1Dir, 'page.json')));
860
+ malformedV1.close();
861
+ const malformedMarkdown = resetV39Home();
862
+ const malformedMarkdownDir = writeLegacyPage('malformed-markdown-page', {}, 'md');
863
+ const malformedMarkdownManifest = JSON.parse(readFileSync(join(malformedMarkdownDir, 'page.json'), 'utf8'));
864
+ malformedMarkdownManifest.steps = 2;
865
+ writeFileSync(join(malformedMarkdownDir, 'page.json'), JSON.stringify(malformedMarkdownManifest));
866
+ assert.throws(() => migrate(malformedMarkdown), (error) => error instanceof Error && error.message.includes(join(malformedMarkdownDir, 'page.json')));
867
+ malformedMarkdown.close();
868
+ });
869
+ test('v40 skips recovery witnesses without identity while retaining failures for indexed nodes', () => {
870
+ const raw = buildV39Db();
871
+ const faultDir = join(home, 'nodes', 'orphan-fault', 'job');
872
+ const retryDir = join(home, 'nodes', 'orphan-retry', 'job');
873
+ mkdirSync(faultDir, { recursive: true });
874
+ mkdirSync(retryDir, { recursive: true });
875
+ writeFileSync(join(faultDir, 'fault'), JSON.stringify({
876
+ link: 'viewer↔broker', op: 'connect', kind: 'connection', message: 'ENOENT', since: '2026-09-10T00:00:00.000Z', operation_id: '11111111111111111111111111111111', retry: { disposition: 'fatal' },
877
+ }));
878
+ writeFileSync(join(retryDir, 'provider-retry'), JSON.stringify({
879
+ state: 'pending', sessionFile: '/tmp/orphan-retry.jsonl', anchorEntryId: null,
880
+ fault: { link: 'pi→provider', op: 'request', kind: 'connection', message: 'ECONNRESET', since: '2026-09-10T00:00:00.000Z', operation_id: '22222222222222222222222222222222', retry: { disposition: 'auto', by: 'daemon' } },
881
+ }));
882
+ const resident = join(home, 'nodes', 'metadata-node');
883
+ mkdirSync(join(resident, 'job'), { recursive: true });
884
+ writeFileSync(join(resident, 'meta.json'), JSON.stringify({
885
+ node_id: 'metadata-node', name: 'metadata-node', created: '2026-09-10T00:00:00.000Z', cwd: '/tmp', kind: 'developer', mode: 'base', lifecycle: 'terminal',
886
+ }));
887
+ writeFileSync(join(resident, 'job', 'busy'), '');
888
+ migrate(raw);
889
+ assert.notEqual(raw.prepare('SELECT 1 FROM nodes WHERE node_id = ?').get('metadata-node'), undefined);
890
+ assert.equal(raw.prepare('SELECT COUNT(*) AS count FROM executions WHERE node_id = ?').get('metadata-node').count, 1);
891
+ assert.equal(raw.prepare("SELECT 1 FROM nodes WHERE node_id IN ('orphan-fault', 'orphan-retry')").get(), undefined);
892
+ raw.close();
893
+ const invalid = resetV39Home();
894
+ insertNode(invalid, 'indexed-node');
895
+ const invalidPath = join(home, 'nodes', 'indexed-node', 'job', 'fault');
896
+ mkdirSync(dirname(invalidPath), { recursive: true });
897
+ writeFileSync(invalidPath, 'not json');
898
+ assert.throws(() => migrate(invalid), (error) => error instanceof Error && error.message.includes(invalidPath));
899
+ invalid.close();
900
+ });
787
901
  test('a migrated predicate script delivers its exact body and self-cancels only when the action succeeds', () => {
788
902
  const raw = buildV21Db();
789
903
  insertLegacySchedule(raw, {
@@ -811,7 +925,7 @@ test('a migrated predicate script delivers its exact body and self-cancels only
811
925
  timeoutAt: '2026-01-04T00:00:00.000Z',
812
926
  });
813
927
  raw.close();
814
- const db = openDb();
928
+ const db = openMigratedDb();
815
929
  const script = (id) => db.prepare('SELECT command FROM crons WHERE cron_id = ?').get(id).command;
816
930
  const stored = script('conditional-message');
817
931
  const satisfied = runMigratedScript(stored, { gateReady: true });
@@ -20,9 +20,10 @@ import { join } from 'node:path';
20
20
  import { buildLaunchSpec, isProviderPinnedModelToken, normalizeModel } from '../runtime/launch.js';
21
21
  import { defaultModelLaddersConfig } from '../../types.js';
22
22
  import { persistDormantModel } from '../runtime/model-swap.js';
23
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
23
24
  import { promote, reshapeNode } from '../runtime/promote.js';
24
25
  import { createNode, getNode } from '../canvas/canvas.js';
25
- import { closeDb } from '../canvas/db.js';
26
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
26
27
  const ladders = defaultModelLaddersConfig();
27
28
  // The concrete cell the `ultra` tier normalizes to — the byte-equal collision at
28
29
  // the heart of #328. A raw `ultra` token is UNpinned; this concrete cell is
@@ -36,9 +37,11 @@ before(() => {
36
37
  home = mkdtempSync(join(tmpdir(), 'crtr-model-pin-durability-'));
37
38
  process.env['HOME'] = home; // buildLaunchSpec reads merged launch config here
38
39
  process.env['CRTR_HOME'] = home; // the canvas db lives here
40
+ initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
39
41
  });
40
42
  beforeEach(() => {
41
43
  closeDb();
44
+ migrate(openDb());
42
45
  });
43
46
  after(() => {
44
47
  closeDb();
@@ -1,10 +1,12 @@
1
1
  import { after, afterEach, before, beforeEach, test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
- import { persistModelRoute, readConfig, readMergedLaunchConfig } from '../config.js';
6
+ import { persistModelRoute, readConfig, readMergedLaunchConfig, readRawConfigAtRoot, writeRawConfig } from '../config.js';
7
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
7
8
  import { expandModelCandidates } from '../model-routes.js';
9
+ import { piAgentDir } from '../runtime/package-health.js';
8
10
  import { resetScopeCache } from '../scope.js';
9
11
  let home;
10
12
  let work;
@@ -23,6 +25,10 @@ function route(providerId = 'provider-x', family = 'family-x', credentialSource
23
25
  };
24
26
  }
25
27
  function writeConfig(root, config) {
28
+ if (root === join(home, '.crouter')) {
29
+ writeRawConfig('user', config);
30
+ return;
31
+ }
26
32
  mkdirSync(root, { recursive: true });
27
33
  writeFileSync(join(root, 'config.json'), JSON.stringify(config));
28
34
  }
@@ -34,6 +40,8 @@ beforeEach(() => {
34
40
  work = mkdtempSync(join(tmpdir(), 'crtr-model-routes-work-'));
35
41
  process.env.HOME = home;
36
42
  resetScopeCache();
43
+ initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
44
+ initializeFreshScopedStore({ kind: 'provider', root: piAgentDir() });
37
45
  });
38
46
  afterEach(() => {
39
47
  resetScopeCache();
@@ -87,7 +95,7 @@ test('route records retain __proto__ ids when read and persisted', () => {
87
95
  writeConfig(root, JSON.parse(JSON.stringify({ modelRoutes: { ['__proto__']: route() } })));
88
96
  assert.deepEqual(readMergedLaunchConfig(work, null).modelRoutes?.['__proto__'], route());
89
97
  persistModelRoute(root, '__proto__', route('persisted-provider'));
90
- const raw = JSON.parse(readFileSync(join(root, 'config.json'), 'utf8'));
98
+ const raw = readRawConfigAtRoot(root);
91
99
  assert.deepEqual(raw.modelRoutes['__proto__'], route('persisted-provider'));
92
100
  });
93
101
  test('route candidate identity distinguishes delimiter-containing route and provider ids', () => {
@@ -131,12 +139,12 @@ test('persistModelRoute touches only its raw route and deletes by restoring inhe
131
139
  const other = route('other-provider');
132
140
  writeConfig(root, { modelRoutes: { other }, max_panes_per_window: 7 });
133
141
  persistModelRoute(root, 'edited', route('edited-provider'));
134
- let raw = JSON.parse(readFileSync(join(root, 'config.json'), 'utf8'));
142
+ let raw = readRawConfigAtRoot(root);
135
143
  assert.deepEqual(raw.modelRoutes.other, other);
136
144
  assert.deepEqual(raw.modelRoutes.edited, route('edited-provider'));
137
145
  assert.equal(raw.modelLadders, undefined);
138
146
  assert.equal(raw.max_panes_per_window, 7);
139
147
  persistModelRoute(root, 'edited', undefined);
140
- raw = JSON.parse(readFileSync(join(root, 'config.json'), 'utf8'));
148
+ raw = readRawConfigAtRoot(root);
141
149
  assert.deepEqual(raw.modelRoutes, { other });
142
150
  });
@@ -4,15 +4,80 @@ import { DatabaseSync } from 'node:sqlite';
4
4
  import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { join } from 'node:path';
7
- import { closeDb, MIGRATIONS } from '../canvas/db.js';
8
- import { createNode, getRow } from '../canvas/canvas.js';
9
- import { armOutcomeDelivery, claimOutcomeDelivery, readOutcomeDelivery, recoverRunningOutcomeDeliveries, settleOutcomeDelivery, } from '../canvas/node-outcome-deliveries.js';
7
+ import { closeDb, MIGRATIONS, migrate, openDb, withCanvasWrite } from '../canvas/db.js';
8
+ import { createNode, getNode, getRow } from '../canvas/canvas.js';
9
+ import { activateArmedExternalActionDelivery, armExternalActionDelivery, claimExternalActionDelivery, messageBodyForDelivery, readExternalActionDelivery, resetHandedOffDeliveries, settleDelivery, } from '../canvas/deliveries.js';
10
10
  import { transition } from '../runtime/lifecycle.js';
11
11
  import { composeNodeOutcomeDocument } from '../runtime/outcome-document.js';
12
12
  import { pushFinal } from '../feed/feed.js';
13
13
  import { OUTCOME_PAYLOAD_MAX_BYTES } from '../canvas/types.js';
14
14
  let home;
15
15
  const priorHome = process.env['CRTR_HOME'];
16
+ function outcomeKey(nodeId) {
17
+ return `node-outcome:${nodeId}`;
18
+ }
19
+ function outcomeDelivery(nodeId) {
20
+ return readExternalActionDelivery(outcomeKey(nodeId));
21
+ }
22
+ function nodeAtOutcome(node) {
23
+ const reason = node.outcome_reason;
24
+ let status;
25
+ let intent;
26
+ if (node.outcome_kind === 'result' || reason === 'finished' || reason === 'declined') {
27
+ status = 'done';
28
+ intent = 'done';
29
+ }
30
+ else if (reason === 'parked') {
31
+ status = 'done';
32
+ intent = 'parked';
33
+ }
34
+ else if (reason === 'closed' || reason === 'retired' || reason === 'stranded' || reason === 'deadline_exceeded') {
35
+ status = 'canceled';
36
+ intent = null;
37
+ }
38
+ else {
39
+ status = 'dead';
40
+ intent = null;
41
+ }
42
+ return { ...node, status, intent, terminal_reason: reason };
43
+ }
44
+ function armOutcomeAction(input) {
45
+ const payload = input.payload ?? null;
46
+ const json = JSON.stringify(payload);
47
+ if (json === undefined)
48
+ throw new TypeError('outcome delivery payload must be JSON-serializable');
49
+ if (Buffer.byteLength(json, 'utf8') > OUTCOME_PAYLOAD_MAX_BYTES)
50
+ throw new RangeError(`outcome delivery payload exceeds ${OUTCOME_PAYLOAD_MAX_BYTES} bytes`);
51
+ return withCanvasWrite((db) => {
52
+ const existing = outcomeDelivery(input.nodeId);
53
+ if (existing !== null && existing.state !== 'armed')
54
+ return null;
55
+ armExternalActionDelivery(db, {
56
+ destinationId: input.actionName,
57
+ adapter: { adapter: 'subprocess', argv: input.argv, cwd: input.cwd, config_origin: input.configOrigin, payload },
58
+ idempotencyKey: outcomeKey(input.nodeId),
59
+ }, input.now);
60
+ const node = getNode(input.nodeId);
61
+ if (node === null)
62
+ throw new Error(`unknown node ${input.nodeId}`);
63
+ if (node.outcome_at !== null) {
64
+ if (node.outcome_kind === null || node.outcome_reason === null)
65
+ throw new Error(`outcome latch is incomplete for node ${input.nodeId}`);
66
+ activateArmedExternalActionDelivery(db, {
67
+ idempotencyKey: outcomeKey(input.nodeId),
68
+ label: input.nodeId,
69
+ bodyJson: JSON.stringify(composeNodeOutcomeDocument({
70
+ node: nodeAtOutcome(node),
71
+ outcome: { kind: node.outcome_kind, reason: node.outcome_reason, at: node.outcome_at, revision: node.outcome_revision, detailJson: node.outcome_detail_json ?? null },
72
+ action: { name: input.actionName, payload },
73
+ finalReport: node.final_report,
74
+ })),
75
+ supersede: false,
76
+ }, input.now);
77
+ }
78
+ return outcomeDelivery(input.nodeId);
79
+ });
80
+ }
16
81
  function node(nodeId) {
17
82
  return {
18
83
  node_id: nodeId,
@@ -29,6 +94,7 @@ beforeEach(() => {
29
94
  closeDb();
30
95
  home = mkdtempSync(join(tmpdir(), 'crtr-node-outcome-'));
31
96
  process.env['CRTR_HOME'] = home;
97
+ migrate(openDb());
32
98
  });
33
99
  afterEach(() => {
34
100
  closeDb();
@@ -105,23 +171,24 @@ test('a canonical final supersedes a prior failure outcome and increments revisi
105
171
  });
106
172
  test('outcome delivery rejects an oversized payload before a node is armed', () => {
107
173
  createNode(node('oversized-payload'));
108
- assert.throws(() => armOutcomeDelivery({
174
+ assert.throws(() => armOutcomeAction({
109
175
  nodeId: 'oversized-payload', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { value: 'x'.repeat(OUTCOME_PAYLOAD_MAX_BYTES) },
110
176
  }), /exceeds/);
111
- assert.equal(readOutcomeDelivery('oversized-payload'), null);
177
+ assert.equal(outcomeDelivery('oversized-payload'), null);
112
178
  });
113
179
  test('outcome delivery armed after settlement is born pending with its document', () => {
114
180
  createNode(node('settled-delivery'));
115
181
  transition('settled-delivery', 'crash');
116
- const delivery = armOutcomeDelivery({
182
+ const delivery = armOutcomeAction({
117
183
  nodeId: 'settled-delivery', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { callback: 'one' }, now: 123,
118
184
  });
119
185
  assert.equal(delivery?.state, 'pending');
120
186
  assert.equal(delivery?.nextAttemptAt, 123);
121
- assert.deepEqual(delivery?.document && {
122
- schema: delivery.document.schema,
123
- outcomeId: delivery.document.outcomeId,
124
- action: delivery.document.action,
187
+ const document = delivery === null ? null : messageBodyForDelivery(delivery.deliveryId);
188
+ assert.deepEqual(document && {
189
+ schema: document.schema,
190
+ outcomeId: document.outcomeId,
191
+ action: document.action,
125
192
  }, {
126
193
  schema: 'crtr.node-outcome/v1',
127
194
  outcomeId: 'settled-delivery:1',
@@ -130,28 +197,28 @@ test('outcome delivery armed after settlement is born pending with its document'
130
197
  });
131
198
  test('outcome delivery state is fenced, recoverable, and follows the durable backoff', () => {
132
199
  createNode(node('delivery'));
133
- assert.ok(armOutcomeDelivery({
200
+ assert.ok(armOutcomeAction({
134
201
  nodeId: 'delivery', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { callback: 'one' }, now: 100,
135
202
  }));
136
203
  transition('delivery', 'crash');
137
- assert.equal(readOutcomeDelivery('delivery')?.state, 'pending');
138
- assert.ok(readOutcomeDelivery('delivery')?.document, 'the armed row is stamped with its outcome document');
139
- const first = claimOutcomeDelivery('delivery', Date.now(), 'daemon-a');
140
- assert.equal(first.state, 'running');
141
- assert.equal(settleOutcomeDelivery('delivery', first.attempt, 'daemon-b', { state: 'accepted' }, Date.now()), false, 'a stale claimant cannot settle');
142
- assert.equal(settleOutcomeDelivery('delivery', first.attempt, 'daemon-a', { state: 'accepted' }, Date.now()), true);
143
- assert.equal(readOutcomeDelivery('delivery')?.state, 'accepted');
204
+ assert.equal(outcomeDelivery('delivery')?.state, 'pending');
205
+ assert.ok(messageBodyForDelivery(outcomeDelivery('delivery').deliveryId), 'the armed row is stamped with its outcome document');
206
+ const first = claimExternalActionDelivery(outcomeDelivery('delivery').deliveryId, Date.now(), 'daemon-a');
207
+ assert.equal(first.state, 'handed_off');
208
+ assert.equal(settleDelivery(first.deliveryId, first.attempts, 'daemon-b', { state: 'accepted' }, Date.now()), false, 'a stale claimant cannot settle');
209
+ assert.equal(settleDelivery(first.deliveryId, first.attempts, 'daemon-a', { state: 'accepted' }, Date.now()), true);
210
+ assert.equal(outcomeDelivery('delivery')?.state, 'acknowledged');
144
211
  createNode(node('recover'));
145
- armOutcomeDelivery({ nodeId: 'recover', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', now: 100 });
212
+ armOutcomeAction({ nodeId: 'recover', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', now: 100 });
146
213
  transition('recover', 'crash');
147
- const running = claimOutcomeDelivery('recover', Date.now(), 'daemon-a');
148
- assert.equal(running.state, 'running');
214
+ const running = claimExternalActionDelivery(outcomeDelivery('recover').deliveryId, Date.now(), 'daemon-a');
215
+ assert.equal(running.state, 'handed_off');
149
216
  const recoveredAt = Date.now();
150
- assert.equal(recoverRunningOutcomeDeliveries(recoveredAt), 1);
151
- assert.deepEqual(readOutcomeDelivery('recover') && {
152
- state: readOutcomeDelivery('recover').state,
153
- nextAttemptAt: readOutcomeDelivery('recover').nextAttemptAt,
154
- claimOwner: readOutcomeDelivery('recover').claimOwner,
217
+ assert.equal(resetHandedOffDeliveries(recoveredAt), 1);
218
+ assert.deepEqual(outcomeDelivery('recover') && {
219
+ state: outcomeDelivery('recover').state,
220
+ nextAttemptAt: outcomeDelivery('recover').nextAttemptAt,
221
+ claimOwner: outcomeDelivery('recover').claimOwner,
155
222
  }, { state: 'pending', nextAttemptAt: recoveredAt, claimOwner: null });
156
223
  });
157
224
  test('v39 migrates a v38 database and backfills finalized and terminal rows', () => {
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
10
10
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { join } from 'node:path';
13
- import { closeDb } from '../canvas/db.js';
13
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
14
14
  import { resetScopeCache } from '../scope.js';
15
15
  import { spawnNode } from '../runtime/nodes.js';
16
16
  import { renderOnReadDocs } from '../substrate/on-read-node.js';
@@ -32,6 +32,7 @@ beforeEach(() => {
32
32
  // touch the fake user home; the read path we test is constructed by hand.
33
33
  canvasHome = mkdtempSync(join(tmpdir(), 'crtr-fence-canvas-'));
34
34
  process.env['CRTR_HOME'] = canvasHome;
35
+ migrate(openDb());
35
36
  });
36
37
  after(() => {
37
38
  closeDb();
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
10
10
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { join } from 'node:path';
13
- import { closeDb } from '../canvas/db.js';
13
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
14
14
  import { resetScopeCache } from '../scope.js';
15
15
  import { spawnNode } from '../runtime/nodes.js';
16
16
  import { renderOnReadDocs } from '../substrate/on-read-node.js';
@@ -44,6 +44,7 @@ beforeEach(() => {
44
44
  else
45
45
  process.env['HOME'] = prevHomeEnv;
46
46
  work = mkdtempSync(join(tmpdir(), 'crtr-onread-work-'));
47
+ migrate(openDb());
47
48
  });
48
49
  after(() => {
49
50
  closeDb();