@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
@@ -20,7 +20,7 @@
20
20
  // wake-worthy work" (no revive, relaunch-attempt state cleared); a
21
21
  // non-deferred unseen entry still wakes exactly as before, and delivers
22
22
  // the previously-held deferred entry alongside it with no loss (neither
23
- // entry is ever dropped or skipped by the cursor).
23
+ // delivery is ever dropped or acknowledged before settlement).
24
24
  //
25
25
  // (1) and (2) are exercised through the REAL `node message send` command
26
26
  // boundary (mirrors push-final-guard.test.ts's #339/#340 pattern) with
@@ -47,7 +47,11 @@ import { DatabaseSync } from 'node:sqlite';
47
47
  import { createNode, deleteNode, getNode, setFrozen, setStatus, subscribe, withFreshTerminalGuard } from '../../canvas/canvas.js';
48
48
  import { hasNoNaturalCycle } from '../../runtime/revive-all.js';
49
49
  import { canvasDbPath, nodeDir, apiSocketPath } from '../../canvas/paths.js';
50
- import { closeDb } from '../../canvas/db.js';
50
+ import { closeDb, migrate, openDb } from '../../canvas/db.js';
51
+ import { recordExecutionPid } from '../../canvas/executions.js';
52
+ import { closeScopedStores } from '../../scoped-state/db.js';
53
+ import { initializeFreshScopedStore } from '../../scoped-state/migrate.js';
54
+ import { userScopeRoot } from '../../scope.js';
51
55
  import { createApiServer } from '../../../daemon/api/server.js';
52
56
  import { CrtrClient } from '../../../api/index.js';
53
57
  import { BROKER_IDLE_TURN_START } from '../../runtime/broker/held-deferred-inbox.js';
@@ -55,7 +59,9 @@ import { registerNode } from '../../../commands/node.js';
55
59
  import { pushFinal } from '../../feed/feed.js';
56
60
  import { InputError } from '../../io.js';
57
61
  import { headlessBrokerHost } from '../../runtime/host.js';
58
- import { appendInbox, readInboxSince, readCursor } from '../../feed/inbox.js';
62
+ import { enqueueNodeMail } from '../../feed/messages.js';
63
+ import { acknowledgedNodeMailCount } from '../../canvas/deliveries.js';
64
+ import { beginExecution } from '../../canvas/executions.js';
59
65
  import { superviseTick } from '../../../daemon/crtrd.js';
60
66
  import registerCanvasInboxWatcher from '../../../pi-extensions/canvas-inbox-watcher.js';
61
67
  import { bindTestFleet } from '../helpers/fleet.js';
@@ -63,6 +69,7 @@ import { resetFleetForTests } from '../../runtime/fleet.js';
63
69
  let home;
64
70
  let server;
65
71
  const prevNodeId = process.env['CRTR_NODE_ID'];
72
+ const prevHome = process.env['HOME'];
66
73
  // createApiServer binds its socket asynchronously (file created in the listen
67
74
  // callback), and the socket FILE existing does not yet prove the server answers
68
75
  // a full HTTP round-trip — under serial recycle the first request to a
@@ -86,6 +93,25 @@ async function waitForSocket(path) {
86
93
  }
87
94
  throw new Error(`api socket never answered /healthz at ${path}`);
88
95
  }
96
+ function inboxRows(nodeId) {
97
+ return openDb().prepare(`SELECT m.message_id, d.urgency, d.state, d.acknowledged_at FROM deliveries d JOIN messages m ON m.message_id = d.message_id
98
+ WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.canceled_at IS NULL ORDER BY m.seq`).all(nodeId).map((row) => ({
99
+ messageId: row.message_id,
100
+ tier: row.urgency,
101
+ state: row.state,
102
+ acknowledgedAt: row.acknowledged_at,
103
+ }));
104
+ }
105
+ function sendMail(nodeId, tier, body, senderNodeId = 'human') {
106
+ return enqueueNodeMail(nodeId, {
107
+ ...(senderNodeId === null ? {} : { senderNodeId }),
108
+ kind: 'message',
109
+ label: body,
110
+ bodyJson: { body },
111
+ urgency: tier,
112
+ wake: 1,
113
+ });
114
+ }
89
115
  function node(id, over = {}) {
90
116
  return {
91
117
  node_id: id,
@@ -99,6 +125,14 @@ function node(id, over = {}) {
99
125
  ...over,
100
126
  };
101
127
  }
128
+ function createFixtureNode(meta) {
129
+ createNode(meta);
130
+ if (meta.pi_pid == null)
131
+ return null;
132
+ const executionId = beginExecution(meta.node_id, { daemonEpoch: 'test-epoch' });
133
+ recordExecutionPid(executionId, meta.node_id, meta.pi_pid);
134
+ return executionId;
135
+ }
102
136
  /** A pid guaranteed dead by the time spawnSync returns. */
103
137
  function deadPid() {
104
138
  const r = spawnSync('true', [], { stdio: 'ignore' });
@@ -113,6 +147,7 @@ function messageSendLeaf() {
113
147
  }
114
148
  before(() => {
115
149
  home = mkdtempSync(join(tmpdir(), 'crtr-deferred-nowake-'));
150
+ process.env['HOME'] = home;
116
151
  process.env['CRTR_HOME'] = home;
117
152
  // superviseTick and the in-process API server's launch verbs resolve
118
153
  // boundFleet() (D-2). Empty is the point: no fabricated row here has a live
@@ -121,8 +156,11 @@ before(() => {
121
156
  });
122
157
  beforeEach(async () => {
123
158
  closeDb();
159
+ closeScopedStores();
124
160
  rmSync(home, { recursive: true, force: true });
125
161
  mkdirSync(home, { recursive: true });
162
+ migrate(openDb());
163
+ initializeFreshScopedStore({ kind: 'user', root: userScopeRoot() });
126
164
  // POST-P1 the `node message send` leaf forwards its wake/revive canvas ops
127
165
  // through cliClient() over crtrd's socket. This home neutralizes the daemon (no REAL
128
166
  // crtrd) but binds no server, so stand one up in-process on the isolated
@@ -135,11 +173,17 @@ afterEach(async () => {
135
173
  await server?.close();
136
174
  server = undefined;
137
175
  closeDb();
176
+ closeScopedStores();
138
177
  });
139
178
  after(() => {
140
179
  closeDb();
180
+ closeScopedStores();
141
181
  rmSync(home, { recursive: true, force: true });
142
182
  delete process.env['CRTR_HOME'];
183
+ if (prevHome === undefined)
184
+ delete process.env['HOME'];
185
+ else
186
+ process.env['HOME'] = prevHome;
143
187
  if (prevNodeId === undefined)
144
188
  delete process.env['CRTR_NODE_ID'];
145
189
  else
@@ -153,7 +197,7 @@ test('node message send --tier deferred to an ordinary dormant (idle-released) t
153
197
  // terminal target takes no such cycle, so its mail is raised to the steering
154
198
  // tier instead (locked by the next test).
155
199
  createNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release' }));
156
- const inboxBefore = readInboxSince(id, undefined).length;
200
+ const inboxBefore = inboxRows(id).length;
157
201
  const originalLaunch = headlessBrokerHost.launch;
158
202
  let launchCalls = 0;
159
203
  headlessBrokerHost.launch = ((...args) => {
@@ -169,14 +213,14 @@ test('node message send --tier deferred to an ordinary dormant (idle-released) t
169
213
  headlessBrokerHost.launch = originalLaunch;
170
214
  }
171
215
  assert.equal(launchCalls, 0, 'a deferred delivery to a dormant target must never attempt a revive');
172
- assert.equal(readInboxSince(id, undefined).length, inboxBefore + 1, 'the message is durably appended despite the hold');
216
+ assert.equal(inboxRows(id).length, inboxBefore + 1, 'the SQL delivery is durable despite the hold');
173
217
  const after = getNode(id);
174
218
  assert.equal(after.status, 'idle', 'target left exactly where it was');
175
219
  assert.equal(after.intent, 'idle-release', 'target left exactly where it was');
176
220
  });
177
221
  test('node message send --tier deferred to a TERMINAL-lifecycle target is raised to the steering tier and wakes it', { timeout: 30_000 }, async () => {
178
222
  const id = 'terminalLifecycleTarget';
179
- createNode(node(id, { status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
223
+ createFixtureNode(node(id, { status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
180
224
  // Fully stubbed (never delegates): the point is that the raised entry DOES
181
225
  // attempt the wake, so a real detached broker must not be spawned to prove it.
182
226
  const originalLaunch = headlessBrokerHost.launch;
@@ -192,9 +236,9 @@ test('node message send --tier deferred to a TERMINAL-lifecycle target is raised
192
236
  finally {
193
237
  headlessBrokerHost.launch = originalLaunch;
194
238
  }
195
- const entries = readInboxSince(id, undefined);
196
- assert.equal(entries.length, 1, 'the message is durably appended');
197
- assert.equal(entries[0].tier, 'urgent', 'a terminal target takes no later cycle, so the entry is raised to the steering tier');
239
+ const entries = inboxRows(id);
240
+ assert.equal(entries.length, 1, 'the SQL delivery is durable');
241
+ assert.equal(entries[0].tier, 'urgent', 'a terminal target takes no later cycle, so the delivery is raised to the steering tier');
198
242
  assert.equal(launchCalls, 1, 'the raised entry wakes the dormant terminal target instead of waiting for a cycle it never takes');
199
243
  assert.equal(res['revived'], true, 'the send reports the wake it actually performed');
200
244
  });
@@ -202,7 +246,7 @@ test('node message send --tier deferred rejects ordinary terminal targets but ac
202
246
  for (const status of ['done', 'canceled']) {
203
247
  const id = `terminal-${status}`;
204
248
  createNode(node(id, { status, intent: null }));
205
- const inboxBefore = readInboxSince(id, undefined).length;
249
+ const inboxBefore = inboxRows(id).length;
206
250
  const originalLaunch = headlessBrokerHost.launch;
207
251
  let launchCalls = 0;
208
252
  headlessBrokerHost.launch = ((...args) => {
@@ -216,7 +260,7 @@ test('node message send --tier deferred rejects ordinary terminal targets but ac
216
260
  headlessBrokerHost.launch = originalLaunch;
217
261
  }
218
262
  assert.equal(launchCalls, 0, `${status}: a rejected deferred must never attempt a revive`);
219
- assert.equal(readInboxSince(id, undefined).length, inboxBefore, `${status}: a rejected deferred must not append an inbox entry`);
263
+ assert.equal(inboxRows(id).length, inboxBefore, `${status}: a rejected deferred must not create a SQL delivery`);
220
264
  }
221
265
  const frozenId = 'terminal-frozen';
222
266
  createNode(node(frozenId, {
@@ -225,25 +269,20 @@ test('node message send --tier deferred rejects ordinary terminal targets but ac
225
269
  intent: 'parked',
226
270
  }));
227
271
  setFrozen(frozenId, new Date().toISOString());
228
- const inboxBefore = readInboxSince(frozenId, undefined).length;
272
+ const inboxBefore = inboxRows(frozenId).length;
229
273
  const result = await messageSendLeaf().run({ to: frozenId, tier: 'deferred', body: 'held for thaw' });
230
274
  assert.equal(result['guidance'], "Delivered — held for the target's next natural cycle; not revived.", 'a frozen terminal row still has a thaw cycle');
231
275
  assert.equal(result['revived'], false, 'deferred mail waits for the thaw without launching');
232
- assert.equal(readInboxSince(frozenId, undefined).length, inboxBefore + 1, 'the deferred message is durably appended');
276
+ assert.equal(inboxRows(frozenId).length, inboxBefore + 1, 'the deferred SQL delivery is durable');
233
277
  });
234
278
  // #343 third-fix-cycle review: the IMMEDIATE `--tier deferred` path had its
235
279
  // own, separate TOCTOU — a stale-snapshot check (`rejectDeferredWithoutNaturalCycle`)
236
- // ran well before the real appendInbox call, with several fallible side
237
- // effects (assertFinalizedForReopen/writeOutputSchema/appendSituationalContext/
238
- // commitReopen) in between. Fixed shape: the whole remaining sequence is now
239
- // one `deliverImmediate` closure, guarded end-to-end by `withFreshTerminalGuard`
240
- // when tier === 'deferred' — so the fresh check and every side effect
241
- // (appendInbox included) share ONE canvas write-lock transaction. This drives
242
- // that exact composed shape (the guard wrapping a body that appends an inbox
243
- // entry identical to what `deliverImmediate` builds) against a REAL rival
244
- // writer, proving the immediate path linearizes the same way the shared
245
- // primitive is proven to below.
246
- test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node message send --tier deferred` immediate delivery too — a rival terminalization cannot land between the fresh check and the appendInbox it guards', { timeout: 30_000 }, () => {
280
+ // ran before the SQL delivery write, with several fallible side effects
281
+ // (assertFinalizedForReopen/writeOutputSchema/appendSituationalContext/
282
+ // commitReopen) in between. The guarded closure keeps the fresh check and
283
+ // delivery write in one canvas transaction. This drives that shape against a
284
+ // real rival writer.
285
+ test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node message send --tier deferred` — a rival terminalization cannot land between the fresh check and the SQL delivery it guards', { timeout: 30_000 }, () => {
247
286
  const targetId = 'raceTargetImmediate';
248
287
  createNode(node(targetId, { status: 'active', intent: null }));
249
288
  const rival = new DatabaseSync(canvasDbPath());
@@ -252,13 +291,7 @@ test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node
252
291
  const outcome = withFreshTerminalGuard(targetId, hasNoNaturalCycle, () => {
253
292
  // Same entry shape `deliverImmediate` builds for a body message — the
254
293
  // guarded write this test's race must prove is atomic with the fresh read.
255
- const entry = appendInbox(targetId, {
256
- from: 'human',
257
- tier: 'deferred',
258
- kind: 'message',
259
- label: 'raced note',
260
- data: { body: 'raced note' },
261
- });
294
+ const entry = sendMail(targetId, 'deferred', 'raced note');
262
295
  // Attempted WHILE the guard holds its BEGIN IMMEDIATE write lock — a real
263
296
  // concurrent finish/cancel/finalization racing the immediate delivery.
264
297
  try {
@@ -274,7 +307,7 @@ test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node
274
307
  try {
275
308
  assert.equal(rivalBlocked, true, 'the rival terminalization could not acquire the lock mid-guard — the immediate delivery genuinely serializes against it');
276
309
  assert.equal(outcome.kind, 'ran', 'the guard read the pre-terminal state and completed the delivery, uncontested by the rival');
277
- assert.equal(readInboxSince(targetId, undefined).length, 1, 'exactly the one guarded append landed');
310
+ assert.equal(inboxRows(targetId).length, 1, 'exactly the one guarded SQL delivery landed');
278
311
  }
279
312
  finally {
280
313
  try {
@@ -307,7 +340,7 @@ test('#343 (immediate path): `node message send --tier deferred --reopen` agains
307
340
  else
308
341
  process.env['CRTR_NODE_ID'] = priorNodeId;
309
342
  }
310
- const inboxBefore = readInboxSince(id, undefined).length;
343
+ const inboxBefore = inboxRows(id).length;
311
344
  const originalLaunch = headlessBrokerHost.launch;
312
345
  let launchCalls = 0;
313
346
  headlessBrokerHost.launch = ((...args) => {
@@ -321,7 +354,7 @@ test('#343 (immediate path): `node message send --tier deferred --reopen` agains
321
354
  headlessBrokerHost.launch = originalLaunch;
322
355
  }
323
356
  assert.equal(launchCalls, 0, 'a rejected reopen+deferred retask must never attempt a revive');
324
- assert.equal(readInboxSince(id, undefined).length, inboxBefore, 'a rejected reopen+deferred retask must not append an inbox entry');
357
+ assert.equal(inboxRows(id).length, inboxBefore, 'a rejected reopen+deferred retask must not create a SQL delivery');
325
358
  assert.equal(getNode(id)?.final_report, firstFinal, 'the canonical final must survive untouched — assertFinalizedForReopen/commitReopen never ran on the rejected path');
326
359
  });
327
360
  // The shared `withFreshTerminalGuard` primitive itself (Major, gh #341/#343
@@ -396,11 +429,11 @@ test("withFreshTerminalGuard reports 'missing' — never runs the guarded body
396
429
  let bodyRan = false;
397
430
  const outcome = withFreshTerminalGuard(targetId, hasNoNaturalCycle, () => {
398
431
  bodyRan = true;
399
- return appendInbox(targetId, { from: 'human', tier: 'deferred', kind: 'message', label: 'orphan', data: { body: 'orphan' } });
432
+ return sendMail(targetId, 'deferred', 'orphan');
400
433
  });
401
434
  assert.equal(bodyRan, false, "a missing target must NEVER run the guarded body — 'missing' must not be treated as safe/empty-and-runnable");
402
435
  assert.deepEqual(outcome, { kind: 'missing' }, "the guard reports a distinct 'missing' outcome, not 'terminal' (nothing to report) and not a fabricated 'ran'");
403
- assert.equal(existsSync(nodeDir(targetId)), false, 'the guarded appendInbox never ran, so no orphan node dir was recreated for the deleted target');
436
+ assert.equal(existsSync(nodeDir(targetId)), false, 'the guarded SQL delivery never ran, so no orphan node dir was recreated for the deleted target');
404
437
  });
405
438
  // Daemon second pass — crtrd.ts's idle-release inbox loop
406
439
  // Deterministic: `headlessBrokerHost.launch` is fully STUBBED (never delegates
@@ -409,18 +442,18 @@ test("withFreshTerminalGuard reports 'missing' — never runs the guarded body
409
442
  // `exited` promise never settles and its pid is a fixed sentinel, so the only
410
443
  // thing under test is the daemon's revive-or-hold DECISION (cycles/status/
411
444
  // intent), never a real engine's boot. Genuine post-revive DELIVERY (both
412
- // entries actually reaching the target, no duplicate/loss, cursor settling) is
445
+ // entries actually reaching the target, no duplicate/loss, SQL acknowledgement) is
413
446
  // proven separately below through the real inbox watcher.
414
447
  test('daemon second pass: a deferred-only unseen set never wakes an idle-released node; a later non-deferred entry wakes it (decision only — real delivery is proven separately below)', { timeout: 30_000 }, async () => {
415
448
  const id = 'idleReleaseDeferredDaemon';
416
- createNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
449
+ createFixtureNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
417
450
  // A live child is a standing obligation, which is what keeps this row
418
451
  // idle-released across the tick. Without one it would settle into its park
419
452
  // instead (the resident carve-out, locked in the next test) and the wake half
420
453
  // below would be measuring a parked row.
421
454
  createNode(node(`${id}Child`, { status: 'active' }));
422
455
  subscribe(id, `${id}Child`);
423
- const baseline = readCursor(id); // undefined — the real watcher never ran here
456
+ const baselineAcknowledged = acknowledgedNodeMailCount(id);
424
457
  const originalLaunch = headlessBrokerHost.launch;
425
458
  let launchCalls = 0;
426
459
  headlessBrokerHost.launch = (() => {
@@ -430,13 +463,7 @@ test('daemon second pass: a deferred-only unseen set never wakes an idle-release
430
463
  let deferredEntry;
431
464
  let normalEntry;
432
465
  try {
433
- deferredEntry = appendInbox(id, {
434
- from: 'human',
435
- tier: 'deferred',
436
- kind: 'message',
437
- label: 'deferred note',
438
- data: { body: 'deferred note' },
439
- });
466
+ deferredEntry = sendMail(id, 'deferred', 'deferred note');
440
467
  await superviseTick();
441
468
  {
442
469
  const after = getNode(id);
@@ -445,14 +472,8 @@ test('daemon second pass: a deferred-only unseen set never wakes an idle-release
445
472
  assert.equal(after.intent, 'idle-release', 'target left idle-release');
446
473
  assert.equal(launchCalls, 0, 'no launch attempt for a deferred-only unseen set');
447
474
  }
448
- assert.equal(readCursor(id), baseline, 'the cursor is untouched while the entry is held (nothing consumed it)');
449
- normalEntry = appendInbox(id, {
450
- from: 'human',
451
- tier: 'normal',
452
- kind: 'message',
453
- label: 'go now',
454
- data: { body: 'go now' },
455
- });
475
+ assert.equal(acknowledgedNodeMailCount(id), baselineAcknowledged, 'the held delivery remains unacknowledged');
476
+ normalEntry = sendMail(id, 'normal', 'go now');
456
477
  await superviseTick();
457
478
  {
458
479
  const after = getNode(id);
@@ -465,15 +486,12 @@ test('daemon second pass: a deferred-only unseen set never wakes an idle-release
465
486
  finally {
466
487
  headlessBrokerHost.launch = originalLaunch;
467
488
  }
468
- // No loss: BOTH the previously-held deferred entry and the new one are still
469
- // past the (daemon-never-advances) cursor, ready for the real inbox watcher's
470
- // normal flush to deliver together on this same revived cycle.
471
- const unseen = readInboxSince(id, baseline).map((e) => e.entry_id);
472
- assert.deepEqual(unseen, [deferredEntry.entry_id, normalEntry.entry_id], 'neither entry was dropped or skipped by the daemon');
489
+ // No loss: both independent SQL obligations remain pending for the revived execution.
490
+ assert.deepEqual(inboxRows(id).filter((row) => row.state === 'pending').map((row) => row.messageId), [deferredEntry.messageId, normalEntry.messageId], 'neither delivery was dropped or acknowledged by the daemon');
473
491
  });
474
492
  test('the lifecycle tick parks a resident holding deferred-only mail — mail that never wakes it must not hold it idle forever either', { timeout: 30_000 }, async () => {
475
493
  const id = 'residentDeferredOnly';
476
- createNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
494
+ createFixtureNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
477
495
  const originalLaunch = headlessBrokerHost.launch;
478
496
  let launchCalls = 0;
479
497
  headlessBrokerHost.launch = (() => {
@@ -481,19 +499,19 @@ test('the lifecycle tick parks a resident holding deferred-only mail — mail th
481
499
  return { pid: 0x7ffffffe, exited: new Promise(() => { }) };
482
500
  });
483
501
  try {
484
- const held = appendInbox(id, {
485
- from: null,
486
- tier: 'deferred',
502
+ const held = enqueueNodeMail(id, {
487
503
  kind: 'message',
488
504
  label: 'Child done — auto-done notice',
489
- data: { reason: 'child-auto-done' },
505
+ bodyJson: { reason: 'child-auto-done' },
506
+ urgency: 'deferred',
507
+ wake: 1,
490
508
  });
491
509
  await superviseTick();
492
510
  const after = getNode(id);
493
511
  assert.equal(after.status, 'done', 'the resident settles into its park instead of sitting idle behind mail that cannot wake it');
494
512
  assert.equal(after.intent, 'parked', 'parked, not released');
495
513
  assert.equal(launchCalls, 0, 'settling quietly never launches an engine');
496
- assert.deepEqual(readInboxSince(id, undefined).map((e) => e.entry_id), [held.entry_id], 'the held entry survives the park, ready for the cycle that reopens the conversation');
514
+ assert.deepEqual(inboxRows(id).filter((row) => row.state === 'pending').map((row) => row.messageId), [held.messageId], 'the held delivery survives the park, ready for the cycle that reopens the conversation');
497
515
  }
498
516
  finally {
499
517
  headlessBrokerHost.launch = originalLaunch;
@@ -512,21 +530,32 @@ function makeFakePi() {
512
530
  return pi;
513
531
  }
514
532
  const wait = (ms) => new Promise((r) => setTimeout(r, ms));
515
- test('the real inbox watcher holds a deferred entry alone, then delivers it TOGETHER with a later normal entry with no duplicate/loss, and the durable cursor settles past both (gh #341 review: genuine delivery, not just the daemon decision)', { timeout: 30_000 }, async () => {
533
+ async function waitUntil(check, timeoutMs) {
534
+ const deadline = Date.now() + timeoutMs;
535
+ while (!check()) {
536
+ if (Date.now() >= deadline)
537
+ throw new Error('timed out waiting for watcher deliveries');
538
+ await wait(5);
539
+ }
540
+ }
541
+ test('the real inbox watcher holds a deferred delivery alone, then delivers it TOGETHER with a later normal delivery with no duplicate/loss, and acknowledges both at settlement (gh #341 review: genuine delivery, not just the daemon decision)', { timeout: 30_000 }, async () => {
516
542
  const id = 'watcherDeferredThenNormal';
517
- // The watcher may advance its cursor only for the current broker generation.
518
- // This fake Pi runs in this test process, so its fabricated node must carry
519
- // that process as the active broker just like a real launched node does.
520
- createNode(node(id, { lifecycle: 'resident', status: 'idle', intent: null, pi_pid: process.pid }));
543
+ // The watcher claims mail only for its current execution. This fake Pi runs
544
+ // in this test process, so its fabricated node must carry that execution just
545
+ // like a real launched node does.
546
+ createNode(node(id, { description: 'deferred inbox watcher fixture', lifecycle: 'resident', status: 'idle', intent: null }));
547
+ const executionId = beginExecution(id, { daemonEpoch: 'test' });
521
548
  const TICK_MS = 20;
522
549
  const DEBOUNCE_MS = 25;
523
550
  const SETTLE_MS = TICK_MS * 2 + DEBOUNCE_MS + 30;
524
551
  const origTick = process.env['CRTR_WATCHER_TICK_MS'];
525
552
  const origDebounce = process.env['CRTR_WATCHER_DEBOUNCE_MS'];
526
553
  const origNodeId = process.env['CRTR_NODE_ID'];
554
+ const origExecutionId = process.env['CRTR_EXECUTION_ID'];
527
555
  process.env['CRTR_WATCHER_TICK_MS'] = String(TICK_MS);
528
556
  process.env['CRTR_WATCHER_DEBOUNCE_MS'] = String(DEBOUNCE_MS);
529
557
  process.env['CRTR_NODE_ID'] = id;
558
+ process.env['CRTR_EXECUTION_ID'] = executionId;
530
559
  const pi = makeFakePi();
531
560
  let dispose;
532
561
  try {
@@ -538,24 +567,12 @@ test('the real inbox watcher holds a deferred entry alone, then delivers it TOGE
538
567
  // No agent_start fired — the watcher treats the node as idle, same as the
539
568
  // idle-released daemon scenario above.
540
569
  await wait(TICK_MS + 60);
541
- const deferredEntry = appendInbox(id, {
542
- from: 'human',
543
- tier: 'deferred',
544
- kind: 'message',
545
- label: 'deferred note',
546
- data: { body: 'deferred note' },
547
- });
570
+ const deferredEntry = sendMail(id, 'deferred', 'deferred note');
548
571
  await wait(SETTLE_MS);
549
572
  assert.equal(pi.injected.length, 0, 'a deferred-only idle buffer is held by the real watcher, not delivered alone');
550
- assert.equal(readCursor(id), undefined, 'nothing committed while held');
551
- const normalEntry = appendInbox(id, {
552
- from: 'human',
553
- tier: 'normal',
554
- kind: 'message',
555
- label: 'go now',
556
- data: { body: 'go now' },
557
- });
558
- await wait(SETTLE_MS + TICK_MS * 2);
573
+ assert.equal(acknowledgedNodeMailCount(id), 0, 'nothing is acknowledged while held');
574
+ const normalEntry = sendMail(id, 'normal', 'go now');
575
+ await waitUntil(() => pi.injected.length === 2, SETTLE_MS + TICK_MS * 12);
559
576
  // Two direct human entries are two verbatim deliveries — one message per
560
577
  // entry, physical order preserved, one visible send per watcher cycle.
561
578
  assert.equal(pi.injected.length, 2, 'both entries are delivered once a non-deferred entry joins the batch — each human entry as its own verbatim message');
@@ -563,13 +580,13 @@ test('the real inbox watcher holds a deferred entry alone, then delivers it TOGE
563
580
  assert.doesNotMatch(pi.injected[0].content, /go now/, 'the deliveries are separate messages, not one concatenation');
564
581
  assert.match(pi.injected[1].content, /go now/, 'the new normal entry follows in its own message — not duplicated');
565
582
  assert.equal(pi.injected[0].deliverAs, undefined, 'idle delivery triggers a fresh turn, no deliverAs');
566
- // Idle-path delivery hands off on the void send but only COMMITS the
567
- // durable cursor at agent_settled (never on the fire-and-forget send
568
- // itself) — settle the turn and prove the cursor lands past BOTH delivered
569
- // entries, not just the earlier (previously-held) one.
583
+ // Idle-path delivery hands off on the void send but only acknowledges the
584
+ // SQL deliveries at agent_settled (never on the fire-and-forget send
585
+ // itself) — settle the turn and prove both delivered obligations settle.
570
586
  await pi.fire('agent_settled', { type: 'agent_settled' }, { isIdle: () => true, hasPendingMessages: () => false });
571
- assert.equal(readCursor(id), normalEntry.entry_id, 'the durable cursor settles past BOTH delivered entries, at the final physical entry');
572
- assert.notEqual(readCursor(id), deferredEntry.entry_id, 'not a partial commit stuck at the earlier entry');
587
+ assert.equal(acknowledgedNodeMailCount(id), 2, 'agent_settled acknowledges both delivered SQL obligations');
588
+ assert.ok(inboxRows(id).every((row) => row.acknowledgedAt !== null), 'neither delivery is left in a partial handoff');
589
+ assert.notEqual(normalEntry.messageId, deferredEntry.messageId, 'the batch retains distinct message identities');
573
590
  }
574
591
  finally {
575
592
  dispose?.();
@@ -585,5 +602,9 @@ test('the real inbox watcher holds a deferred entry alone, then delivers it TOGE
585
602
  delete process.env['CRTR_NODE_ID'];
586
603
  else
587
604
  process.env['CRTR_NODE_ID'] = origNodeId;
605
+ if (origExecutionId === undefined)
606
+ delete process.env['CRTR_EXECUTION_ID'];
607
+ else
608
+ process.env['CRTR_EXECUTION_ID'] = origExecutionId;
588
609
  }
589
610
  });
@@ -67,8 +67,8 @@ import { tmpdir } from 'node:os';
67
67
  import { join } from 'node:path';
68
68
  import { createHarness } from '../helpers/harness.js';
69
69
  import { isPidAlive, capturePidIdentities, killProcessTreePids, captureTeardownSnapshot } from '../../canvas/pid.js';
70
- import { recordPid } from '../../canvas/canvas.js';
71
70
  import { openDb } from '../../canvas/db.js';
71
+ import { beginExecution, recordExecutionPid } from '../../canvas/executions.js';
72
72
  import { headlessBrokerHost } from '../../runtime/host.js';
73
73
  let h;
74
74
  let root;
@@ -85,6 +85,10 @@ function trackPid(pid) {
85
85
  if (pid != null)
86
86
  spawnedPids.add(pid);
87
87
  }
88
+ function recordExecution(nodeId, pid) {
89
+ const executionId = beginExecution(nodeId, { daemonEpoch: h.fleet.epoch() });
90
+ recordExecutionPid(executionId, nodeId, pid);
91
+ }
88
92
  before(async () => {
89
93
  h = await createHarness({ headless: true, sessionPrefix: 'crtr-hostpg' });
90
94
  root = h.spawnRoot('host-teardown-process-group suite root');
@@ -362,7 +366,7 @@ test('CONNECTED broker accepts the shutdown frame and exits cleanly, but leaves
362
366
  const id = h.fabricateBrokerNode({ parent: root, pi_pid: null, status: 'active' });
363
367
  const sockPath = h.brokerSock(id);
364
368
  const { leaderPid, descendantPid } = await spawnBrokerWithDetachedDescendantAndSocket(sockPath, ['sleep', '300']);
365
- recordPid(id, leaderPid);
369
+ recordExecution(id, leaderPid);
366
370
  assert.equal(isPidAlive(leaderPid), true, 'fake connected broker leader alive before teardown');
367
371
  assert.equal(isPidAlive(descendantPid), true, 'detached descendant alive before teardown');
368
372
  // teardown() connects, writes the shutdown frame, and the fake broker
@@ -455,14 +459,14 @@ test('end-to-end: headlessBrokerHost.teardown() honors a launch-time identity mi
455
459
  'stand-in "reused pid" tree at all, even after both the SIGTERM and SIGKILL escalation grace windows elapse', async () => {
456
460
  const { leaderPid, descendantPid } = await spawnBrokerWithSharedGroupDescendant('teardown-mismatch');
457
461
  const id = h.fabricateBrokerNode({ parent: root, pi_pid: leaderPid, status: 'active' });
458
- // recordPid captures the REAL current identity for leaderPid (genuinely
462
+ // recordExecution captures the REAL current identity for leaderPid (genuinely
459
463
  // alive right now, so this succeeds) — then this corrupts JUST the stored
460
464
  // identity column to simulate "the recorded launch-time baseline no
461
465
  // longer matches this pid's CURRENT owner", standing in for an actual OS
462
466
  // pid-recycle between this node's launch and this teardown() call.
463
- recordPid(id, leaderPid);
467
+ recordExecution(id, leaderPid);
464
468
  openDb()
465
- .prepare('UPDATE nodes SET pi_pid_identity = ? WHERE node_id = ?')
469
+ .prepare("UPDATE executions SET process_identity = ? WHERE node_id = ? AND state != 'exited'")
466
470
  .run('deliberately-mismatched-launch-identity', id);
467
471
  headlessBrokerHost.teardown(id);
468
472
  // No real broker listens on this fabricated node's view.sock, so