@north-light/crouter 0.3.311 → 0.3.313

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 (520) 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 +746 -735
  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 +17 -5
  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/host-teardown-process-group.test.js +9 -5
  121. package/dist/core/__tests__/integration/lifecycle-hooks.test.js +312 -1
  122. package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
  123. package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
  124. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
  125. package/dist/core/__tests__/integration/revive.test.js +67 -21
  126. package/dist/core/__tests__/integration/spawn-root.test.js +62 -19
  127. package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
  128. package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
  129. package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
  130. package/dist/core/__tests__/kickoff.test.js +36 -39
  131. package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
  132. package/dist/core/__tests__/lifecycle.test.js +2 -1
  133. package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
  134. package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
  135. package/dist/core/__tests__/migration.test.js +145 -31
  136. package/dist/core/__tests__/model-pin-durability.test.js +4 -1
  137. package/dist/core/__tests__/model-routes-config.test.js +13 -5
  138. package/dist/core/__tests__/node-outcome.test.js +93 -26
  139. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +4 -3
  140. package/dist/core/__tests__/on-read-dedup-resume.test.js +8 -7
  141. package/dist/core/__tests__/on-read-nested-store.test.js +8 -7
  142. package/dist/core/__tests__/passive-subscription.test.js +50 -157
  143. package/dist/core/__tests__/plugin-kinds.test.js +6 -4
  144. package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
  145. package/dist/core/__tests__/plugin-page-components.test.js +6 -4
  146. package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
  147. package/dist/core/__tests__/profile-project-memory-delivery.test.js +35 -32
  148. package/dist/core/__tests__/prune.test.js +11 -5
  149. package/dist/core/__tests__/push-final-guard.test.js +14 -7
  150. package/dist/core/__tests__/relaunch-root.test.js +30 -2
  151. package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
  152. package/dist/core/__tests__/repository-root-identity.test.js +4 -0
  153. package/dist/core/__tests__/review-model-floor.test.js +6 -3
  154. package/dist/core/__tests__/revive-all.test.js +13 -3
  155. package/dist/core/__tests__/revive-capacity.test.js +26 -4
  156. package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
  157. package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
  158. package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
  159. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
  160. package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
  161. package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
  162. package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
  163. package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
  164. package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
  165. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +5 -4
  166. package/dist/core/__tests__/stop-guard.test.js +2 -1
  167. package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
  168. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +19 -5
  169. package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
  170. package/dist/core/bootstrap.js +2 -0
  171. package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
  172. package/dist/core/broker-client/transport-socket.d.ts +1 -0
  173. package/dist/core/broker-client/transport-socket.js +13 -5
  174. package/dist/core/canvas/__tests__/attention.test.js +29 -35
  175. package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
  176. package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
  177. package/dist/core/canvas/attention.d.ts +2 -5
  178. package/dist/core/canvas/attention.js +15 -64
  179. package/dist/core/canvas/boot.d.ts +6 -7
  180. package/dist/core/canvas/boot.js +5 -9
  181. package/dist/core/canvas/canvas.d.ts +13 -109
  182. package/dist/core/canvas/canvas.js +140 -401
  183. package/dist/core/canvas/crons.d.ts +2 -0
  184. package/dist/core/canvas/crons.js +16 -15
  185. package/dist/core/canvas/daemon-owner.js +10 -0
  186. package/dist/core/canvas/db.d.ts +2 -2
  187. package/dist/core/canvas/db.js +2 -18
  188. package/dist/core/canvas/deliveries.d.ts +104 -0
  189. package/dist/core/canvas/deliveries.js +354 -0
  190. package/dist/core/canvas/executions.d.ts +29 -0
  191. package/dist/core/canvas/executions.js +68 -0
  192. package/dist/core/canvas/history.js +35 -35
  193. package/dist/core/canvas/index.d.ts +3 -1
  194. package/dist/core/canvas/index.js +3 -1
  195. package/dist/core/canvas/migrations.js +20 -12
  196. package/dist/core/canvas/nav-model.js +1 -3
  197. package/dist/core/canvas/nav-render.d.ts +4 -4
  198. package/dist/core/canvas/nav-render.js +11 -21
  199. package/dist/core/canvas/node-sql-migration.d.ts +3 -0
  200. package/dist/core/canvas/node-sql-migration.js +261 -0
  201. package/dist/core/canvas/paths.d.ts +2 -16
  202. package/dist/core/canvas/paths.js +2 -31
  203. package/dist/core/canvas/recovery.d.ts +76 -0
  204. package/dist/core/canvas/recovery.js +588 -0
  205. package/dist/core/canvas/remote-canvas-source.js +4 -0
  206. package/dist/core/canvas/render-source.d.ts +16 -27
  207. package/dist/core/canvas/render-source.js +16 -45
  208. package/dist/core/canvas/sessions.d.ts +33 -0
  209. package/dist/core/canvas/sessions.js +78 -0
  210. package/dist/core/canvas/types.d.ts +33 -33
  211. package/dist/core/canvas/types.js +2 -5
  212. package/dist/core/canvas/waits.d.ts +18 -0
  213. package/dist/core/canvas/waits.js +70 -0
  214. package/dist/core/canvas/worktrees.d.ts +40 -0
  215. package/dist/core/canvas/worktrees.js +93 -0
  216. package/dist/core/command-hooks/artifact.d.ts +6 -0
  217. package/dist/core/command-hooks/artifact.js +22 -6
  218. package/dist/core/command-hooks/discovery.d.ts +2 -0
  219. package/dist/core/command-hooks/discovery.js +2 -0
  220. package/dist/core/command-hooks/lifecycle-catalog.d.ts +20 -2
  221. package/dist/core/command-hooks/lifecycle-catalog.js +7 -2
  222. package/dist/core/command-hooks/lifecycle-create.d.ts +4 -0
  223. package/dist/core/command-hooks/lifecycle-create.js +51 -0
  224. package/dist/core/command-hooks/schema.d.ts +2 -2
  225. package/dist/core/command-hooks/schema.js +5 -4
  226. package/dist/core/command-hooks/transport/exec-invoke.js +3 -1
  227. package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +18 -8
  228. package/dist/core/command-hooks/transport/exec-lifecycle.js +89 -30
  229. package/dist/core/config.d.ts +17 -12
  230. package/dist/core/config.js +106 -155
  231. package/dist/core/errors.d.ts +1 -0
  232. package/dist/core/errors.js +4 -0
  233. package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
  234. package/dist/core/feed/feed.d.ts +0 -6
  235. package/dist/core/feed/feed.js +68 -56
  236. package/dist/core/feed/inbox.d.ts +7 -81
  237. package/dist/core/feed/inbox.js +46 -378
  238. package/dist/core/feed/messages.d.ts +58 -0
  239. package/dist/core/feed/messages.js +217 -0
  240. package/dist/core/fs-utils.d.ts +4 -0
  241. package/dist/core/fs-utils.js +21 -1
  242. package/dist/core/human/answer.d.ts +1 -1
  243. package/dist/core/human/answer.js +1 -1
  244. package/dist/core/human/claim.d.ts +1 -1
  245. package/dist/core/human/claim.js +2 -4
  246. package/dist/core/human/component-docs.js +1 -1
  247. package/dist/core/human/feedback-companion.js +4 -3
  248. package/dist/core/human/feedback.d.ts +5 -8
  249. package/dist/core/human/feedback.js +57 -85
  250. package/dist/core/human/page-authoring.d.ts +30 -0
  251. package/dist/core/human/page-authoring.js +19 -0
  252. package/dist/core/human/requests.d.ts +136 -0
  253. package/dist/core/human/requests.js +515 -0
  254. package/dist/core/human/root.d.ts +0 -7
  255. package/dist/core/human/root.js +0 -17
  256. package/dist/core/human/scan.d.ts +2 -7
  257. package/dist/core/human/scan.js +48 -132
  258. package/dist/core/human/summary.d.ts +1 -1
  259. package/dist/core/human/summary.js +1 -1
  260. package/dist/core/human/tickets.d.ts +23 -83
  261. package/dist/core/human/tickets.js +92 -243
  262. package/dist/core/human/types.d.ts +5 -1
  263. package/dist/core/installed-plugins.js +8 -3
  264. package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
  265. package/dist/core/locked-file.d.ts +6 -3
  266. package/dist/core/locked-file.js +36 -9
  267. package/dist/core/memory/extensions.js +2 -0
  268. package/dist/core/preview-registry.js +0 -1
  269. package/dist/core/profiles/__tests__/env-store.test.js +2 -0
  270. package/dist/core/profiles/default-binding.d.ts +0 -4
  271. package/dist/core/profiles/default-binding.js +12 -58
  272. package/dist/core/profiles/env-store.js +8 -3
  273. package/dist/core/profiles/manifest.d.ts +2 -37
  274. package/dist/core/profiles/manifest.js +35 -161
  275. package/dist/core/profiles/select.js +3 -0
  276. package/dist/core/review/__tests__/capture-origin.test.js +13 -2
  277. package/dist/core/review/__tests__/comments.test.js +28 -10
  278. package/dist/core/review/__tests__/remap.test.js +28 -10
  279. package/dist/core/review/__tests__/stage-identity.test.js +17 -7
  280. package/dist/core/review/birth.d.ts +1 -1
  281. package/dist/core/review/birth.js +34 -5
  282. package/dist/core/review/comments.js +1 -1
  283. package/dist/core/review/companion.d.ts +1 -1
  284. package/dist/core/review/companion.js +2 -2
  285. package/dist/core/review/document.js +1 -1
  286. package/dist/core/review/project.d.ts +5 -1
  287. package/dist/core/review/project.js +14 -36
  288. package/dist/core/review/realize.d.ts +3 -6
  289. package/dist/core/review/realize.js +51 -168
  290. package/dist/core/review/remap.js +1 -1
  291. package/dist/core/review/stage.d.ts +3 -2
  292. package/dist/core/review/stage.js +2 -3
  293. package/dist/core/review/store.d.ts +3 -21
  294. package/dist/core/review/store.js +140 -186
  295. package/dist/core/review/types.d.ts +0 -1
  296. package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
  297. package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
  298. package/dist/core/runtime/broker/auth-reload.js +9 -10
  299. package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
  300. package/dist/core/runtime/broker/daemon-ops.js +42 -9
  301. package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
  302. package/dist/core/runtime/broker/extension-abort.js +11 -0
  303. package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
  304. package/dist/core/runtime/broker/fault-retry.js +142 -95
  305. package/dist/core/runtime/broker/inbox.d.ts +4 -32
  306. package/dist/core/runtime/broker/inbox.js +4 -92
  307. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  308. package/dist/core/runtime/broker/rebind.js +21 -12
  309. package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
  310. package/dist/core/runtime/broker-persona-guidance.js +2 -13
  311. package/dist/core/runtime/broker.js +28 -22
  312. package/dist/core/runtime/close.d.ts +3 -9
  313. package/dist/core/runtime/close.js +16 -27
  314. package/dist/core/runtime/fault.d.ts +3 -93
  315. package/dist/core/runtime/fault.js +9 -419
  316. package/dist/core/runtime/kickoff.d.ts +3 -6
  317. package/dist/core/runtime/kickoff.js +12 -18
  318. package/dist/core/runtime/launch.js +1 -1
  319. package/dist/core/runtime/lifecycle.js +40 -7
  320. package/dist/core/runtime/nodes.d.ts +5 -1
  321. package/dist/core/runtime/nodes.js +26 -11
  322. package/dist/core/runtime/placement.js +2 -2
  323. package/dist/core/runtime/recycle.js +24 -14
  324. package/dist/core/runtime/reset.js +26 -7
  325. package/dist/core/runtime/revive.js +226 -217
  326. package/dist/core/runtime/spawn.d.ts +4 -1
  327. package/dist/core/runtime/spawn.js +43 -5
  328. package/dist/core/runtime/warm-pool.d.ts +2 -2
  329. package/dist/core/runtime/warm-pool.js +23 -3
  330. package/dist/core/scoped-state/db.d.ts +7 -0
  331. package/dist/core/scoped-state/db.js +92 -0
  332. package/dist/core/scoped-state/migrate.d.ts +11 -0
  333. package/dist/core/scoped-state/migrate.js +198 -0
  334. package/dist/core/scoped-state/paths.d.ts +9 -0
  335. package/dist/core/scoped-state/paths.js +27 -0
  336. package/dist/core/scoped-state/profiles.d.ts +12 -0
  337. package/dist/core/scoped-state/profiles.js +62 -0
  338. package/dist/core/scoped-state/providers.d.ts +16 -0
  339. package/dist/core/scoped-state/providers.js +8 -0
  340. package/dist/core/scoped-state/schema.d.ts +3 -0
  341. package/dist/core/scoped-state/schema.js +26 -0
  342. package/dist/core/scoped-state/settings.d.ts +28 -0
  343. package/dist/core/scoped-state/settings.js +82 -0
  344. package/dist/core/subscription-state.d.ts +29 -48
  345. package/dist/core/subscription-state.js +176 -712
  346. package/dist/core/worktree-close.js +4 -4
  347. package/dist/core/worktree-mutation-async.js +2 -2
  348. package/dist/core/worktree-sweep.js +11 -16
  349. package/dist/core/worktree.js +6 -12
  350. package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
  351. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
  352. package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
  353. package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
  354. package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
  355. package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +41 -17
  356. package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
  357. package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
  358. package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
  359. package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
  360. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
  361. package/dist/daemon/api/__tests__/model-config.test.js +8 -8
  362. package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
  363. package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
  364. package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
  365. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +6 -3
  366. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
  367. package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
  368. package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
  369. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
  370. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
  371. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +29 -43
  372. package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
  373. package/dist/daemon/api/bridge.js +1 -1
  374. package/dist/daemon/api/handlers/bash-jobs.js +6 -7
  375. package/dist/daemon/api/handlers/broker-ops.js +47 -84
  376. package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
  377. package/dist/daemon/api/handlers/broker-recovery.js +251 -0
  378. package/dist/daemon/api/handlers/canvas.js +2 -44
  379. package/dist/daemon/api/handlers/daemon.js +41 -0
  380. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  381. package/dist/daemon/api/handlers/health.js +10 -5
  382. package/dist/daemon/api/handlers/human-requests.js +168 -188
  383. package/dist/daemon/api/handlers/human.js +3 -148
  384. package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
  385. package/dist/daemon/api/handlers/inbox-page.js +89 -0
  386. package/dist/daemon/api/handlers/inbox.js +21 -120
  387. package/dist/daemon/api/handlers/messages.d.ts +1 -0
  388. package/dist/daemon/api/handlers/messages.js +73 -15
  389. package/dist/daemon/api/handlers/node-outcomes.js +58 -13
  390. package/dist/daemon/api/handlers/nodes.d.ts +1 -0
  391. package/dist/daemon/api/handlers/nodes.js +41 -42
  392. package/dist/daemon/api/handlers/reviews.js +4 -29
  393. package/dist/daemon/api/map.d.ts +25 -3
  394. package/dist/daemon/api/map.js +84 -14
  395. package/dist/daemon/api/server.js +68 -9
  396. package/dist/daemon/control.d.ts +18 -0
  397. package/dist/daemon/cron/disposition.d.ts +2 -0
  398. package/dist/daemon/cron/disposition.js +8 -5
  399. package/dist/daemon/cron/settle.js +37 -13
  400. package/dist/daemon/cron/sinks.d.ts +18 -1
  401. package/dist/daemon/cron/sinks.js +47 -93
  402. package/dist/daemon/crtrd-cli.js +4 -19
  403. package/dist/daemon/crtrd.d.ts +2 -0
  404. package/dist/daemon/crtrd.js +206 -81
  405. package/dist/daemon/fleet.js +5 -6
  406. package/dist/daemon/human/finish.d.ts +12 -24
  407. package/dist/daemon/human/finish.js +88 -285
  408. package/dist/daemon/human/settle.d.ts +40 -0
  409. package/dist/daemon/human/settle.js +89 -0
  410. package/dist/daemon/human/sweep.d.ts +20 -5
  411. package/dist/daemon/human/sweep.js +56 -60
  412. package/dist/daemon/manage.d.ts +21 -16
  413. package/dist/daemon/manage.js +85 -51
  414. package/dist/daemon/messaging/node-message.d.ts +1 -1
  415. package/dist/daemon/messaging/node-message.js +9 -12
  416. package/dist/daemon/park-activity.js +3 -3
  417. package/dist/daemon/reconcilers/bash-deadline.js +6 -7
  418. package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
  419. package/dist/daemon/reconcilers/broker-supervision.js +41 -19
  420. package/dist/daemon/reconcilers/controller-death.js +6 -18
  421. package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
  422. package/dist/daemon/reconcilers/delivery-lane.js +130 -0
  423. package/dist/daemon/reconcilers/live-obligation.js +3 -3
  424. package/dist/daemon/reconcilers/node-deadline.js +1 -1
  425. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
  426. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
  427. package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
  428. package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
  429. package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
  430. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
  431. package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
  432. package/dist/daemon/review/companion.js +5 -11
  433. package/dist/daemon/review/deliver.d.ts +1 -5
  434. package/dist/daemon/review/deliver.js +3 -52
  435. package/dist/daemon/review/finish.d.ts +2 -27
  436. package/dist/daemon/review/finish.js +79 -263
  437. package/dist/daemon/review/sweep.d.ts +3 -10
  438. package/dist/daemon/review/sweep.js +13 -131
  439. package/dist/daemon/startup-policy.d.ts +0 -5
  440. package/dist/daemon/startup-policy.js +0 -5
  441. package/dist/hook-authoring.d.ts +28 -2
  442. package/dist/hook-authoring.js +53 -1
  443. package/dist/index.d.ts +3 -2
  444. package/dist/index.js +1 -1
  445. package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
  446. package/dist/migrations/__tests__/activation.test.js +9 -6
  447. package/dist/migrations/activation.d.ts +3 -2
  448. package/dist/migrations/activation.js +2 -4
  449. package/dist/migrations/corpus.d.ts +2 -5
  450. package/dist/migrations/corpus.js +9 -20
  451. package/dist/migrations/runner.d.ts +3 -1
  452. package/dist/migrations/runner.js +6 -2
  453. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
  454. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
  455. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
  456. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
  457. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
  458. package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
  459. package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
  460. package/dist/pi-extensions/canvas-passive-context.js +85 -37
  461. package/dist/pi-extensions/canvas-stophook.js +47 -31
  462. package/dist/shared/env.d.ts +1 -0
  463. package/dist/shared/env.js +3 -0
  464. package/package.json +1 -1
  465. package/runtime.lock.json +5 -5
  466. package/dist/clients/attach/__tests__/page-block.test.js +0 -54
  467. package/dist/clients/inbox/review-visibility.d.ts +0 -5
  468. package/dist/clients/inbox/review-visibility.js +0 -19
  469. package/dist/commands/canvas-rebuild-index.d.ts +0 -2
  470. package/dist/commands/canvas-rebuild-index.js +0 -46
  471. package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
  472. package/dist/core/__tests__/human-deliver.test.d.ts +0 -1
  473. package/dist/core/__tests__/human-deliver.test.js +0 -256
  474. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
  475. package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
  476. package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
  477. package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
  478. package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
  479. package/dist/core/__tests__/phase4-review-store.test.js +0 -144
  480. package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
  481. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
  482. package/dist/core/canvas/human-deliveries.d.ts +0 -39
  483. package/dist/core/canvas/human-deliveries.js +0 -75
  484. package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
  485. package/dist/core/canvas/node-outcome-deliveries.js +0 -159
  486. package/dist/core/feed/passive.d.ts +0 -3
  487. package/dist/core/feed/passive.js +0 -53
  488. package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
  489. package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
  490. package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
  491. package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
  492. package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
  493. package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
  494. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
  495. package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
  496. package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
  497. package/dist/core/human/__tests__/page-tickets.test.js +0 -161
  498. package/dist/core/review/ticket-filter.d.ts +0 -17
  499. package/dist/core/review/ticket-filter.js +0 -21
  500. package/dist/core/runtime/broker/passive.d.ts +0 -17
  501. package/dist/core/runtime/broker/passive.js +0 -44
  502. package/dist/core/runtime/busy.d.ts +0 -46
  503. package/dist/core/runtime/busy.js +0 -150
  504. package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
  505. package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
  506. package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
  507. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
  508. package/dist/daemon/human/deliver-action.d.ts +0 -3
  509. package/dist/daemon/human/deliver-action.js +0 -9
  510. package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
  511. package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
  512. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
  513. package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
  514. package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
  515. package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
  516. package/dist/daemon/startup-block-marker.d.ts +0 -25
  517. package/dist/daemon/startup-block-marker.js +0 -99
  518. /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
  519. /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
  520. /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
@@ -1,11 +1,19 @@
1
1
  import assert from 'node:assert/strict';
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import test from 'node:test';
5
5
  import { contextDir, jobDir, nodeDir, reportsDir } from '../../canvas/paths.js';
6
+ import { listNodes, listWarmSpares } from '../../canvas/canvas.js';
7
+ import { readExternalActionDelivery } from '../../canvas/deliveries.js';
6
8
  import { validateHookManifest } from '../../command-hooks/schema.js';
7
9
  import { headlessBrokerHost } from '../../runtime/host.js';
8
10
  import { reviveNode } from '../../runtime/revive.js';
11
+ import { handleCreate, handleFork } from '../../../daemon/api/handlers/nodes.js';
12
+ import { armCron, getCron } from '../../canvas/crons.js';
13
+ import { settleRun } from '../../../daemon/cron/settle.js';
14
+ import { DeliveryLaneReconciler } from '../../../daemon/reconcilers/delivery-lane.js';
15
+ import { relaunchRoot } from '../../runtime/reset.js';
16
+ import { recycleNode } from '../../runtime/recycle.js';
9
17
  import { createHarness } from '../helpers/harness.js';
10
18
  function installLifecyclePlugin(harness, name, op) {
11
19
  const ops = typeof op === 'string' ? [op] : op;
@@ -53,6 +61,64 @@ process.stdin.on('end', () => {
53
61
  `);
54
62
  chmodSync(executable, 0o755);
55
63
  }
64
+ function installCreateLifecyclePlugin(harness, name, declarations, behavior) {
65
+ const root = join(harness.agentHome, '.crouter', 'plugins', name);
66
+ const records = join(root, 'records.jsonl');
67
+ mkdirSync(join(root, '.crouter-plugin'), { recursive: true });
68
+ writeFileSync(join(root, '.crouter-plugin', 'plugin.json'), JSON.stringify({
69
+ name,
70
+ version: '0.1.0',
71
+ description: 'node create lifecycle fixture',
72
+ hooks: 'hooks.json',
73
+ hookExecutable: 'bin/hook.js',
74
+ }));
75
+ writeFileSync(join(root, 'hooks.json'), JSON.stringify({
76
+ schemaVersion: 2,
77
+ lifecycle: declarations.map((declaration) => ({ ...declaration, description: `${declaration.op} fixture`, effects: ['writes fixture state'] })),
78
+ }));
79
+ const executable = join(root, 'bin', 'hook.js');
80
+ mkdirSync(join(root, 'bin'), { recursive: true });
81
+ writeFileSync(executable, `#!/usr/bin/env node
82
+ const { appendFileSync, existsSync, readdirSync } = require('node:fs');
83
+ const { join } = require('node:path');
84
+ const behavior = ${JSON.stringify(behavior)};
85
+ let input = '';
86
+ process.stdin.on('data', (chunk) => { input += chunk; });
87
+ process.stdin.on('end', () => {
88
+ const request = JSON.parse(input);
89
+ appendFileSync(${JSON.stringify(records)}, JSON.stringify(request) + '\\n');
90
+ if (request.event === 'node:create' && behavior === 'refuse') {
91
+ process.stdout.write(JSON.stringify({ protocolVersion: 1, ok: false, error: { code: 'refused', message: 'fixture refusal' } }));
92
+ return;
93
+ }
94
+ if (request.event === 'node:create' && behavior === 'crash') process.exit(1);
95
+ if (request.event === 'node:create' && behavior === 'hang') return setInterval(() => {}, 1000);
96
+ if (request.event === 'node:create' && behavior === 'bad-output') return process.stdout.write('not an envelope');
97
+ if (request.event === 'node:create' && behavior === 'second-refuses') {
98
+ const nodes = join(request.runtime.canvasHome, 'nodes');
99
+ if (existsSync(nodes) && readdirSync(nodes).some((entry) => entry !== 'index.json')) {
100
+ process.stdout.write(JSON.stringify({ protocolVersion: 1, ok: false, error: { code: 'occupied', message: 'second create refused' } }));
101
+ return;
102
+ }
103
+ }
104
+ if (request.event === 'node:start' && behavior === 'start-refuses') {
105
+ process.stdout.write(JSON.stringify({ protocolVersion: 1, ok: false, error: { code: 'ignored', message: 'start refusal' } }));
106
+ return;
107
+ }
108
+ process.stdout.write(JSON.stringify({ protocolVersion: 1, ok: true }));
109
+ });
110
+ `);
111
+ chmodSync(executable, 0o755);
112
+ return { root, records };
113
+ }
114
+ function createContext(body, params = {}) {
115
+ return { method: 'POST', path: '/v1/nodes', params, query: new URLSearchParams(), body };
116
+ }
117
+ function lifecycleRecords(path) {
118
+ return existsSync(path)
119
+ ? readFileSync(path, 'utf8').trim().split('\n').filter(Boolean).map((line) => JSON.parse(line))
120
+ : [];
121
+ }
56
122
  function installV1CommandHook(harness) {
57
123
  const root = join(harness.agentHome, '.crouter', 'plugins', 'z-v1-command');
58
124
  mkdirSync(join(root, '.crouter-plugin'), { recursive: true });
@@ -149,6 +215,244 @@ test('node:start hooks receive resolved paths before broker launch; invalid and
149
215
  await harness.dispose();
150
216
  }
151
217
  });
218
+ test('node:create fails closed before a row exists and node:start fires on cold birth', async () => {
219
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-create-hooks' });
220
+ try {
221
+ const birth = installCreateLifecyclePlugin(harness, 'birth-fixture', [
222
+ { event: 'node:create', phase: 'before', op: 'record-create' },
223
+ { event: 'node:start', phase: 'on', op: 'record-start' },
224
+ ], 'start-refuses');
225
+ const created = await handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' }));
226
+ const nodeId = created.body.node_id;
227
+ await harness.awaitBoot(nodeId);
228
+ const records = lifecycleRecords(birth.records);
229
+ const create = records.find((record) => record.event === 'node:create');
230
+ assert.deepEqual(create.create, {
231
+ kind: 'developer', mode: 'base', cwd: harness.agentHome, profile: null, root: true, parentId: null, requestingNodeId: null, lifecycle: 'resident', replaces: null,
232
+ });
233
+ assert.deepEqual(create.runtime, { canvasHome: harness.home, profile: null });
234
+ assert.deepEqual(create.context, { cwd: harness.agentHome });
235
+ assert.equal(Object.hasOwn(create, 'node'), false);
236
+ const start = records.find((record) => record.event === 'node:start');
237
+ assert.equal(start.runtime.isBirth, true);
238
+ assert.ok(harness.node(nodeId), 'a refusing node:start hook leaves the cold birth launched');
239
+ rmSync(birth.root, { recursive: true, force: true });
240
+ for (const behavior of ['refuse', 'crash', 'bad-output', 'hang']) {
241
+ const plugin = installCreateLifecyclePlugin(harness, `create-${behavior}`, [{ event: 'node:create', phase: 'before', op: behavior }], behavior);
242
+ const count = listNodes().length;
243
+ await assert.rejects(() => handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' })), (error) => {
244
+ assert.equal(error.code, 'node_create_refused');
245
+ assert.match(error.message, new RegExp(`${behavior === 'refuse' ? 'fixture refusal' : `plugin "create-${behavior}" lifecycle hook "${behavior}"`}`));
246
+ return true;
247
+ });
248
+ assert.equal(listNodes().length, count, `${behavior} leaves no node row`);
249
+ rmSync(plugin.root, { recursive: true, force: true });
250
+ }
251
+ const missing = installCreateLifecyclePlugin(harness, 'create-missing', [{ event: 'node:create', phase: 'before', op: 'missing-op' }], 'allow');
252
+ rmSync(join(missing.root, 'bin', 'hook.js'));
253
+ await assert.rejects(() => handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' })), (error) => {
254
+ assert.equal(error.code, 'node_create_refused');
255
+ assert.match(error.message, /plugin "create-missing" lifecycle hook "missing-op"/);
256
+ return true;
257
+ });
258
+ rmSync(missing.root, { recursive: true, force: true });
259
+ const malformed = installCreateLifecyclePlugin(harness, 'create-malformed', [
260
+ { event: 'node:create', phase: 'before', op: 'valid-create' },
261
+ { event: 'node:create', phase: 'on', op: 'invalid-create' },
262
+ ], 'allow');
263
+ const count = listNodes().length;
264
+ await assert.rejects(() => handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' })), (error) => {
265
+ assert.equal(error.code, 'node_create_refused');
266
+ assert.match(error.message, /plugin "create-malformed" lifecycle hook "valid-create"/);
267
+ return true;
268
+ });
269
+ assert.equal(listNodes().length, count, 'a later malformed node:create declaration leaves no node row');
270
+ rmSync(malformed.root, { recursive: true, force: true });
271
+ const malformedOtherLifecycle = installCreateLifecyclePlugin(harness, 'create-malformed-other-lifecycle', [
272
+ { event: 'node:start', phase: 'before', op: 'invalid-start' },
273
+ { event: 'node:create', phase: 'before', op: 'valid-create' },
274
+ ], 'allow');
275
+ await assert.rejects(() => handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' })), (error) => {
276
+ assert.equal(error.code, 'node_create_refused');
277
+ assert.match(error.message, /plugin "create-malformed-other-lifecycle" lifecycle hook "valid-create"/);
278
+ return true;
279
+ });
280
+ rmSync(malformedOtherLifecycle.root, { recursive: true, force: true });
281
+ }
282
+ finally {
283
+ await harness.dispose();
284
+ }
285
+ });
286
+ test('node:fork runs the node:create gate for its new resident root', async () => {
287
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-fork-create' });
288
+ try {
289
+ const plugin = installCreateLifecyclePlugin(harness, 'fork-create', [{ event: 'node:create', phase: 'before', op: 'record-fork' }], 'allow');
290
+ const source = await handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' }));
291
+ const sourceId = source.body.node_id;
292
+ await harness.awaitBoot(sourceId);
293
+ const fork = await handleFork(createContext({}, { id: sourceId }));
294
+ const forkId = fork.body.node_id;
295
+ await harness.awaitBoot(forkId);
296
+ const creates = lifecycleRecords(plugin.records).filter((record) => record.event === 'node:create');
297
+ assert.equal(creates.length, 2);
298
+ assert.deepEqual(creates[1].create, {
299
+ kind: 'developer', mode: 'base', cwd: harness.agentHome, profile: null, root: true, parentId: null, requestingNodeId: null, lifecycle: 'resident', replaces: null,
300
+ });
301
+ }
302
+ finally {
303
+ await harness.dispose();
304
+ }
305
+ });
306
+ test('node:create gates warm mint and warm claim with the birth payload', async () => {
307
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-create-warm' });
308
+ const priorWarmPool = process.env['CRTR_WARM_POOL'];
309
+ process.env['CRTR_WARM_POOL'] = '1';
310
+ try {
311
+ const plugin = installCreateLifecyclePlugin(harness, 'warm-create', [{ event: 'node:create', phase: 'before', op: 'record-warm' }], 'allow');
312
+ const first = await handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '', prefer_warm: true }));
313
+ await harness.awaitBoot(first.body.node_id);
314
+ let spareId;
315
+ for (let i = 0; i < 100; i++) {
316
+ spareId = listWarmSpares()[0]?.node_id;
317
+ if (spareId !== undefined)
318
+ break;
319
+ await new Promise((resolve) => setTimeout(resolve, 25));
320
+ }
321
+ assert.ok(spareId, 'the cold create refills one warm spare');
322
+ await harness.awaitBoot(spareId);
323
+ const createsBeforeClaim = lifecycleRecords(plugin.records).filter((record) => record.event === 'node:create').length;
324
+ const second = await handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '', prefer_warm: true }));
325
+ assert.equal(second.body.node_id, spareId, 'the second request claims the pre-born spare');
326
+ const creates = lifecycleRecords(plugin.records).filter((record) => record.event === 'node:create');
327
+ assert.equal(creates.length, createsBeforeClaim + 1, 'claiming the spare runs one additional create gate');
328
+ for (const record of creates) {
329
+ assert.deepEqual(record.create, {
330
+ kind: 'developer', mode: 'base', cwd: harness.agentHome, profile: null, root: true, parentId: null, requestingNodeId: null, lifecycle: 'resident', replaces: null,
331
+ });
332
+ }
333
+ let replacementSpareId;
334
+ for (let i = 0; i < 100; i++) {
335
+ replacementSpareId = listWarmSpares()[0]?.node_id;
336
+ if (replacementSpareId !== undefined)
337
+ break;
338
+ await new Promise((resolve) => setTimeout(resolve, 25));
339
+ }
340
+ assert.ok(replacementSpareId, 'a claimed spare is refilled before the test tears down its harness');
341
+ await harness.awaitBoot(replacementSpareId);
342
+ }
343
+ finally {
344
+ if (priorWarmPool === undefined)
345
+ delete process.env['CRTR_WARM_POOL'];
346
+ else
347
+ process.env['CRTR_WARM_POOL'] = priorWarmPool;
348
+ await harness.dispose();
349
+ }
350
+ });
351
+ test('a refused recycle leaves the old root active', async () => {
352
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-create-recycle' });
353
+ const priorWarmPool = process.env['CRTR_WARM_POOL'];
354
+ process.env['CRTR_WARM_POOL'] = '0';
355
+ try {
356
+ const source = await handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' }));
357
+ const sourceId = source.body.node_id;
358
+ await harness.awaitBoot(sourceId);
359
+ installCreateLifecyclePlugin(harness, 'recycle-refusal', [{ event: 'node:create', phase: 'before', op: 'refuse-recycle' }], 'refuse');
360
+ await assert.rejects(() => recycleNode(sourceId, '%recycle-refusal'), /fixture refusal/);
361
+ assert.equal(harness.node(sourceId)?.status, 'active', 'the old root is unchanged when the replacement hook refuses');
362
+ }
363
+ finally {
364
+ if (priorWarmPool === undefined)
365
+ delete process.env['CRTR_WARM_POOL'];
366
+ else
367
+ process.env['CRTR_WARM_POOL'] = priorWarmPool;
368
+ await harness.dispose();
369
+ }
370
+ });
371
+ test('node:create reports the root replaced by /new', async () => {
372
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-create-relaunch' });
373
+ const priorWarmPool = process.env['CRTR_WARM_POOL'];
374
+ process.env['CRTR_WARM_POOL'] = '0';
375
+ try {
376
+ const plugin = installCreateLifecyclePlugin(harness, 'relaunch-create', [{ event: 'node:create', phase: 'before', op: 'record-relaunch' }], 'allow');
377
+ const source = await handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' }));
378
+ const sourceId = source.body.node_id;
379
+ await harness.awaitBoot(sourceId);
380
+ const result = await relaunchRoot(sourceId, {
381
+ launchBroker: () => ({ pid: 999_999, exited: new Promise(() => { }) }),
382
+ waitForViewSocket: () => true,
383
+ respawnViewer: () => true,
384
+ teardownBroker: async () => ({ confirmed: true }),
385
+ });
386
+ assert.ok(result);
387
+ const create = lifecycleRecords(plugin.records).filter((record) => record.event === 'node:create').at(-1);
388
+ assert.deepEqual(create.create, {
389
+ kind: 'developer', mode: 'base', cwd: harness.agentHome, profile: null, root: true, parentId: null, requestingNodeId: null, lifecycle: 'resident', replaces: sourceId,
390
+ });
391
+ }
392
+ finally {
393
+ if (priorWarmPool === undefined)
394
+ delete process.env['CRTR_WARM_POOL'];
395
+ else
396
+ process.env['CRTR_WARM_POOL'] = priorWarmPool;
397
+ await harness.dispose();
398
+ }
399
+ });
400
+ test('a refused cron spawn records its failure and does not throw', async () => {
401
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-create-cron' });
402
+ try {
403
+ installCreateLifecyclePlugin(harness, 'cron-create', [{ event: 'node:create', phase: 'before', op: 'refuse-cron' }], 'refuse');
404
+ armCron({
405
+ cron_id: 'refused-cron-spawn', name: 'refused cron spawn', created_by: null, command: 'echo fixture', fire_at: new Date().toISOString(), recur: null, tz: null, expires_at: null, anchor_node: null, cancel_on_wake: false,
406
+ cwd: harness.agentHome, env_json: null, profile: null, scope: 'global', run_timeout_s: 30, overlap: 'skip', on_output: 'always', sink: JSON.stringify({ spawn: 'developer' }), tier: 'normal',
407
+ });
408
+ const cron = getCron('refused-cron-spawn');
409
+ const record = await settleRun(cron, {
410
+ outOfBand: false,
411
+ outcome: {
412
+ kind: 'disposed',
413
+ ctx: {
414
+ runId: 'refused-cron-run', startedAtIso: new Date().toISOString(), exitCode: 0, timedOut: false, runTimeoutS: 30, processErrorMessage: null,
415
+ stdoutHead: 'fixture output', stdoutSha256: 'fixture-output-hash', stderrHead: '',
416
+ },
417
+ },
418
+ record: { runId: 'refused-cron-run', startedAtIso: new Date().toISOString(), finishedAtMs: Date.now(), durationMs: 1, exitCode: 0, stdoutHead: 'fixture output', stderrHead: null },
419
+ lease: 'none',
420
+ });
421
+ assert.match(record.delivered ?? '', /^delivered: /, 'the cron run records its durable delivery');
422
+ const pendingDelivery = readExternalActionDelivery('refused-cron-run');
423
+ assert.equal(pendingDelivery.state, 'pending');
424
+ assert.equal(pendingDelivery.destinationId, 'cron_spawn');
425
+ assert.equal(getCron(cron.cron_id), null, 'the one-shot occurrence is spent once its delivery is durable');
426
+ const pending = [];
427
+ new DeliveryLaneReconciler().run(Date.now(), {
428
+ lifecycle: { acceptsDetachedWork: () => true, registerDetached: (work) => { pending.push(work); } },
429
+ });
430
+ await Promise.all(pending);
431
+ const settledDelivery = readExternalActionDelivery('refused-cron-run');
432
+ assert.equal(settledDelivery.state, 'permanent_failed');
433
+ assert.match(settledDelivery.lastFailure?.message ?? '', /fixture refusal/);
434
+ assert.equal(listNodes().length, 0, 'the refused delivery neither creates a node nor escalates');
435
+ }
436
+ finally {
437
+ await harness.dispose();
438
+ }
439
+ });
440
+ test('node:create serializes concurrent creates through the persisted row', async () => {
441
+ const harness = await createHarness({ headless: true, sessionPrefix: 'crtr-node-create-lock' });
442
+ try {
443
+ installCreateLifecyclePlugin(harness, 'create-lock', [{ event: 'node:create', phase: 'before', op: 'only-one' }], 'second-refuses');
444
+ const request = () => handleCreate(createContext({ kind: 'developer', root: true, profile: null, cwd: harness.agentHome, prompt: '' }));
445
+ const results = await Promise.allSettled([request(), request()]);
446
+ assert.equal(results.filter((result) => result.status === 'fulfilled').length, 1);
447
+ const refusal = results.find((result) => result.status === 'rejected');
448
+ assert.equal(refusal.reason.code, 'node_create_refused');
449
+ assert.match(refusal.reason.message, /second create refused/);
450
+ assert.equal(listNodes().length, 1);
451
+ }
452
+ finally {
453
+ await harness.dispose();
454
+ }
455
+ });
152
456
  test('hooks.json v1 remains command-only while v2 accepts a lifecycle-only declaration', () => {
153
457
  const v1 = validateHookManifest({
154
458
  schemaVersion: 1,
@@ -176,10 +480,17 @@ test('hooks.json v1 remains command-only while v2 accepts a lifecycle-only decla
176
480
  lifecycle: [{ event: 'node:close', phase: 'on', op: 'cleanup', description: 'cleanup', effects: ['removes a directory'] }],
177
481
  });
178
482
  assert.deepEqual(close.issues, []);
483
+ const create = validateHookManifest({
484
+ schemaVersion: 2,
485
+ lifecycle: [{ event: 'node:create', phase: 'before', op: 'guard', description: 'guard', effects: ['refuses a create'] }],
486
+ });
487
+ assert.deepEqual(create.issues, []);
179
488
  for (const manifest of [
180
489
  { schemaVersion: 2, hooks: [], lifecycle: [] },
181
490
  { schemaVersion: 2, lifecycle: [{ event: 'node:stop', phase: 'on', op: 'prepare', description: 'prepare', effects: ['creates a directory'] }] },
182
491
  { schemaVersion: 2, lifecycle: [{ event: 'node:start', phase: 'before', op: 'prepare', description: 'prepare', effects: ['creates a directory'] }] },
492
+ { schemaVersion: 2, lifecycle: [{ event: 'node:create', phase: 'on', op: 'prepare', description: 'prepare', effects: ['creates a directory'] }] },
493
+ { schemaVersion: 2, lifecycle: [{ event: 'node:close', phase: 'before', op: 'prepare', description: 'prepare', effects: ['creates a directory'] }] },
183
494
  { schemaVersion: 2, lifecycle: [{ event: 'node:start', phase: 'on', op: 'prepare', description: 'prepare', effects: ['creates a directory'], unexpected: true }] },
184
495
  ]) {
185
496
  assert.equal(validateHookManifest(manifest).manifest, undefined, 'invalid v2 lifecycle declarations are rejected');
@@ -31,6 +31,8 @@ import { join } from 'node:path';
31
31
  import * as tar from 'tar';
32
32
  import { resetScopeCache, userScopeRoot } from '../../scope.js';
33
33
  import { readState, readConfig } from '../../config.js';
34
+ import { closeScopedStores } from '../../scoped-state/db.js';
35
+ import { initializeFreshScopedStore } from '../../scoped-state/migrate.js';
34
36
  import { revalidateBundlePlugins, forceRevalidateBundlePlugin } from '../../command-plugins/revalidate.js';
35
37
  import { pluginInstall } from '../../../commands/pkg/plugin-manage.js';
36
38
  import { runCli } from '../../command.js';
@@ -68,6 +70,7 @@ beforeEach(async () => {
68
70
  isolatedCwd = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-revalidate-cwd-')));
69
71
  process.env.HOME = isolatedHome;
70
72
  process.chdir(isolatedCwd);
73
+ initializeFreshScopedStore({ kind: 'user', root: join(isolatedHome, '.crouter') });
71
74
  resetScopeCache();
72
75
  served = { archive: makeArchive('v1'), etag: '"sha256-v1"' };
73
76
  requests = [];
@@ -105,6 +108,7 @@ beforeEach(async () => {
105
108
  afterEach(async () => {
106
109
  await new Promise((resolve) => server.close(() => resolve()));
107
110
  process.chdir(prevCwd);
111
+ closeScopedStores();
108
112
  rmSync(isolatedHome, { recursive: true, force: true });
109
113
  rmSync(isolatedCwd, { recursive: true, force: true });
110
114
  });
@@ -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());