@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
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
4
4
  import { contextDir } from '../../canvas/paths.js';
5
5
  import { getRow } from '../../canvas/canvas.js';
6
6
  import { bashJobPaths, bashJobsDir } from '../../bash-jobs.js';
7
- import { readCursor } from '../../feed/inbox.js';
7
+ import { acknowledgedNodeMailCount } from '../../canvas/deliveries.js';
8
8
  import { transition } from '../../runtime/lifecycle.js';
9
9
  import { ensureRootProfile, pauseProfile } from '../../profiles/manifest.js';
10
10
  import { createHeadlessHarness } from '../helpers/harness.js';
@@ -22,7 +22,7 @@ function runningJob(nodeId) {
22
22
  }
23
23
  return undefined;
24
24
  }
25
- test('profile pause backgrounds real bash, retains its inbox cursor, and recovers after a quick resume without failure', { timeout: 120_000 }, async () => {
25
+ test('profile pause backgrounds real bash, retains its queued SQL delivery, and recovers after a quick resume without failure', { timeout: 120_000 }, async () => {
26
26
  const h = await createHeadlessHarness({ sessionPrefix: 'crtr-profile-pause' });
27
27
  try {
28
28
  const profile = ensureRootProfile();
@@ -39,7 +39,7 @@ test('profile pause backgrounds real bash, retains its inbox cursor, and recover
39
39
  assert.equal(retained.code, 0, retained.stderr);
40
40
  const retainedEntry = h.inbox(child).find((entry) => entry.data?.['body'] === 'mail retained while profile is paused');
41
41
  assert.ok(retainedEntry, 'the message was durably appended');
42
- assert.equal(readCursor(child), undefined, 'pause leaves the durable inbox cursor unadvanced');
42
+ assert.equal(acknowledgedNodeMailCount(child), 0, 'pause leaves the queued SQL delivery unacknowledged');
43
43
  const resumed = h.cli(null, ['profile', 'resume', profile.profileId]);
44
44
  assert.equal(resumed.code, 0, resumed.stderr);
45
45
  await h.awaitFleetExit(child);
@@ -47,7 +47,7 @@ test('profile pause backgrounds real bash, retains its inbox cursor, and recover
47
47
  assert.equal(existsSync(job.jobRun), false, 'the foreground valve ownership is gone');
48
48
  await h.waitFor(() => existsSync(job.jobExit), { label: 'background bash completion' });
49
49
  assert.equal(readFileSync(job.jobExit, 'utf8').trim(), '0', 'the handoff command completed after its broker exited');
50
- assert.equal(readCursor(child), undefined, 'neither retained message nor background completion advanced the stopped broker cursor');
50
+ assert.equal(acknowledgedNodeMailCount(child), 0, 'neither retained message nor background completion acknowledges the stopped broker delivery');
51
51
  await h.tick();
52
52
  assert.equal(h.bootCount(child), 1, 'the first resumed policy pass only consumes the pause interruption');
53
53
  assert.equal(getRow(child)?.respawn_failures, 0, 'the intentional SIGTERM never counts as a crash');
@@ -21,13 +21,10 @@
21
21
  import { test } from 'node:test';
22
22
  import assert from 'node:assert/strict';
23
23
  import { spawn } from 'node:child_process';
24
- import { utimesSync } from 'node:fs';
25
- import { join } from 'node:path';
26
24
  import { createHeadlessHarness } from '../helpers/harness.js';
27
- import { getRow, recordLaunch, recordPid } from '../../canvas/canvas.js';
28
- import { jobDir } from '../../canvas/paths.js';
25
+ import { getRow } from '../../canvas/canvas.js';
26
+ import { abortExecution, beginExecution, recordExecutionPid } from '../../canvas/executions.js';
29
27
  import { isPidAlive } from '../../canvas/pid.js';
30
- import { markBusy } from '../../runtime/busy.js';
31
28
  import { BrokerSupervisionReconciler, yieldStallVerdict, YIELD_STALL_GRACE_MS } from '../../../daemon/reconcilers/broker-supervision.js';
32
29
  // D4a — the pure refresh-stall truth table.
33
30
  test('D4a yieldStallVerdict: kill ONLY a live, not-busy, refresh engine past the grace', () => {
@@ -61,7 +58,8 @@ test('D4b daemon force-recycles a stuck refresh: a live, not-busy refresh engine
61
58
  // A live broker records its launch-time identity with its pid. The force
62
59
  // signal re-verifies that identity, so this fixture must model the
63
60
  // production record rather than a legacy identity-less row.
64
- recordPid(id, enginePid);
61
+ const executionId = beginExecution(id, { daemonEpoch: h.fleet.epoch() });
62
+ recordExecutionPid(executionId, id, enginePid);
65
63
  // Post-cutover the live-fleet pass iterates fleet ENTRIES, not rows: a
66
64
  // live broker IS a fleet entry (design D-1). Model the launch-time
67
65
  // registration the production host performs, with a never-resolving
@@ -103,8 +101,8 @@ test('D4c yield escalation is scoped to the broker that received SIGTERM', { tim
103
101
  pi_pid: original.pid,
104
102
  pi_session_id: 'sess-d4c',
105
103
  });
106
- recordLaunch(id, new Date(T0).toISOString());
107
- recordPid(id, original.pid);
104
+ const originalExecution = beginExecution(id, { daemonEpoch: h.fleet.epoch() });
105
+ recordExecutionPid(originalExecution, id, original.pid);
108
106
  h.fleet.register(id, { pid: original.pid, exited: new Promise(() => { }) });
109
107
  const supervisor = new BrokerSupervisionReconciler();
110
108
  const context = () => ({
@@ -119,15 +117,14 @@ test('D4c yield escalation is scoped to the broker that received SIGTERM', { tim
119
117
  timeoutMs: 10_000,
120
118
  });
121
119
  h.fleet.forget(id);
122
- markBusy(id); // interrupted predecessor witness must not block the replacement's refresh timer
123
- utimesSync(join(jobDir(id), 'busy'), T_KILL / 1_000, T_KILL / 1_000);
120
+ abortExecution(originalExecution, { reason: 'test replacement' });
124
121
  replacement = spawn(process.execPath, ['-e', 'process.on("SIGTERM", () => {}); console.log("ready"); setInterval(() => {}, 1000)'], { stdio: ['ignore', 'pipe', 'ignore'] });
125
122
  await new Promise((resolve, reject) => {
126
123
  replacement.stdout.once('data', () => resolve());
127
124
  replacement.once('error', reject);
128
125
  });
129
- recordLaunch(id, new Date(T_KILL + 1).toISOString());
130
- recordPid(id, replacement.pid);
126
+ const replacementExecution = beginExecution(id, { daemonEpoch: h.fleet.epoch() });
127
+ recordExecutionPid(replacementExecution, id, replacement.pid);
131
128
  h.fleet.register(id, { pid: replacement.pid, exited: new Promise(() => { }) });
132
129
  const replacementStarted = T_KILL + KILL_ESCALATE_MS + 1;
133
130
  await supervisor.run(replacementStarted, context());
@@ -34,8 +34,12 @@ import { join } from 'node:path';
34
34
  import { spawn, spawnSync } from 'node:child_process';
35
35
  import { createNode, deleteNode, getNode, subscribe, updateNode } from '../../canvas/canvas.js';
36
36
  import { armCron, getCron } from '../../canvas/crons.js';
37
- import { readInboxSince } from '../../feed/inbox.js';
38
- import { closeDb } from '../../canvas/db.js';
37
+ import { closeDb, migrate, openDb } from '../../canvas/db.js';
38
+ import { closeScopedStores } from '../../scoped-state/db.js';
39
+ import { initializeFreshScopedStore } from '../../scoped-state/migrate.js';
40
+ import { userScopeRoot } from '../../scope.js';
41
+ import { resetSession } from '../../canvas/sessions.js';
42
+ import { liveExecutionForNode } from '../../canvas/executions.js';
39
43
  import { apiSocketPath } from '../../canvas/paths.js';
40
44
  import { buildPiArgv } from '../../runtime/launch.js';
41
45
  import { headlessBrokerHost } from '../../runtime/host.js';
@@ -45,12 +49,15 @@ import { parseCard } from '../../../shared/generated-context.js';
45
49
  import { seedRoadmap } from '../../runtime/roadmap.js';
46
50
  import { InputError } from '../../io.js';
47
51
  import { CrtrClient } from '../../../api/client.js';
48
- import { readFault } from '../../runtime/fault.js';
52
+ import { readFault } from '../../canvas/recovery.js';
49
53
  import { createHarness } from '../helpers/harness.js';
50
54
  import { bindTestFleet } from '../helpers/fleet.js';
51
55
  import { resetFleetForTests } from '../../runtime/fleet.js';
52
56
  import { emptyContextExposureState, exposureTarget, initializeForkContextExposure, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../substrate/injected-store.js';
53
57
  let home;
58
+ let scopeCwd;
59
+ let originalCwd;
60
+ let originalHome;
54
61
  function node(id, over = {}) {
55
62
  return {
56
63
  node_id: id,
@@ -64,6 +71,26 @@ function node(id, over = {}) {
64
71
  ...over,
65
72
  };
66
73
  }
74
+ function fabricateInScope(h, options = {}) {
75
+ const id = h.fabricateBrokerNode(options);
76
+ updateNode(id, { cwd: scopeCwd });
77
+ return id;
78
+ }
79
+ function createNodeWithSession(meta) {
80
+ createNode(meta);
81
+ if (meta.pi_session_id !== undefined || meta.pi_session_file !== undefined) {
82
+ resetSession(meta.node_id, { sessionId: meta.pi_session_id ?? '', transcriptPath: meta.pi_session_file ?? null });
83
+ }
84
+ }
85
+ function wakeMail(nodeId) {
86
+ return openDb().prepare(`SELECT m.sender_node_id AS "from", m.body_json AS data
87
+ FROM deliveries d JOIN messages m ON m.message_id = d.message_id
88
+ WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.wake = 1
89
+ ORDER BY m.seq`).all(nodeId).map((row) => {
90
+ const mail = row;
91
+ return { from: mail.from, data: mail.data === null ? null : JSON.parse(mail.data) };
92
+ });
93
+ }
67
94
  /** A launch prompt is the fresh-revive kickoff when it carries that card kind. */
68
95
  function isReviveKickoff(arg) {
69
96
  return parseCard({ role: 'user', content: arg })?.kind === 'revive';
@@ -88,16 +115,33 @@ before(() => {
88
115
  // to /private/var, and the runtime resolves a fork's durable source file, so a
89
116
  // raw temp path would compare unequal to the very path the runtime replays.
90
117
  home = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-revive-')));
118
+ scopeCwd = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-revive-scope-')));
119
+ originalCwd = process.cwd();
120
+ originalHome = process.env['HOME'];
121
+ process.chdir(scopeCwd);
122
+ process.env['HOME'] = scopeCwd;
91
123
  process.env['CRTR_HOME'] = home;
92
124
  });
93
125
  beforeEach(() => {
94
126
  closeDb();
127
+ closeScopedStores();
95
128
  rmSync(home, { recursive: true, force: true });
129
+ rmSync(userScopeRoot(), { recursive: true, force: true });
130
+ migrate(openDb());
131
+ initializeFreshScopedStore({ kind: 'user', root: userScopeRoot() });
96
132
  });
97
133
  after(() => {
98
134
  closeDb();
135
+ closeScopedStores();
99
136
  rmSync(home, { recursive: true, force: true });
137
+ rmSync(userScopeRoot(), { recursive: true, force: true });
100
138
  delete process.env['CRTR_HOME'];
139
+ process.chdir(originalCwd);
140
+ rmSync(scopeCwd, { recursive: true, force: true });
141
+ if (originalHome === undefined)
142
+ delete process.env['HOME'];
143
+ else
144
+ process.env['HOME'] = originalHome;
101
145
  for (const pid of livePids) {
102
146
  try {
103
147
  process.kill(pid, 'SIGKILL');
@@ -188,7 +232,7 @@ test('a resumed revive replays the persisted LaunchSpec (model, tools, extension
188
232
  env: { FOO: 'bar' },
189
233
  },
190
234
  });
191
- createNode(m); // scaffolds the node dir so the system prompt persists to a file
235
+ createNodeWithSession(m); // scaffolds the node dir so the system prompt persists to a file
192
236
  const { argv, env } = buildPiArgv(m, resumeArgs(m, true));
193
237
  // Persona extensions, in order.
194
238
  const ea = argv.indexOf('/ext/a.js');
@@ -223,7 +267,7 @@ test('reviveNode no-ops when the node has a live fleet entry — double-revive g
223
267
  // synchronously, so a live entry means a broker is already on this .jsonl
224
268
  // and re-launching would double-spawn onto it. No pid probe is involved
225
269
  // anywhere — the entry alone is the authority.
226
- const M = h.fabricateBrokerNode({
270
+ const M = fabricateInScope(h, {
227
271
  status: 'active',
228
272
  intent: null,
229
273
  pi_pid: deadPid(),
@@ -251,7 +295,7 @@ test('reviveNode PROCEEDS when the node has NO fleet entry — cycle counter bum
251
295
  const stalePid = deadPid();
252
296
  mkdirSync(join(home, 'sessions'), { recursive: true });
253
297
  writeFileSync(sessionFile, '{}\n', 'utf8');
254
- const M = h.fabricateBrokerNode({
298
+ const M = fabricateInScope(h, {
255
299
  status: 'active',
256
300
  intent: null,
257
301
  pi_pid: stalePid,
@@ -301,11 +345,11 @@ function armDeadline(anchor, cronId) {
301
345
  test('a recovery relaunch keeps the armed deadline; a wake revive consumes it', async () => {
302
346
  const h = await createHarness({ headless: true, sessionPrefix: 'crtr-revive-deadline' });
303
347
  try {
304
- const recovered = h.fabricateBrokerNode({ status: 'active', intent: null, pi_pid: deadPid(), pi_session_id: 'uuid-1' });
348
+ const recovered = fabricateInScope(h, { status: 'active', intent: null, pi_pid: deadPid(), pi_session_id: 'uuid-1' });
305
349
  armDeadline(recovered, 'cron-recovery');
306
350
  await reviveNode(recovered, { resume: true, recovery: true, capacity: 'freeze' });
307
351
  assert.equal(getCron('cron-recovery') !== null, true, 'the daemon replacing a dead broker is not a wake — the deadline survives so the wait can still settle');
308
- const woken = h.fabricateBrokerNode({ status: 'active', intent: null, pi_pid: deadPid(), pi_session_id: 'uuid-2' });
352
+ const woken = fabricateInScope(h, { status: 'active', intent: null, pi_pid: deadPid(), pi_session_id: 'uuid-2' });
309
353
  armDeadline(woken, 'cron-wake');
310
354
  await reviveNode(woken, { resume: true, capacity: 'freeze' });
311
355
  assert.equal(getCron('cron-wake'), null, 'a wake revive won the race and cancels the deadline');
@@ -320,15 +364,17 @@ test('a second broker session-bound request is shut down instead of adopting a l
320
364
  const sessionFile = join(home, 'sessions', 'owned.jsonl');
321
365
  mkdirSync(join(home, 'sessions'), { recursive: true });
322
366
  writeFileSync(sessionFile, '{}\n', 'utf8');
323
- const owner = h.fabricateBrokerNode({
367
+ const owner = fabricateInScope(h, {
324
368
  id: 'live-session-owner',
325
369
  pi_pid: process.pid,
326
370
  pi_session_id: 'owner-session',
327
371
  pi_session_file: sessionFile,
328
372
  });
329
- const claimant = h.fabricateBrokerNode({ id: 'session-claimant', pi_pid: deadPid() });
373
+ const claimant = fabricateInScope(h, { id: 'session-claimant', pi_pid: process.pid });
330
374
  const client = new CrtrClient({ socketPath: apiSocketPath(), autostart: false });
375
+ assert.equal(getNode(owner)?.pi_pid, process.pid, 'the owner has a live SQL execution');
331
376
  const result = await client.bindBrokerSession(claimant, {
377
+ executionId: liveExecutionForNode(claimant).executionId,
332
378
  piSessionId: 'claimant-session', sessionFile, pid: process.pid, reason: 'startup', reviewBoundaryIds: [],
333
379
  });
334
380
  assert.deepEqual(result, { action: 'shutdown' }, 'the second broker is refused before it can bind the live transcript');
@@ -351,7 +397,7 @@ test('reviveNode fresh-fallback clears the stale session identity (Major-3: no p
351
397
  // retried forever instead of surfaced as a boot failure.
352
398
  const missingFile = join(home, 'sessions', 'never-written.jsonl');
353
399
  const stalePid = deadPid();
354
- const M = h.fabricateBrokerNode({
400
+ const M = fabricateInScope(h, {
355
401
  status: 'active',
356
402
  intent: null,
357
403
  pi_pid: stalePid,
@@ -378,7 +424,7 @@ test('unbound forks replay one durable source file for node-id, path, and UUID o
378
424
  try {
379
425
  mkdirSync(home, { recursive: true });
380
426
  writeFileSync(forkFile, '{}\n', 'utf8');
381
- createNode(node('ordinary-fork-source', { cwd: home, pi_session_file: forkFile }));
427
+ createNodeWithSession(node('ordinary-fork-source', { cwd: home, pi_session_file: forkFile }));
382
428
  const sourceExposure = emptyContextExposureState();
383
429
  registerExposure(exposureTarget(sourceExposure, 'system'), 'source-system-only', 'content');
384
430
  registerExposure(exposureTarget(sourceExposure, 'transcript'), 'source-transcript', 'content');
@@ -392,7 +438,7 @@ test('unbound forks replay one durable source file for node-id, path, and UUID o
392
438
  const operands = ['ordinary-fork-source', forkFile, '019e8ce3-322e-7000-8000-000000000001'];
393
439
  for (const [index, operand] of operands.entries()) {
394
440
  const destination = `ordinary-fork-${index}`;
395
- createNode(node(destination, {
441
+ createNodeWithSession(node(destination, {
396
442
  cwd: home,
397
443
  fork_from: operand,
398
444
  fork_source_file: forkFile,
@@ -431,7 +477,7 @@ test('reviveNode throws a typed cwd_missing error and does NOT crash the node wh
431
477
  const h = await createHarness({ headless: true, sessionPrefix: 'crtr-revive-cwdmissing' });
432
478
  try {
433
479
  const missingCwd = join(home, 'never-created-cwd');
434
- const M = h.fabricateBrokerNode({
480
+ const M = fabricateInScope(h, {
435
481
  status: 'active',
436
482
  intent: null,
437
483
  pi_pid: deadPid(),
@@ -452,7 +498,7 @@ test('reviveNode relaunches fresh when the session header cwd was removed (NL-14
452
498
  const sessionFile = join(home, 'sessions', 'stale-cwd.jsonl');
453
499
  mkdirSync(join(home, 'sessions'), { recursive: true });
454
500
  writeFileSync(sessionFile, `${JSON.stringify({ type: 'session', version: 3, id: 'stale-session', cwd: removedCwd })}\n`, 'utf8');
455
- const M = h.fabricateBrokerNode({
501
+ const M = fabricateInScope(h, {
456
502
  status: 'active', intent: null, pi_pid: deadPid(), pi_session_id: 'stale-session', pi_session_file: sessionFile,
457
503
  });
458
504
  const result = await reviveNode(M, { resume: true, capacity: 'freeze', recovery: true });
@@ -471,7 +517,7 @@ test('reviveNode crashes and clears pid when launch/preflight throws', async ()
471
517
  const sessionFile = join(home, 'sessions', 'crtr-revive-fail.jsonl');
472
518
  mkdirSync(join(home, 'sessions'), { recursive: true });
473
519
  writeFileSync(sessionFile, '{}\n', 'utf8');
474
- const M = h.fabricateBrokerNode({
520
+ const M = fabricateInScope(h, {
475
521
  status: 'active',
476
522
  intent: null,
477
523
  pi_pid: deadPid(),
@@ -500,7 +546,7 @@ test('reviveNode fans a doctrine wake to subscribers when launch/preflight throw
500
546
  const sessionFile = join(home, 'sessions', 'crtr-revive-crashwake.jsonl');
501
547
  mkdirSync(join(home, 'sessions'), { recursive: true });
502
548
  writeFileSync(sessionFile, '{}\n', 'utf8');
503
- const M = h.fabricateBrokerNode({
549
+ const M = fabricateInScope(h, {
504
550
  parent: root,
505
551
  status: 'active',
506
552
  intent: null,
@@ -514,7 +560,7 @@ test('reviveNode fans a doctrine wake to subscribers when launch/preflight throw
514
560
  headlessBrokerHost.launch = (_nodeId, _inv, _opts) => { throw new Error('boom'); };
515
561
  await assert.rejects(reviveNode(M, { resume: true, capacity: 'freeze' }), /refusing to revive/i);
516
562
  assert.equal(h.node(M).status, 'dead', 'failed revive marks the node dead');
517
- const entries = readInboxSince(root, undefined);
563
+ const entries = wakeMail(root);
518
564
  const notice = entries.find((e) => e.data?.['reason'] === 'child-crashed' && e.data?.['child'] === M);
519
565
  assert.ok(notice !== undefined, 'the surviving parent got a doctrine wake for the dead child');
520
566
  assert.equal(notice.from, M, 'the notice is attributed to the crashed child');
@@ -546,18 +592,18 @@ test('an unstarted node relaunches as a birth, not a "revived fresh" kickoff', a
546
592
  };
547
593
  const missing = join(home, 'sessions', 'never-written.jsonl');
548
594
  // (1) Bare interactive node — nothing on disk anywhere: launch promptless.
549
- createNode(node('bare-unstarted', { cwd: home, pi_session_id: 'uuid-a', pi_session_file: missing }));
595
+ createNodeWithSession(node('bare-unstarted', { cwd: home, pi_session_id: 'uuid-a', pi_session_file: missing }));
550
596
  await reviveNode('bare-unstarted', { resume: true, capacity: 'freeze' });
551
597
  const bare = launches[0];
552
598
  assert.equal(bare.some((a) => isReviveKickoff(a)), false, 'an unstarted node is never told it lost context it never had');
553
599
  assert.deepEqual(bare, buildPiArgv(getNode('bare-unstarted'), {}).argv, 'a promptless boot — the argv carries no trailing prompt positional at all');
554
600
  // (2) Spawned with a mandate whose first turn never persisted: replay it.
555
- createNode(node('mandate-unstarted', { cwd: home, pi_session_id: 'uuid-b', pi_session_file: missing }));
601
+ createNodeWithSession(node('mandate-unstarted', { cwd: home, pi_session_id: 'uuid-b', pi_session_file: missing }));
556
602
  writeGoal('mandate-unstarted', 'do the thing');
557
603
  await reviveNode('mandate-unstarted', { resume: true, capacity: 'freeze' });
558
604
  assert.equal(launches[1].at(-1), 'do the thing', 'a birth replays the spawn mandate');
559
605
  // (3) Real durable progress on disk → still a genuine fresh revive.
560
- createNode(node('progressed', { cwd: home, pi_session_id: 'uuid-c', pi_session_file: missing }));
606
+ createNodeWithSession(node('progressed', { cwd: home, pi_session_id: 'uuid-c', pi_session_file: missing }));
561
607
  seedRoadmap('progressed', { goal: 'ship it' });
562
608
  await reviveNode('progressed', { resume: true, capacity: 'freeze' });
563
609
  assert.equal(isReviveKickoff(launches[2].at(-1)), true, 'a node with a roadmap still rebuilds from the kickoff');
@@ -9,9 +9,13 @@ import { join } from 'node:path';
9
9
  import { createServer } from 'node:net';
10
10
  import { spawn as spawnProcess } from 'node:child_process';
11
11
  import { createNode, getNode, getRow, subscribersOf } from '../../canvas/canvas.js';
12
- import { coalesce, readInboxSince } from '../../feed/inbox.js';
12
+ import { coalesce } from '../../feed/inbox.js';
13
13
  import { coalesceBrokerInbox } from '../../runtime/broker/inbox.js';
14
- import { closeDb } from '../../canvas/db.js';
14
+ import { closeDb, migrate, openDb } from '../../canvas/db.js';
15
+ import { resetSession } from '../../canvas/sessions.js';
16
+ import { closeScopedStores } from '../../scoped-state/db.js';
17
+ import { initializeFreshScopedStore } from '../../scoped-state/migrate.js';
18
+ import { userScopeRoot } from '../../scope.js';
15
19
  import { nodeDir } from '../../canvas/paths.js';
16
20
  import { spawnNode } from '../../runtime/nodes.js';
17
21
  import { readGoal } from '../../runtime/kickoff.js';
@@ -19,12 +23,35 @@ import { spawnChild, resolveSpawner } from '../../runtime/spawn.js';
19
23
  import { headlessBrokerHost } from '../../runtime/host.js';
20
24
  import { resetFleetForTests } from '../../runtime/fleet.js';
21
25
  import { bindTestFleet } from '../helpers/fleet.js';
22
- import { recordFault } from '../../runtime/fault.js';
26
+ import { recordFault } from '../../canvas/recovery.js';
23
27
  import { piSessionsRoot } from '../../runtime/pi-vendored.js';
24
28
  import { emptyContextExposureState, exposureTarget, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../substrate/injected-store.js';
25
29
  import { ROOT_PROFILE_ID } from '../../profiles/manifest.js';
26
30
  let home;
31
+ let scopedHome;
32
+ let previousHome;
27
33
  let previousPidfile;
34
+ function initializeTestUserScope() {
35
+ initializeFreshScopedStore({ kind: 'user', root: userScopeRoot() });
36
+ }
37
+ function mailFor(nodeId) {
38
+ return openDb().prepare(`SELECT m.message_id, m.operation_id, m.created_at, m.sender_node_id, m.sender_name, m.kind, m.label, m.body_ref, m.body_json, m.disposition, d.urgency
39
+ FROM deliveries d JOIN messages m ON m.message_id = d.message_id
40
+ WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.canceled_at IS NULL
41
+ ORDER BY m.seq`).all(nodeId).map((row) => ({
42
+ entry_id: row['message_id'],
43
+ operation_id: row['operation_id'],
44
+ ts: row['created_at'],
45
+ from: row['sender_node_id'],
46
+ ...(row['sender_name'] === null ? {} : { from_name: row['sender_name'] }),
47
+ tier: row['urgency'],
48
+ kind: row['kind'],
49
+ ...(row['body_ref'] === null ? {} : { ref: row['body_ref'] }),
50
+ label: row['label'],
51
+ ...(row['body_json'] === null ? {} : { data: JSON.parse(row['body_json']) }),
52
+ ...(row['disposition'] === null ? {} : { disposition: row['disposition'] }),
53
+ }));
54
+ }
28
55
  function spawner(id) {
29
56
  return {
30
57
  node_id: id,
@@ -41,6 +68,9 @@ before(() => {
41
68
  // Unix-domain socket paths are capped at ~104 bytes on macOS; keep the
42
69
  // canvas home short because root readiness now opens a real view.sock.
43
70
  home = mkdtempSync('/tmp/crtr-spawn-root-');
71
+ scopedHome = mkdtempSync('/tmp/crtr-spawn-root-scope-');
72
+ previousHome = process.env['HOME'];
73
+ process.env['HOME'] = scopedHome;
44
74
  process.env['CRTR_HOME'] = home;
45
75
  previousPidfile = process.env['CRTR_PIDFILE'];
46
76
  process.env['CRTR_PIDFILE'] = join(home, 'crtrd.pid');
@@ -50,8 +80,12 @@ before(() => {
50
80
  });
51
81
  beforeEach(() => {
52
82
  closeDb();
83
+ closeScopedStores();
53
84
  rmSync(home, { recursive: true, force: true });
85
+ rmSync(userScopeRoot(), { recursive: true, force: true });
54
86
  mkdirSync(home, { recursive: true });
87
+ migrate(openDb());
88
+ initializeTestUserScope();
55
89
  // These tests exercise direct spawn behavior, not daemon autostart. Keep
56
90
  // ensureDaemon() from launching a background daemon whose startup verifier
57
91
  // would outlive the test's next CRTR_HOME reset.
@@ -59,8 +93,14 @@ beforeEach(() => {
59
93
  });
60
94
  after(() => {
61
95
  closeDb();
96
+ closeScopedStores();
62
97
  resetFleetForTests();
63
98
  rmSync(home, { recursive: true, force: true });
99
+ rmSync(scopedHome, { recursive: true, force: true });
100
+ if (previousHome === undefined)
101
+ delete process.env['HOME'];
102
+ else
103
+ process.env['HOME'] = previousHome;
64
104
  if (previousPidfile === undefined)
65
105
  delete process.env['CRTR_PIDFILE'];
66
106
  else
@@ -84,7 +124,7 @@ test('external child birth is deferred, persists its creator, and renders its ag
84
124
  const child = await spawnNode({ kind: 'developer', cwd: '/tmp/work', parent: 'A', creator: creator.node_id, name: 'worker' });
85
125
  const saved = getNode(child.node_id);
86
126
  assert.equal(saved.creator, creator.node_id, 'creator persists through meta.json and getNode');
87
- const [entry] = readInboxSince('A');
127
+ const [entry] = mailFor('A');
88
128
  assert.ok(entry, 'the parent receives exactly one birth entry');
89
129
  assert.equal(entry.from, child.node_id, 'birth groups with the child’s future reports');
90
130
  assert.equal(entry.tier, 'deferred', 'birth never starts a parent cycle');
@@ -107,9 +147,9 @@ test('external child birth is deferred, persists its creator, and renders its ag
107
147
  }
108
148
  }
109
149
  await spawnNode({ kind: 'developer', cwd: '/tmp/work', parent: 'A', creator: 'A' });
110
- assert.equal(readInboxSince('A').length, 1, 'a parent-created child stays silent');
150
+ assert.equal(mailFor('A').length, 1, 'a parent-created child stays silent');
111
151
  await spawnNode({ kind: 'developer', cwd: '/tmp/work', parent: null });
112
- assert.equal(readInboxSince('A').length, 1, 'a root never announces');
152
+ assert.equal(mailFor('A').length, 1, 'a root never announces');
113
153
  });
114
154
  test('independent root: provenance only, no parent, no subscription', async () => {
115
155
  createNode(spawner('A'));
@@ -178,6 +218,7 @@ test('fork spawn resolves path and UUID provenance to the same transcript-only e
178
218
  const launched = [];
179
219
  try {
180
220
  process.env['HOME'] = localHome;
221
+ initializeTestUserScope();
181
222
  process.env['PI_CODING_AGENT_DIR'] = join(localHome, 'pi-agent');
182
223
  process.env['CRTR_NODE_ID'] = 'fork-source';
183
224
  delete process.env['TMUX'];
@@ -195,9 +236,8 @@ test('fork spawn resolves path and UUID provenance to the same transcript-only e
195
236
  createNode({
196
237
  ...spawner('fork-source'),
197
238
  cwd: localCwd,
198
- pi_session_id: sessionId,
199
- pi_session_file: sourceFile,
200
239
  });
240
+ resetSession('fork-source', { sessionId, transcriptPath: sourceFile });
201
241
  const canonicalSourceFile = getNode('fork-source').pi_session_file;
202
242
  const sourceExposure = emptyContextExposureState();
203
243
  registerExposure(exposureTarget(sourceExposure, 'system'), 'source-system', 'content');
@@ -243,6 +283,7 @@ test('fork spawn resolves path and UUID provenance to the same transcript-only e
243
283
  }
244
284
  finally {
245
285
  headlessBrokerHost.launch = originalLaunch;
286
+ closeScopedStores();
246
287
  if (previousHome === undefined)
247
288
  delete process.env['HOME'];
248
289
  else
@@ -281,6 +322,7 @@ test('spawnChild: a root from a shell gets the stable root profile id', async ()
281
322
  const previousStdoutTty = Object.getOwnPropertyDescriptor(process.stdout, 'isTTY');
282
323
  try {
283
324
  process.env.HOME = localHome;
325
+ initializeTestUserScope();
284
326
  Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true, writable: true });
285
327
  Object.defineProperty(process.stdout, 'isTTY', { value: false, configurable: true, writable: true });
286
328
  Reflect.deleteProperty(env, 'CRTR_NODE_ID');
@@ -303,6 +345,7 @@ test('spawnChild: a root from a shell gets the stable root profile id', async ()
303
345
  }
304
346
  finally {
305
347
  headlessBrokerHost.launch = originalLaunch;
348
+ closeScopedStores();
306
349
  if (previousHome === undefined)
307
350
  delete process.env.HOME;
308
351
  else
@@ -1,7 +1,7 @@
1
1
  // Run with: npm run test:integration
2
2
  //
3
3
  // THE DAEMON WAKE HALF of delivery-vs-wake, on a graph >=2 levels deep. Routing
4
- // itself (active → inbox.jsonl, passive → passive.jsonl) is owned by
4
+ // itself (active → wake delivery, passive → non-waking delivery) is owned by
5
5
  // passive-subscription.test.ts; what this file owns is the decision ONE real
6
6
  // `superviseTick` makes on that routing: the ACTIVE subscriber of a finished
7
7
  // target is revived, the PASSIVE subscriber on the same target is not.
@@ -30,27 +30,40 @@
30
30
  import { test } from 'node:test';
31
31
  import assert from 'node:assert/strict';
32
32
  import { spawnSync } from 'node:child_process';
33
- import { existsSync, readFileSync } from 'node:fs';
34
33
  import { createHarness } from '../helpers/harness.js';
35
- import { closeDb } from '../../canvas/db.js';
34
+ import { openDb } from '../../canvas/db.js';
36
35
  import { subscribe } from '../../canvas/canvas.js';
37
36
  import { pushFinal } from '../../feed/feed.js';
38
- import { passivePath } from '../../canvas/paths.js';
39
37
  // A pid reaped (dead) by the time spawnSync returns — a released broker's exited
40
38
  // process (the headless analog of a closed pane).
41
39
  function deadPid() {
42
40
  const r = spawnSync('true', [], { stdio: 'ignore' });
43
41
  return r.pid ?? 0x7ffffffe;
44
42
  }
45
- // LOCAL helper: read a node's PASSIVE accumulator (passive.jsonl) straight off
46
- // the data layer, mirroring the harness's own `inbox()` reader. The harness
47
- // exposes inbox() but NOT passive() — this fills that gap.
43
+ // Read the canonical non-waking delivery rows, mirroring the harness's SQL
44
+ // wake-delivery reader.
48
45
  function passive(nodeId) {
49
- closeDb();
50
- const path = passivePath(nodeId);
51
- if (!existsSync(path))
52
- return [];
53
- return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
46
+ return openDb().prepare(`SELECT m.message_id AS entry_id, m.operation_id, m.created_at AS ts,
47
+ m.sender_node_id AS "from", m.sender_name AS from_name, d.urgency AS tier,
48
+ m.kind, m.body_ref AS ref, m.label, m.body_json AS data, m.disposition
49
+ FROM deliveries d JOIN messages m ON m.message_id = d.message_id
50
+ WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.wake = 0
51
+ ORDER BY m.seq`).all(nodeId).map((row) => {
52
+ const value = row;
53
+ return {
54
+ entry_id: value['entry_id'],
55
+ operation_id: value['operation_id'],
56
+ ts: value['ts'],
57
+ from: value['from'],
58
+ ...(value['from_name'] === null ? {} : { from_name: value['from_name'] }),
59
+ tier: value['tier'],
60
+ kind: value['kind'],
61
+ ...(value['ref'] === null ? {} : { ref: value['ref'] }),
62
+ label: value['label'],
63
+ ...(value['data'] === null ? {} : { data: JSON.parse(value['data']) }),
64
+ ...(value['disposition'] === null ? {} : { disposition: value['disposition'] }),
65
+ };
66
+ });
54
67
  }
55
68
  // `{node_id, active}` arrays → a stable comparable set of `id:active|passive`.
56
69
  function edgeSet(arr) {
@@ -87,15 +100,14 @@ test('one daemon tick revives the active subscriber of a finished target and lea
87
100
  assert.equal(h.node(B).cycles ?? 0, 0, 'B not yet revived');
88
101
  assert.equal(h.node(P).cycles ?? 0, 0, 'P not yet revived');
89
102
  // T FINISHES via the model layer — the precondition the daemon decides on:
90
- // B's pointer lands in inbox.jsonl (the wake channel), P's in passive.jsonl.
103
+ // B gets a wake delivery, P gets a non-waking delivery.
91
104
  await pushFinal(T, 'TARGET-FINAL-BODY: the worker completed');
92
- closeDb();
93
- assert.ok(h.inbox(B).find((e) => e.from === T && e.kind === 'final'), 'ACTIVE subscriber B: T-final pointer delivered to inbox.jsonl');
94
- assert.equal(h.inbox(P).length, 0, 'PASSIVE subscriber P: inbox.jsonl stays empty (no wake channel)');
95
- assert.equal(passive(P).length, 1, 'PASSIVE subscriber P: exactly one passive entry');
105
+ assert.ok(h.inbox(B).find((e) => e.from === T && e.kind === 'final'), 'ACTIVE subscriber B: T-final wake delivery is durable');
106
+ assert.equal(h.inbox(P).length, 0, 'PASSIVE subscriber P: wake-delivery set stays empty');
107
+ assert.equal(passive(P).length, 1, 'PASSIVE subscriber P: exactly one non-waking delivery');
96
108
  // The DAEMON decision pass. Its 2nd pass revives an idle-released node
97
- // ONLY on an unseen INBOX entry. B has one (active) → REVIVED. P has none
98
- // (its entry went to passive.jsonl) → stays idle. Decided in ONE tick.
109
+ // ONLY on an unseen wake delivery. B has one (active) → REVIVED. P has none
110
+ // (its entry is non-waking) → stays idle. Decided in ONE tick.
99
111
  await h.tick();
100
112
  // ACTIVE subscriber: WOKEN — reviveNode bumped cycles + transition(revive).
101
113
  {
@@ -111,8 +123,8 @@ test('one daemon tick revives the active subscriber of a finished target and lea
111
123
  assert.equal(p.status, 'idle', 'P STILL idle — daemon did NOT wake it');
112
124
  assert.equal(p.intent, 'idle-release', 'P still intent=idle-release (untouched)');
113
125
  }
114
- assert.equal(passive(P).length, 1, "P's passive entry is still pending (drained only on its next message)");
115
- assert.equal(h.inbox(P).length, 0, "P's inbox still empty");
126
+ assert.equal(passive(P).length, 1, "P's non-waking delivery is still pending (drained only on its next message)");
127
+ assert.equal(h.inbox(P).length, 0, "P's wake-delivery set is still empty");
116
128
  }
117
129
  finally {
118
130
  await h.dispose();
@@ -13,7 +13,8 @@ import { createManagedWorktree, closeManagedWorktree, managedWorktreePath, rollb
13
13
  import { closeManagedWorktreeAsync } from '../../worktree-close.js';
14
14
  import { reconcileManagedWorktree } from '../../worktree-sweep.js';
15
15
  import { createNode, getNode } from '../../canvas/canvas.js';
16
- import { closeDb } from '../../canvas/db.js';
16
+ import { closeDb, migrate, openDb } from '../../canvas/db.js';
17
+ import { resetSession } from '../../canvas/sessions.js';
17
18
  let home;
18
19
  let remoteDir;
19
20
  let sourceDir;
@@ -61,6 +62,7 @@ beforeEach(() => {
61
62
  closeDb();
62
63
  rmSync(home, { recursive: true, force: true });
63
64
  process.env['CRTR_HOME'] = home;
65
+ migrate(openDb());
64
66
  });
65
67
  after(() => {
66
68
  closeDb();
@@ -81,7 +83,8 @@ test('closeManagedWorktree leaves the active caller checkout intact with named d
81
83
  mkdirSync(sessionDir, { recursive: true });
82
84
  const sessionFile = join(sessionDir, 'session.jsonl');
83
85
  writeFileSync(sessionFile, `${JSON.stringify({ type: 'session', version: 3, id: 'wt-session', cwd: wt.path })}\n`);
84
- createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt, pi_session_id: 'wt-session', pi_session_file: sessionFile }));
86
+ createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
87
+ resetSession(nodeId, { sessionId: 'wt-session', transcriptPath: sessionFile });
85
88
  const result = closeManagedWorktree(nodeId);
86
89
  assert.equal(result.worktree_removed, false);
87
90
  assert.match(result.worktree_remove_error ?? '', /cleanup pending/);