@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
@@ -11,7 +11,7 @@ import assert from 'node:assert/strict';
11
11
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
12
12
  import { tmpdir } from 'node:os';
13
13
  import { join } from 'node:path';
14
- import { closeDb } from '../canvas/db.js';
14
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
15
15
  import { resetScopeCache } from '../scope.js';
16
16
  import { spawnNode } from '../runtime/nodes.js';
17
17
  import { renderOnReadDocs, renderWorkspaceOpenDocs } from '../substrate/on-read-node.js';
@@ -36,6 +36,7 @@ beforeEach(() => {
36
36
  process.env['HOME'] = fakeHome;
37
37
  canvasHome = mkdtempSync(join(tmpdir(), 'crtr-nested-canvas-'));
38
38
  process.env['CRTR_HOME'] = canvasHome;
39
+ migrate(openDb());
39
40
  });
40
41
  after(() => {
41
42
  closeDb();
@@ -1,49 +1,17 @@
1
- // End-to-end tests for passive subscriptions:
2
- // 1. A passive subscriber's pushes land in passive.jsonl, NOT inbox.jsonl
3
- // (so the inbox-watcher never wakes it); an active subscriber's land in
4
- // inbox.jsonl as before.
5
- // 2. the broker drain reads + clears the accumulator (surfaces exactly once).
6
- // 3. canvas-passive-context formats drained entries as timestamped XML and
7
- // transforms an `input` event into pre-text + the original message.
8
- //
9
- // Run: node --import tsx/esm --test src/core/__tests__/passive-subscription.test.ts
10
- import { test, before, beforeEach, afterEach, after } from 'node:test';
1
+ import { after, before, beforeEach, test } from 'node:test';
11
2
  import assert from 'node:assert/strict';
12
- import { mkdtempSync, mkdirSync, rmSync, existsSync, readFileSync, appendFileSync } from 'node:fs';
3
+ import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
13
4
  import { tmpdir } from 'node:os';
14
5
  import { basename, join } from 'node:path';
15
6
  import { createNode, subscribe } from '../canvas/canvas.js';
16
7
  import { armCron, getCron } from '../canvas/crons.js';
17
- import { closeDb } from '../canvas/db.js';
18
- import { inboxPath, passivePath, apiSocketPath } from '../canvas/paths.js';
19
- import { createApiServer } from '../../daemon/api/server.js';
8
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
9
+ import { acknowledgeNodeMail, claimNodeMail, hasUnseenWakeMail } from '../canvas/deliveries.js';
10
+ import { beginExecution } from '../canvas/executions.js';
20
11
  import { push } from '../feed/feed.js';
21
- import { appendPassive } from '../feed/passive.js';
22
- import { readInboxSince } from '../feed/inbox.js';
23
- import { drainBrokerPassive } from '../runtime/broker/passive.js';
24
- import { readLeaf } from '../../commands/canvas-history/read.js';
25
- import registerCanvasPassiveContext from '../../pi-extensions/canvas-passive-context.js';
12
+ import { nodeMailEntry } from '../feed/inbox.js';
13
+ import { formatPassive } from '../../pi-extensions/canvas-passive-context.js';
26
14
  let home;
27
- function readPassiveEntries(nodeId) {
28
- const path = passivePath(nodeId);
29
- if (!existsSync(path))
30
- return [];
31
- return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
32
- }
33
- // The one test that resolves an emitted feed ref drives `canvas history read`'s
34
- // leaf (`readLeaf`), a thin CLI forwarder to crtrd since the API re-plumb. Stand
35
- // up a real in-process `createApiServer()` on the isolated CRTR_HOME's socket so
36
- // that read resolves through the genuine daemon seam — no autostarted
37
- // subprocess, no mock client.
38
- let server;
39
- async function waitForSocket(path) {
40
- for (let i = 0; i < 200; i++) {
41
- if (existsSync(path))
42
- return;
43
- await new Promise((r) => setTimeout(r, 25));
44
- }
45
- throw new Error(`api socket never bound at ${path}`);
46
- }
47
15
  function node(id, over = {}) {
48
16
  return {
49
17
  node_id: id,
@@ -57,53 +25,44 @@ function node(id, over = {}) {
57
25
  ...over,
58
26
  };
59
27
  }
28
+ function execution(nodeId) {
29
+ return beginExecution(nodeId, { daemonEpoch: 'test' });
30
+ }
60
31
  before(() => {
61
32
  home = mkdtempSync(join(tmpdir(), 'crtr-passive-'));
62
33
  process.env['CRTR_HOME'] = home;
63
34
  });
64
- beforeEach(async () => {
35
+ beforeEach(() => {
65
36
  closeDb();
66
37
  rmSync(home, { recursive: true, force: true });
67
38
  mkdirSync(home, { recursive: true });
68
- server = createApiServer();
69
- await waitForSocket(apiSocketPath());
70
- });
71
- afterEach(async () => {
72
- await server?.close();
39
+ migrate(openDb());
73
40
  });
74
41
  after(() => {
75
42
  closeDb();
76
43
  rmSync(home, { recursive: true, force: true });
77
44
  delete process.env['CRTR_HOME'];
78
- delete process.env['CRTR_NODE_ID'];
79
45
  });
80
- test('passive push accumulates in passive.jsonl, not inbox.jsonl', async () => {
46
+ test('a passive subscriber receives a non-waking SQL delivery with the canonical report ref', async () => {
81
47
  createNode(node('pub'));
82
48
  createNode(node('observer'));
83
- subscribe('observer', 'pub', false); // PASSIVE
49
+ subscribe('observer', 'pub', false);
84
50
  const result = await push('pub', { kind: 'update', body: 'first observation\nmore detail' });
85
- // The emitted feed ref is the same canonical handle accepted by `canvas
86
- // history read`, while PushResult.reportPath remains an absolute path.
87
- const acc = readPassiveEntries('observer');
88
- const ref = acc[0].ref;
89
- assert.equal(ref, `pub:reports/${basename(result.reportPath)}`);
90
- const resolved = await readLeaf.run({ ref });
91
- assert.equal(resolved?.['ref'], ref, 'canvas history read accepts the emitted ref verbatim');
92
- assert.equal(resolved?.['content'], 'first observation\nmore detail');
93
- assert.equal(result.reportPath.startsWith('/'), true);
94
- // No inbox entry → the inbox-watcher would never see it → no wake.
95
- assert.equal(existsSync(inboxPath('observer')), false);
96
- assert.equal(readInboxSince('observer').length, 0);
97
- // It landed in the passive accumulator instead.
98
- assert.equal(acc.length, 1);
99
- assert.equal(acc[0].from, 'pub');
100
- assert.equal(acc[0].label, 'first observation');
101
- assert.match(acc[0].ref ?? '', /^pub:reports\/\d{8}T\d{9}-[0-9a-f]{8}-update\.md$/, 'report refs use a compact random suffix');
51
+ const executionId = execution('observer');
52
+ const claim = claimNodeMail('observer', executionId, 'passive');
53
+ assert.equal(hasUnseenWakeMail('observer'), false, 'passive mail does not wake a node');
54
+ assert.equal(claim.action, 'claimed');
55
+ assert.equal(claim.deliveries.length, 1);
56
+ const entry = nodeMailEntry(claim.deliveries[0]);
57
+ assert.equal(entry.from, 'pub');
58
+ assert.equal(entry.label, 'first observation');
59
+ assert.equal(entry.ref, `pub:reports/${basename(result.reportPath)}`);
60
+ assert.match(entry.ref ?? '', /^pub:reports\/\d{8}T\d{9}-[0-9a-f]{8}-update\.md$/);
102
61
  });
103
- test('active push still lands in inbox.jsonl (wakes) and clears the manager deadline', async () => {
62
+ test('an active subscriber receives waking SQL mail and clears its deadline', async () => {
104
63
  createNode(node('pub'));
105
64
  createNode(node('worker-mgr'));
106
- subscribe('worker-mgr', 'pub', true); // ACTIVE
65
+ subscribe('worker-mgr', 'pub', true);
107
66
  armCron({
108
67
  cron_id: 'manager-deadline',
109
68
  name: 'manager deadline',
@@ -126,102 +85,36 @@ test('active push still lands in inbox.jsonl (wakes) and clears the manager dead
126
85
  tier: 'normal',
127
86
  });
128
87
  await push('pub', { kind: 'update', body: 'active report' });
129
- assert.equal(existsSync(passivePath('worker-mgr')), false);
130
- const inbox = readInboxSince('worker-mgr');
131
- assert.equal(inbox.length, 1);
132
- assert.equal(inbox[0].from, 'pub');
133
- assert.match(inbox[0].ref ?? '', /^pub:reports\/.+-update\.md$/);
134
- assert.equal(getCron('manager-deadline'), null, 'the child report won the deadline race');
135
- });
136
- test('a deferred update still lands in an active subscriber inbox at deferred tier', async () => {
137
- createNode(node('pub'));
138
- // Resident: deferred mail to a TERMINAL target is bumped to urgent by the
139
- // shipped tier bump, so only a resident subscriber shows the requested tier.
140
- createNode(node('worker-mgr', { lifecycle: 'resident' }));
141
- subscribe('worker-mgr', 'pub', true); // ACTIVE
142
- await push('pub', { kind: 'update', deliveryTier: 'deferred', body: 'active report' });
143
- assert.equal(existsSync(passivePath('worker-mgr')), false);
144
- const inbox = readInboxSince('worker-mgr');
145
- assert.equal(inbox.length, 1);
146
- assert.equal(inbox[0].from, 'pub');
147
- assert.equal(inbox[0].tier, 'deferred', 'requested delivery urgency reaches the subscriber unchanged');
148
- assert.equal(inbox[0].kind, 'update', 'the stored report kind is untouched by delivery urgency');
149
- assert.match(inbox[0].ref ?? '', /^pub:reports\/.+-update\.md$/);
88
+ const claim = claimNodeMail('worker-mgr', execution('worker-mgr'), 'wake');
89
+ assert.equal(hasUnseenWakeMail('worker-mgr'), true);
90
+ assert.equal(getCron('manager-deadline'), null, 'the report won the deadline race');
91
+ assert.equal(claim.deliveries.length, 1);
92
+ assert.equal(nodeMailEntry(claim.deliveries[0]).from, 'pub');
150
93
  });
151
- test('mixed active + passive subscribers route to their own stores', async () => {
94
+ test('a passive delivery is acknowledged only after it has been claimed', async () => {
152
95
  createNode(node('pub'));
153
- createNode(node('active-sub'));
154
- createNode(node('passive-sub'));
155
- subscribe('active-sub', 'pub', true);
156
- subscribe('passive-sub', 'pub', false);
157
- const res = await push('pub', { kind: 'urgent', body: 'something happened' });
158
- assert.deepEqual(new Set(res.deliveredTo), new Set(['active-sub', 'passive-sub']));
159
- assert.equal(readInboxSince('active-sub').length, 1);
160
- assert.equal(existsSync(passivePath('active-sub')), false);
161
- assert.equal(readPassiveEntries('passive-sub').length, 1);
162
- assert.equal(existsSync(inboxPath('passive-sub')), false);
163
- });
164
- test('broker passive drain reads then clears (surfaces exactly once)', () => {
165
- createNode(node('observer'));
166
- appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
167
- appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
168
- const drained = drainBrokerPassive('observer');
169
- assert.equal(drained.length, 2);
170
- assert.deepEqual(drained.map((e) => e.label), ['one', 'two']); // oldest first
171
- // Cleared — a second drain is empty.
172
- assert.equal(drainBrokerPassive('observer').length, 0);
173
- assert.equal(readPassiveEntries('observer').length, 0);
174
- });
175
- test('broker passive drain tolerates a corrupt line — keeps every good entry, loses only the bad one', () => {
176
96
  createNode(node('observer'));
177
- appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
178
- appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
179
- // A torn/garbage line lands between good entries (a partial write, a crash
180
- // mid-append). The whole feed must NOT be discarded over it.
181
- appendFileSync(passivePath('observer'), '{not valid json\n', 'utf8');
182
- appendPassive('observer', { from: 'c', tier: 'normal', kind: 'update', label: 'three' });
183
- const drained = drainBrokerPassive('observer');
184
- assert.equal(drained.length, 3, 'all three good entries survive the corrupt line');
185
- assert.deepEqual(drained.map((e) => e.label), ['one', 'two', 'three']);
186
- // And it still cleared: a second drain is empty (snapshot removed).
187
- assert.equal(drainBrokerPassive('observer').length, 0);
188
- assert.equal(existsSync(passivePath('observer')), false);
97
+ subscribe('observer', 'pub', false);
98
+ await push('pub', { kind: 'update', body: 'one' });
99
+ await push('pub', { kind: 'update', body: 'two' });
100
+ const executionId = execution('observer');
101
+ const claim = claimNodeMail('observer', executionId, 'passive');
102
+ assert.deepEqual(claim.deliveries.map((delivery) => nodeMailEntry(delivery).label), ['one', 'two']);
103
+ const receipt = acknowledgeNodeMail('observer', executionId, claim.deliveries.map((delivery) => ({
104
+ deliveryId: delivery.deliveryId,
105
+ attempts: delivery.attempts,
106
+ disposition: 'acknowledged',
107
+ })));
108
+ assert.deepEqual(receipt.results.map((result) => result.outcome), ['acknowledged', 'acknowledged']);
109
+ assert.deepEqual(claimNodeMail('observer', executionId, 'passive').deliveries, []);
189
110
  });
190
- function makeFakePi() {
191
- return { on(e, h) { if (e === 'input')
192
- this.handler = h; } };
193
- }
194
- test('input handler injects drained backlog as pre-text, then clears it', async () => {
111
+ test('passive SQL mail retains the existing timestamped backlog rendering', async () => {
195
112
  createNode(node('pub'));
196
113
  createNode(node('observer'));
197
114
  subscribe('observer', 'pub', false);
198
115
  await push('pub', { kind: 'update', body: 'the body of the report\nsecond line' });
199
- process.env['CRTR_NODE_ID'] = 'observer';
200
- const pi = makeFakePi();
201
- registerCanvasPassiveContext(pi);
202
- assert.ok(pi.handler, 'input handler registered');
203
- // First message → backlog drains in as pre-text before the user's text.
204
- const out = await pi.handler({ type: 'input', text: 'hey what happened', source: 'interactive' });
205
- assert.equal(out.action, 'transform');
206
- assert.match(out.text, /<passive-subscription-backlog/);
207
- assert.match(out.text, /the body of the report/); // dereferenced report body
208
- assert.match(out.text, /hey what happened$/); // original message preserved at the end
209
- // Second message → nothing accumulated → left untouched.
210
- const out2 = await pi.handler({ type: 'input', text: 'still there?', source: 'interactive' });
211
- assert.ok(out2 === undefined || out2.action === 'continue');
212
- // Oversized canonical reports must leave an actionable reader command in the
213
- // truncation tail, using the exact ref emitted into the passive feed.
214
- await push('pub', { kind: 'update', body: 'oversized report line '.repeat(250) });
215
- const oversizedRef = readPassiveEntries('observer')[0].ref;
216
- const out3 = await pi.handler({ type: 'input', text: 'show me the rest', source: 'interactive' });
217
- assert.equal(out3.action, 'transform');
218
- assert.ok(out3.text.includes(`crtr canvas history read ${oversizedRef}`), 'truncation tail names the canonical reader and emitted ref');
219
- });
220
- test('input handler is inert when nothing is accumulated', async () => {
221
- createNode(node('observer'));
222
- process.env['CRTR_NODE_ID'] = 'observer';
223
- const pi = makeFakePi();
224
- registerCanvasPassiveContext(pi);
225
- const out = await pi.handler({ type: 'input', text: 'plain message', source: 'interactive' });
226
- assert.ok(out === undefined || out.action === 'continue');
116
+ const claim = claimNodeMail('observer', execution('observer'), 'passive');
117
+ const text = formatPassive(claim.deliveries.map(nodeMailEntry));
118
+ assert.match(text, /<passive-subscription-backlog/);
119
+ assert.match(text, /the body of the report/);
227
120
  });
@@ -17,7 +17,8 @@ import { tmpdir } from 'node:os';
17
17
  import { join } from 'node:path';
18
18
  import * as tar from 'tar';
19
19
  import { resetScopeCache } from '../scope.js';
20
- import { readMergedLaunchConfig } from '../config.js';
20
+ import { readMergedLaunchConfig, updateRawConfigAtomically, writeRawConfig } from '../config.js';
21
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
21
22
  import { validatePluginBundle } from '../command-plugins/bundle.js';
22
23
  let isolatedHome;
23
24
  let isolatedCwd;
@@ -32,6 +33,7 @@ beforeEach(() => {
32
33
  isolatedCwd = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-plugin-kinds-cwd-')));
33
34
  process.env.HOME = isolatedHome;
34
35
  process.chdir(isolatedCwd);
36
+ initializeFreshScopedStore({ kind: 'user', root: join(isolatedHome, '.crouter') });
35
37
  resetScopeCache();
36
38
  });
37
39
  afterEach(() => {
@@ -76,7 +78,7 @@ test('the scope\u2019s own config.json overrides its plugins; a disabled plugin
76
78
  const root = userRoot();
77
79
  plantPlugin(root, 'northlight', { 'applet-builder': { whenToUse: 'plugin gloss', model: 'anthropic/strong' } });
78
80
  plantPlugin(root, 'zzz-disabled', { ghost: { whenToUse: 'never registers' } });
79
- writeFileSync(join(root, 'config.json'), JSON.stringify({
81
+ updateRawConfigAtomically('user', () => ({
80
82
  plugins: { 'zzz-disabled': { enabled: false } },
81
83
  kinds: { 'applet-builder': { model: 'openai/medium' } },
82
84
  }));
@@ -91,7 +93,7 @@ test('a whenToUse-only entry re-glosses an existing kind without stripping its m
91
93
  // Now every entry field-merges over an existing kind; only a NEW kind
92
94
  // requires whenToUse (a whenToUse-less new kind is dropped).
93
95
  const root = userRoot();
94
- writeFileSync(join(root, 'config.json'), JSON.stringify({ kinds: { explore: { whenToUse: 'custom explore gloss' }, 'new-glossless': { model: 'openai/light' } } }));
96
+ updateRawConfigAtomically('user', () => ({ kinds: { explore: { whenToUse: 'custom explore gloss' }, 'new-glossless': { model: 'openai/light' } } }));
95
97
  const kinds = readMergedLaunchConfig().kinds;
96
98
  assert.equal(kinds['explore']?.whenToUse, 'custom explore gloss');
97
99
  assert.equal(kinds['explore']?.model, 'openai/light'); // builtin tier survives
@@ -100,7 +102,7 @@ test('a whenToUse-only entry re-glosses an existing kind without stripping its m
100
102
  });
101
103
  test('a project-root plugin layers above user config, and the project config above the plugin', () => {
102
104
  const uRoot = userRoot();
103
- writeFileSync(join(uRoot, 'config.json'), JSON.stringify({ kinds: { shared: { whenToUse: 'user gloss', model: 'openai/light' } } }));
105
+ writeRawConfig('user', { kinds: { shared: { whenToUse: 'user gloss', model: 'openai/light' } } });
104
106
  const pRoot = projectRoot();
105
107
  plantPlugin(pRoot, 'proj-plugin', { shared: { model: 'anthropic/strong' }, 'proj-kind': { whenToUse: 'project plugin kind' } });
106
108
  writeFileSync(join(pRoot, 'config.json'), JSON.stringify({ kinds: { 'proj-kind': { model: 'openai/medium' } } }));
@@ -11,8 +11,11 @@ import assert from 'node:assert/strict';
11
11
  import { mkdtempSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
12
12
  import { tmpdir } from 'node:os';
13
13
  import { join } from 'node:path';
14
+ import { realpathSync } from 'node:fs';
14
15
  import { linkOrCopy } from '../fs-utils.js';
15
16
  import { listInstalledPluginsInRoot } from '../resolver.js';
17
+ import { closeScopedStores } from '../scoped-state/db.js';
18
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
16
19
  describe('plugin link through a symlinked scope-root ancestor', () => {
17
20
  test('the installed plugin resolves on the next invocation', () => {
18
21
  const base = mkdtempSync(join(tmpdir(), 'crtr-plugin-link-'));
@@ -28,10 +31,12 @@ describe('plugin link through a symlinked scope-root ancestor', () => {
28
31
  symlinkSync(join('physical', 'home'), join(base, 'home'), 'dir');
29
32
  const scopeRootPath = join(base, 'home', '.crouter');
30
33
  mkdirSync(join(scopeRootPath, 'plugins'), { recursive: true });
34
+ initializeFreshScopedStore({ kind: 'user', root: scopeRootPath });
31
35
  linkOrCopy(source, join(scopeRootPath, 'plugins', 'search'));
32
- assert.deepEqual(listInstalledPluginsInRoot('user', scopeRootPath).map((p) => p.name), ['search']);
36
+ assert.deepEqual(listInstalledPluginsInRoot('user', realpathSync(scopeRootPath)).map((p) => p.name), ['search']);
33
37
  }
34
38
  finally {
39
+ closeScopedStores();
35
40
  rmSync(base, { recursive: true, force: true });
36
41
  }
37
42
  });
@@ -15,7 +15,8 @@ import { tmpdir } from 'node:os';
15
15
  import { join } from 'node:path';
16
16
  import * as tar from 'tar';
17
17
  import { resetScopeCache } from '../scope.js';
18
- import { resolvePageComponents } from '../config.js';
18
+ import { resolvePageComponents, updateRawConfigAtomically, writeRawConfig } from '../config.js';
19
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
19
20
  import { validatePluginBundle } from '../command-plugins/bundle.js';
20
21
  let isolatedHome;
21
22
  let isolatedCwd;
@@ -30,6 +31,7 @@ beforeEach(() => {
30
31
  isolatedCwd = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-page-components-cwd-')));
31
32
  process.env.HOME = isolatedHome;
32
33
  process.chdir(isolatedCwd);
34
+ initializeFreshScopedStore({ kind: 'user', root: join(isolatedHome, '.crouter') });
33
35
  resetScopeCache();
34
36
  });
35
37
  afterEach(() => {
@@ -60,7 +62,7 @@ test('an enabled user-scope plugin contributes its page components; a disabled o
60
62
  { kind: 'applet-card', display: true },
61
63
  ]);
62
64
  plantPlugin(root, 'zzz-disabled', ['ghost']);
63
- writeFileSync(join(root, 'config.json'), JSON.stringify({ plugins: { 'zzz-disabled': { enabled: false } } }));
65
+ writeRawConfig('user', { plugins: { 'zzz-disabled': { enabled: false } } });
64
66
  const components = resolvePageComponents();
65
67
  assert.deepEqual(components, [
66
68
  { kind: 'connector', description: 'Connector decision', useWhen: 'Use for a connector decision.', doc: 'Product contract.' },
@@ -70,9 +72,9 @@ test('an enabled user-scope plugin contributes its page components; a disabled o
70
72
  test('user config entries and plugin contributions resolve together, and a colliding kind is rejected', () => {
71
73
  const root = userRoot();
72
74
  plantPlugin(root, 'northlight', ['connector']);
73
- writeFileSync(join(root, 'config.json'), JSON.stringify({ page_components: ['approval'] }));
75
+ updateRawConfigAtomically('user', () => ({ page_components: ['approval'] }));
74
76
  assert.deepEqual(resolvePageComponents().map((component) => component.kind), ['approval', 'connector']);
75
- writeFileSync(join(root, 'config.json'), JSON.stringify({ page_components: ['connector'] }));
77
+ updateRawConfigAtomically('user', () => ({ page_components: ['connector'] }));
76
78
  assert.throws(() => resolvePageComponents(), /page component kind "connector" is registered by both config.json and plugin "northlight"/);
77
79
  });
78
80
  test('two plugins whose kinds derive one JSX tag are rejected, naming both sources', () => {
@@ -16,6 +16,7 @@ import { tmpdir } from 'node:os';
16
16
  import { join } from 'node:path';
17
17
  import { buildRoot } from '../../build-root.js';
18
18
  import { SUPPRESS_OUTPUT_LEAVES, LEAF_ICON_CP, SUMMARIZERS } from '../preview-registry.js';
19
+ import { ensureScopeInitialized } from '../config.js';
19
20
  import { resetScopeCache } from '../scope.js';
20
21
  // buildRoot() composes external command-plugin branches from the effective
21
22
  // enabled plugins (user scope + project ancestors). Pin HOME + profile to an
@@ -31,6 +32,7 @@ before(() => {
31
32
  process.env['HOME'] = home;
32
33
  delete process.env['CRTR_PROFILE_ID'];
33
34
  resetScopeCache();
35
+ ensureScopeInitialized('user', join(home, '.crouter'));
34
36
  });
35
37
  after(() => {
36
38
  if (realHome !== undefined)
@@ -18,9 +18,10 @@ import assert from 'node:assert/strict';
18
18
  import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
19
19
  import { tmpdir } from 'node:os';
20
20
  import { dirname, join } from 'node:path';
21
- import { closeDb } from '../canvas/db.js';
21
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
22
22
  import { resetScopeCache } from '../scope.js';
23
23
  import { createProfile } from '../profiles/manifest.js';
24
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
24
25
  import { spawnNode } from '../runtime/nodes.js';
25
26
  import { memoryDir } from '../runtime/memory.js';
26
27
  import { clearSessionCache } from '../substrate/session-cache.js';
@@ -115,6 +116,8 @@ before(() => {
115
116
  canvasHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-ppm-canvas-')));
116
117
  process.env['HOME'] = home;
117
118
  process.env['CRTR_HOME'] = canvasHome;
119
+ initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
120
+ migrate(openDb());
118
121
  // A project store answers only under the namespace its root INDEX.md
119
122
  // declares; without that declaration the store does not mount at all.
120
123
  const store = (slug, namespace) => {
@@ -3,8 +3,9 @@ import assert from 'node:assert/strict';
3
3
  import { mkdtempSync, rmSync, existsSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
- import { createNode, getRow, subscribe, recordPid, recordSpawn, subscribersOf, subscriptionsOf, pruneNodes, pruneToLimit, transitionStatusIntent, } from '../canvas/canvas.js';
7
- import { closeDb, openDb } from '../canvas/db.js';
6
+ import { createNode, getRow, subscribe, recordSpawn, subscribersOf, subscriptionsOf, pruneNodes, pruneToLimit, transitionStatusIntent, } from '../canvas/canvas.js';
7
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
8
+ import { beginExecution, recordExecutionPid } from '../canvas/executions.js';
8
9
  import { nodeDir } from '../canvas/paths.js';
9
10
  let home;
10
11
  const DAY = 86_400_000;
@@ -33,6 +34,10 @@ function totalEdges() {
33
34
  function markCollectibleTerminal(nodeId) {
34
35
  transitionStatusIntent(nodeId, { terminal_reason: 'closed' });
35
36
  }
37
+ function recordExecution(nodeId, pid) {
38
+ const executionId = beginExecution(nodeId, { daemonEpoch: 'test' });
39
+ recordExecutionPid(executionId, nodeId, pid);
40
+ }
36
41
  /** The standard fixture: three prunable terminal nodes (old), three survivors
37
42
  * (two old-but-live, one recent-but-dead), and edges that touch a pruned node
38
43
  * so cascade is observable. */
@@ -55,6 +60,7 @@ before(() => {
55
60
  beforeEach(() => {
56
61
  closeDb();
57
62
  rmSync(home, { recursive: true, force: true });
63
+ migrate(openDb());
58
64
  });
59
65
  after(() => {
60
66
  closeDb();
@@ -146,7 +152,7 @@ test('count pruning keeps all live nodes even when liveCount exceeds the cap', (
146
152
  // fall beyond rank 5 and are pruned.
147
153
  for (let i = 0; i < 5; i++) {
148
154
  createNode(node(`live-${i}`, { status: 'active', created: at(i) }));
149
- recordPid(`live-${i}`, process.pid); // process.pid is alive → isPidAlive true
155
+ recordExecution(`live-${i}`, process.pid); // process.pid is alive → isPidAlive true
150
156
  }
151
157
  createNode(node('old-a', { status: 'dead', created: at(100) }));
152
158
  createNode(node('old-b', { status: 'done', created: at(101) }));
@@ -164,7 +170,7 @@ test('count pruning deletes the oldest terminal nodes beyond max(cap, liveCount)
164
170
  // Ranked by recency DESC: live-0, term-0, term-1 (top 3, kept), then
165
171
  // term-2, term-3, term-4 (rank >= 3, pruned).
166
172
  createNode(node('live-0', { status: 'idle', created: at(0) }));
167
- recordPid('live-0', process.pid);
173
+ recordExecution('live-0', process.pid);
168
174
  for (let i = 0; i < 5; i++) {
169
175
  const id = `term-${i}`;
170
176
  createNode(node(id, { status: 'dead', created: at(10 + i) }));
@@ -185,7 +191,7 @@ test('count pruning deletes the oldest terminal nodes beyond max(cap, liveCount)
185
191
  });
186
192
  test('count pruning dry-run reports candidates without deleting rows or dirs', () => {
187
193
  createNode(node('live-0', { status: 'idle', created: at(0) }));
188
- recordPid('live-0', process.pid);
194
+ recordExecution('live-0', process.pid);
189
195
  for (let i = 0; i < 5; i++) {
190
196
  const id = `term-${i}`;
191
197
  createNode(node(id, { status: 'dead', created: at(10 + i) }));
@@ -4,21 +4,28 @@ import { mkdtempSync, rmSync, mkdirSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { createNode, getNode, setStatus } from '../canvas/canvas.js';
7
- import { closeDb } from '../canvas/db.js';
7
+ import { closeDb, openDb, migrate } from '../canvas/db.js';
8
8
  import { InputError } from '../io.js';
9
9
  import { messageRoutes } from '../../daemon/api/handlers/messages.js';
10
10
  import { pushFinal, FinalizationError } from '../feed/feed.js';
11
11
  import { assertNotFinalized, assertFinalizedForReopen, commitReopen } from '../runtime/reopen.js';
12
12
  import { reviveNode } from '../runtime/revive.js';
13
13
  import { headlessBrokerHost } from '../runtime/host.js';
14
- import { readInboxSince } from '../feed/inbox.js';
14
+ function nodeMailCount(nodeId) {
15
+ return Number(openDb().prepare(`SELECT COUNT(*) AS count FROM deliveries
16
+ WHERE destination_kind = 'node_inbox' AND destination_id = ?`).get(nodeId).count);
17
+ }
15
18
  import { writeGoal } from '../runtime/kickoff.js';
16
19
  import { situationalContextPath } from '../canvas/paths.js';
17
20
  let home;
18
21
  const prevNodeId = process.env['CRTR_NODE_ID'];
19
22
  function node(id) { return { node_id: id, name: id, created: new Date().toISOString(), cwd: '/tmp/work', kind: 'general', mode: 'base', lifecycle: 'terminal', status: 'active' }; }
20
23
  before(() => { home = mkdtempSync(join(tmpdir(), 'crtr-pushfinal-')); process.env['CRTR_HOME'] = home; });
21
- beforeEach(() => { closeDb(); rmSync(home, { recursive: true, force: true }); });
24
+ beforeEach(() => {
25
+ closeDb();
26
+ rmSync(home, { recursive: true, force: true });
27
+ migrate(openDb());
28
+ });
22
29
  after(() => { closeDb(); rmSync(home, { recursive: true, force: true }); delete process.env['CRTR_HOME']; if (prevNodeId === undefined)
23
30
  delete process.env['CRTR_NODE_ID'];
24
31
  else
@@ -66,13 +73,13 @@ test('a finalized node re-tasked via node message send without --reopen is rejec
66
73
  const firstFinal = getNode(id)?.final_report;
67
74
  assert.ok(firstFinal);
68
75
  // A message to the now-finalized node, no --reopen: rejected before the
69
- // handler ever reaches appendInbox/reviveNode. Prove BOTH: the latch is
76
+ // handler ever creates a delivery or reaches reviveNode. Prove BOTH: the latch is
70
77
  // untouched, AND (per #340's Minor finding) no side effect landed first —
71
78
  // spy on the exact revive seam reviveNode calls (headlessBrokerHost.launch,
72
79
  // the same seam revive.test.ts already uses to instrument reviveNode without
73
80
  // a real engine) and assert it was never invoked, plus the inbox is
74
81
  // unchanged.
75
- const inboxBefore = readInboxSince(id, undefined).length;
82
+ const inboxBefore = nodeMailCount(id);
76
83
  const originalLaunch = headlessBrokerHost.launch;
77
84
  let launchCalls = 0;
78
85
  headlessBrokerHost.launch = ((...args) => { launchCalls++; return originalLaunch(...args); });
@@ -84,7 +91,7 @@ test('a finalized node re-tasked via node message send without --reopen is rejec
84
91
  }
85
92
  assert.equal(launchCalls, 0, 'a rejected retask must never attempt a revive');
86
93
  assert.equal(getNode(id)?.final_report, firstFinal, 'a rejected retask must not touch the latch');
87
- assert.equal(readInboxSince(id, undefined).length, inboxBefore, 'a rejected retask must not append an inbox entry');
94
+ assert.equal(nodeMailCount(id), inboxBefore, 'a rejected retask must not create a delivery');
88
95
  // The successful path, driven through the ACTUAL daemon message handler (the
89
96
  // real seam --reopen now lives behind, not a direct call into the gate
90
97
  // internals) — stub the revive seam so no real engine process is spawned,
@@ -105,7 +112,7 @@ test('a finalized node re-tasked via node message send without --reopen is rejec
105
112
  headlessBrokerHost.launch = originalLaunch;
106
113
  }
107
114
  assert.equal(getNode(id)?.final_report, null, '--reopen cleared the latch through the real handler seam');
108
- assert.equal(readInboxSince(id, undefined).length, inboxBefore + 1, 'the re-task message was actually appended to the inbox');
115
+ assert.equal(nodeMailCount(id), inboxBefore + 1, 'the re-task message created one durable delivery');
109
116
  // The re-tasked node can now finish its new mandate and re-latch cleanly.
110
117
  process.env['CRTR_NODE_ID'] = id;
111
118
  await pushFinal(id, 'round 2 verdict');
@@ -15,13 +15,19 @@ import { CrtrClient } from '../../api/client.js';
15
15
  import { createApiServer } from '../../daemon/api/server.js';
16
16
  import registerCanvasStophook from '../../pi-extensions/canvas-stophook.js';
17
17
  import { createNode, getNode, subscribe } from '../canvas/canvas.js';
18
- import { closeDb } from '../canvas/db.js';
18
+ import { beginExecution, recordExecutionPid } from '../canvas/executions.js';
19
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
20
+ import { resetScopeCache } from '../scope.js';
21
+ import { closeScopedStores } from '../scoped-state/db.js';
22
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
19
23
  import { apiSocketPath } from '../canvas/paths.js';
20
24
  import { brokerCapReached, resetFleetForTests } from '../runtime/fleet.js';
21
25
  import { buildPiArgv } from '../runtime/launch.js';
22
26
  import { relaunchRoot } from '../runtime/reset.js';
23
27
  import { bindTestFleet } from './helpers/fleet.js';
24
28
  let home;
29
+ let tf;
30
+ const previousHome = process.env['HOME'];
25
31
  function node(id, over = {}) {
26
32
  return {
27
33
  node_id: id,
@@ -67,16 +73,30 @@ function makeDeps(pid) {
67
73
  }
68
74
  before(() => {
69
75
  home = mkdtempSync(join(tmpdir(), 'crtr-relaunch-'));
70
- process.env['CRTR_HOME'] = home;
76
+ process.env['HOME'] = home;
77
+ process.env['CRTR_HOME'] = join(home, 'canvas');
71
78
  });
72
79
  beforeEach(() => {
73
80
  closeDb();
81
+ closeScopedStores();
74
82
  rmSync(home, { recursive: true, force: true });
83
+ resetScopeCache();
84
+ initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
85
+ migrate(openDb());
86
+ tf = bindTestFleet();
75
87
  });
76
88
  after(() => {
77
89
  closeDb();
90
+ closeScopedStores();
91
+ tf.fleet.dispose();
92
+ resetFleetForTests();
78
93
  rmSync(home, { recursive: true, force: true });
79
94
  delete process.env['CRTR_HOME'];
95
+ if (previousHome === undefined)
96
+ delete process.env['HOME'];
97
+ else
98
+ process.env['HOME'] = previousHome;
99
+ resetScopeCache();
80
100
  });
81
101
  test('relaunchRoot parks the old root done and mints a fresh active root, reaping descendants', async () => {
82
102
  // root → child → grandchild (a parent that subscribes to its workers).
@@ -169,6 +189,10 @@ test('root /new capacity refusal reaches the attached human through broker UI no
169
189
  process.env['CRTR_NODE_ID'] = 'root';
170
190
  createNode(node('root', { parent: null, lifecycle: 'resident' }));
171
191
  const tf = bindTestFleet();
192
+ const executionId = beginExecution('root', { daemonEpoch: tf.fleet.epoch() });
193
+ assert.equal(recordExecutionPid(executionId, 'root', process.pid), 'recorded');
194
+ const previousExecutionId = process.env['CRTR_EXECUTION_ID'];
195
+ process.env['CRTR_EXECUTION_ID'] = executionId;
172
196
  const never = new Promise(() => { });
173
197
  tf.fleet.register('filler-a', { pid: 700_001, exited: never });
174
198
  tf.fleet.register('filler-b', { pid: 700_002, exited: never });
@@ -207,6 +231,10 @@ test('root /new capacity refusal reaches the attached human through broker UI no
207
231
  resetFleetForTests();
208
232
  delete process.env['CRTR_MAX_LIVE_BROKERS'];
209
233
  delete process.env['CRTR_NODE_ID'];
234
+ if (previousExecutionId === undefined)
235
+ delete process.env['CRTR_EXECUTION_ID'];
236
+ else
237
+ process.env['CRTR_EXECUTION_ID'] = previousExecutionId;
210
238
  }
211
239
  });
212
240
  test('relaunchRoot is a no-op for a non-root child and an already-parked root', async () => {