@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,10 +1,12 @@
1
1
  import { after, afterEach, before, beforeEach, test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
- import { persistModelRoute, readConfig, readMergedLaunchConfig } from '../config.js';
6
+ import { persistModelRoute, readConfig, readMergedLaunchConfig, readRawConfigAtRoot, writeRawConfig } from '../config.js';
7
+ import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
7
8
  import { expandModelCandidates } from '../model-routes.js';
9
+ import { piAgentDir } from '../runtime/package-health.js';
8
10
  import { resetScopeCache } from '../scope.js';
9
11
  let home;
10
12
  let work;
@@ -23,6 +25,10 @@ function route(providerId = 'provider-x', family = 'family-x', credentialSource
23
25
  };
24
26
  }
25
27
  function writeConfig(root, config) {
28
+ if (root === join(home, '.crouter')) {
29
+ writeRawConfig('user', config);
30
+ return;
31
+ }
26
32
  mkdirSync(root, { recursive: true });
27
33
  writeFileSync(join(root, 'config.json'), JSON.stringify(config));
28
34
  }
@@ -34,6 +40,8 @@ beforeEach(() => {
34
40
  work = mkdtempSync(join(tmpdir(), 'crtr-model-routes-work-'));
35
41
  process.env.HOME = home;
36
42
  resetScopeCache();
43
+ initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
44
+ initializeFreshScopedStore({ kind: 'provider', root: piAgentDir() });
37
45
  });
38
46
  afterEach(() => {
39
47
  resetScopeCache();
@@ -87,7 +95,7 @@ test('route records retain __proto__ ids when read and persisted', () => {
87
95
  writeConfig(root, JSON.parse(JSON.stringify({ modelRoutes: { ['__proto__']: route() } })));
88
96
  assert.deepEqual(readMergedLaunchConfig(work, null).modelRoutes?.['__proto__'], route());
89
97
  persistModelRoute(root, '__proto__', route('persisted-provider'));
90
- const raw = JSON.parse(readFileSync(join(root, 'config.json'), 'utf8'));
98
+ const raw = readRawConfigAtRoot(root);
91
99
  assert.deepEqual(raw.modelRoutes['__proto__'], route('persisted-provider'));
92
100
  });
93
101
  test('route candidate identity distinguishes delimiter-containing route and provider ids', () => {
@@ -131,12 +139,12 @@ test('persistModelRoute touches only its raw route and deletes by restoring inhe
131
139
  const other = route('other-provider');
132
140
  writeConfig(root, { modelRoutes: { other }, max_panes_per_window: 7 });
133
141
  persistModelRoute(root, 'edited', route('edited-provider'));
134
- let raw = JSON.parse(readFileSync(join(root, 'config.json'), 'utf8'));
142
+ let raw = readRawConfigAtRoot(root);
135
143
  assert.deepEqual(raw.modelRoutes.other, other);
136
144
  assert.deepEqual(raw.modelRoutes.edited, route('edited-provider'));
137
145
  assert.equal(raw.modelLadders, undefined);
138
146
  assert.equal(raw.max_panes_per_window, 7);
139
147
  persistModelRoute(root, 'edited', undefined);
140
- raw = JSON.parse(readFileSync(join(root, 'config.json'), 'utf8'));
148
+ raw = readRawConfigAtRoot(root);
141
149
  assert.deepEqual(raw.modelRoutes, { other });
142
150
  });
@@ -4,15 +4,80 @@ import { DatabaseSync } from 'node:sqlite';
4
4
  import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { join } from 'node:path';
7
- import { closeDb, MIGRATIONS } from '../canvas/db.js';
8
- import { createNode, getRow } from '../canvas/canvas.js';
9
- import { armOutcomeDelivery, claimOutcomeDelivery, readOutcomeDelivery, recoverRunningOutcomeDeliveries, settleOutcomeDelivery, } from '../canvas/node-outcome-deliveries.js';
7
+ import { closeDb, MIGRATIONS, migrate, openDb, withCanvasWrite } from '../canvas/db.js';
8
+ import { createNode, getNode, getRow } from '../canvas/canvas.js';
9
+ import { activateArmedExternalActionDelivery, armExternalActionDelivery, claimExternalActionDelivery, messageBodyForDelivery, readExternalActionDelivery, resetHandedOffDeliveries, settleDelivery, } from '../canvas/deliveries.js';
10
10
  import { transition } from '../runtime/lifecycle.js';
11
11
  import { composeNodeOutcomeDocument } from '../runtime/outcome-document.js';
12
12
  import { pushFinal } from '../feed/feed.js';
13
13
  import { OUTCOME_PAYLOAD_MAX_BYTES } from '../canvas/types.js';
14
14
  let home;
15
15
  const priorHome = process.env['CRTR_HOME'];
16
+ function outcomeKey(nodeId) {
17
+ return `node-outcome:${nodeId}`;
18
+ }
19
+ function outcomeDelivery(nodeId) {
20
+ return readExternalActionDelivery(outcomeKey(nodeId));
21
+ }
22
+ function nodeAtOutcome(node) {
23
+ const reason = node.outcome_reason;
24
+ let status;
25
+ let intent;
26
+ if (node.outcome_kind === 'result' || reason === 'finished' || reason === 'declined') {
27
+ status = 'done';
28
+ intent = 'done';
29
+ }
30
+ else if (reason === 'parked') {
31
+ status = 'done';
32
+ intent = 'parked';
33
+ }
34
+ else if (reason === 'closed' || reason === 'retired' || reason === 'stranded' || reason === 'deadline_exceeded') {
35
+ status = 'canceled';
36
+ intent = null;
37
+ }
38
+ else {
39
+ status = 'dead';
40
+ intent = null;
41
+ }
42
+ return { ...node, status, intent, terminal_reason: reason };
43
+ }
44
+ function armOutcomeAction(input) {
45
+ const payload = input.payload ?? null;
46
+ const json = JSON.stringify(payload);
47
+ if (json === undefined)
48
+ throw new TypeError('outcome delivery payload must be JSON-serializable');
49
+ if (Buffer.byteLength(json, 'utf8') > OUTCOME_PAYLOAD_MAX_BYTES)
50
+ throw new RangeError(`outcome delivery payload exceeds ${OUTCOME_PAYLOAD_MAX_BYTES} bytes`);
51
+ return withCanvasWrite((db) => {
52
+ const existing = outcomeDelivery(input.nodeId);
53
+ if (existing !== null && existing.state !== 'armed')
54
+ return null;
55
+ armExternalActionDelivery(db, {
56
+ destinationId: input.actionName,
57
+ adapter: { adapter: 'subprocess', argv: input.argv, cwd: input.cwd, config_origin: input.configOrigin, payload },
58
+ idempotencyKey: outcomeKey(input.nodeId),
59
+ }, input.now);
60
+ const node = getNode(input.nodeId);
61
+ if (node === null)
62
+ throw new Error(`unknown node ${input.nodeId}`);
63
+ if (node.outcome_at !== null) {
64
+ if (node.outcome_kind === null || node.outcome_reason === null)
65
+ throw new Error(`outcome latch is incomplete for node ${input.nodeId}`);
66
+ activateArmedExternalActionDelivery(db, {
67
+ idempotencyKey: outcomeKey(input.nodeId),
68
+ label: input.nodeId,
69
+ bodyJson: JSON.stringify(composeNodeOutcomeDocument({
70
+ node: nodeAtOutcome(node),
71
+ outcome: { kind: node.outcome_kind, reason: node.outcome_reason, at: node.outcome_at, revision: node.outcome_revision, detailJson: node.outcome_detail_json ?? null },
72
+ action: { name: input.actionName, payload },
73
+ finalReport: node.final_report,
74
+ })),
75
+ supersede: false,
76
+ }, input.now);
77
+ }
78
+ return outcomeDelivery(input.nodeId);
79
+ });
80
+ }
16
81
  function node(nodeId) {
17
82
  return {
18
83
  node_id: nodeId,
@@ -29,6 +94,7 @@ beforeEach(() => {
29
94
  closeDb();
30
95
  home = mkdtempSync(join(tmpdir(), 'crtr-node-outcome-'));
31
96
  process.env['CRTR_HOME'] = home;
97
+ migrate(openDb());
32
98
  });
33
99
  afterEach(() => {
34
100
  closeDb();
@@ -105,23 +171,24 @@ test('a canonical final supersedes a prior failure outcome and increments revisi
105
171
  });
106
172
  test('outcome delivery rejects an oversized payload before a node is armed', () => {
107
173
  createNode(node('oversized-payload'));
108
- assert.throws(() => armOutcomeDelivery({
174
+ assert.throws(() => armOutcomeAction({
109
175
  nodeId: 'oversized-payload', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { value: 'x'.repeat(OUTCOME_PAYLOAD_MAX_BYTES) },
110
176
  }), /exceeds/);
111
- assert.equal(readOutcomeDelivery('oversized-payload'), null);
177
+ assert.equal(outcomeDelivery('oversized-payload'), null);
112
178
  });
113
179
  test('outcome delivery armed after settlement is born pending with its document', () => {
114
180
  createNode(node('settled-delivery'));
115
181
  transition('settled-delivery', 'crash');
116
- const delivery = armOutcomeDelivery({
182
+ const delivery = armOutcomeAction({
117
183
  nodeId: 'settled-delivery', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { callback: 'one' }, now: 123,
118
184
  });
119
185
  assert.equal(delivery?.state, 'pending');
120
186
  assert.equal(delivery?.nextAttemptAt, 123);
121
- assert.deepEqual(delivery?.document && {
122
- schema: delivery.document.schema,
123
- outcomeId: delivery.document.outcomeId,
124
- action: delivery.document.action,
187
+ const document = delivery === null ? null : messageBodyForDelivery(delivery.deliveryId);
188
+ assert.deepEqual(document && {
189
+ schema: document.schema,
190
+ outcomeId: document.outcomeId,
191
+ action: document.action,
125
192
  }, {
126
193
  schema: 'crtr.node-outcome/v1',
127
194
  outcomeId: 'settled-delivery:1',
@@ -130,28 +197,28 @@ test('outcome delivery armed after settlement is born pending with its document'
130
197
  });
131
198
  test('outcome delivery state is fenced, recoverable, and follows the durable backoff', () => {
132
199
  createNode(node('delivery'));
133
- assert.ok(armOutcomeDelivery({
200
+ assert.ok(armOutcomeAction({
134
201
  nodeId: 'delivery', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { callback: 'one' }, now: 100,
135
202
  }));
136
203
  transition('delivery', 'crash');
137
- assert.equal(readOutcomeDelivery('delivery')?.state, 'pending');
138
- assert.ok(readOutcomeDelivery('delivery')?.document, 'the armed row is stamped with its outcome document');
139
- const first = claimOutcomeDelivery('delivery', Date.now(), 'daemon-a');
140
- assert.equal(first.state, 'running');
141
- assert.equal(settleOutcomeDelivery('delivery', first.attempt, 'daemon-b', { state: 'accepted' }, Date.now()), false, 'a stale claimant cannot settle');
142
- assert.equal(settleOutcomeDelivery('delivery', first.attempt, 'daemon-a', { state: 'accepted' }, Date.now()), true);
143
- assert.equal(readOutcomeDelivery('delivery')?.state, 'accepted');
204
+ assert.equal(outcomeDelivery('delivery')?.state, 'pending');
205
+ assert.ok(messageBodyForDelivery(outcomeDelivery('delivery').deliveryId), 'the armed row is stamped with its outcome document');
206
+ const first = claimExternalActionDelivery(outcomeDelivery('delivery').deliveryId, Date.now(), 'daemon-a');
207
+ assert.equal(first.state, 'handed_off');
208
+ assert.equal(settleDelivery(first.deliveryId, first.attempts, 'daemon-b', { state: 'accepted' }, Date.now()), false, 'a stale claimant cannot settle');
209
+ assert.equal(settleDelivery(first.deliveryId, first.attempts, 'daemon-a', { state: 'accepted' }, Date.now()), true);
210
+ assert.equal(outcomeDelivery('delivery')?.state, 'acknowledged');
144
211
  createNode(node('recover'));
145
- armOutcomeDelivery({ nodeId: 'recover', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', now: 100 });
212
+ armOutcomeAction({ nodeId: 'recover', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', now: 100 });
146
213
  transition('recover', 'crash');
147
- const running = claimOutcomeDelivery('recover', Date.now(), 'daemon-a');
148
- assert.equal(running.state, 'running');
214
+ const running = claimExternalActionDelivery(outcomeDelivery('recover').deliveryId, Date.now(), 'daemon-a');
215
+ assert.equal(running.state, 'handed_off');
149
216
  const recoveredAt = Date.now();
150
- assert.equal(recoverRunningOutcomeDeliveries(recoveredAt), 1);
151
- assert.deepEqual(readOutcomeDelivery('recover') && {
152
- state: readOutcomeDelivery('recover').state,
153
- nextAttemptAt: readOutcomeDelivery('recover').nextAttemptAt,
154
- claimOwner: readOutcomeDelivery('recover').claimOwner,
217
+ assert.equal(resetHandedOffDeliveries(recoveredAt), 1);
218
+ assert.deepEqual(outcomeDelivery('recover') && {
219
+ state: outcomeDelivery('recover').state,
220
+ nextAttemptAt: outcomeDelivery('recover').nextAttemptAt,
221
+ claimOwner: outcomeDelivery('recover').claimOwner,
155
222
  }, { state: 'pending', nextAttemptAt: recoveredAt, claimOwner: null });
156
223
  });
157
224
  test('v39 migrates a v38 database and backfills finalized and terminal rows', () => {
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
10
10
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { join } from 'node:path';
13
- import { closeDb } from '../canvas/db.js';
13
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
14
14
  import { resetScopeCache } from '../scope.js';
15
15
  import { spawnNode } from '../runtime/nodes.js';
16
16
  import { renderOnReadDocs } from '../substrate/on-read-node.js';
@@ -32,6 +32,7 @@ beforeEach(() => {
32
32
  // touch the fake user home; the read path we test is constructed by hand.
33
33
  canvasHome = mkdtempSync(join(tmpdir(), 'crtr-fence-canvas-'));
34
34
  process.env['CRTR_HOME'] = canvasHome;
35
+ migrate(openDb());
35
36
  });
36
37
  after(() => {
37
38
  closeDb();
@@ -44,8 +45,8 @@ after(() => {
44
45
  else
45
46
  process.env['HOME'] = prevHomeEnv;
46
47
  });
47
- test('a store nested in the crouter home does not leak into a canvas-artifact read', () => {
48
- const node = spawnNode({ kind: 'general', cwd: fakeHome, parent: null }).node_id;
48
+ test('a store nested in the crouter home does not leak into a canvas-artifact read', async () => {
49
+ const node = (await spawnNode({ kind: 'general', cwd: fakeHome, parent: null })).node_id;
49
50
  // A private store misresolved into the crouter home: `~/.crouter/.crouter/memory/`.
50
51
  const leakedStore = join(fakeHome, '.crouter', '.crouter', 'memory');
51
52
  mkdirSync(leakedStore, { recursive: true });
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
10
10
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
11
11
  import { tmpdir } from 'node:os';
12
12
  import { join } from 'node:path';
13
- import { closeDb } from '../canvas/db.js';
13
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
14
14
  import { resetScopeCache } from '../scope.js';
15
15
  import { spawnNode } from '../runtime/nodes.js';
16
16
  import { renderOnReadDocs } from '../substrate/on-read-node.js';
@@ -44,6 +44,7 @@ beforeEach(() => {
44
44
  else
45
45
  process.env['HOME'] = prevHomeEnv;
46
46
  work = mkdtempSync(join(tmpdir(), 'crtr-onread-work-'));
47
+ migrate(openDb());
47
48
  });
48
49
  after(() => {
49
50
  closeDb();
@@ -56,11 +57,11 @@ after(() => {
56
57
  else
57
58
  process.env['HOME'] = prevHomeEnv;
58
59
  });
59
- test('project and user memory docs without read surfaces do not fire on file reads', () => {
60
+ test('project and user memory docs without read surfaces do not fire on file reads', async () => {
60
61
  const fakeHome = mkdtempSync(join(tmpdir(), 'crtr-onread-user-home-'));
61
62
  process.env['HOME'] = fakeHome;
62
63
  resetScopeCache();
63
- const node = spawnNode({ kind: 'general', cwd: join(fakeHome, 'work'), parent: null }).node_id;
64
+ const node = (await spawnNode({ kind: 'general', cwd: join(fakeHome, 'work'), parent: null })).node_id;
64
65
  const userMemDir = join(fakeHome, '.crouter', 'memory');
65
66
  mkdirSync(userMemDir, { recursive: true });
66
67
  writeFileSync(join(userMemDir, 'noisy-global.md'), '---\nkind: preference\n' +
@@ -79,8 +80,8 @@ test('project and user memory docs without read surfaces do not fire on file rea
79
80
  assert.equal(rendered, '', 'no file context renders without an explicit trigger');
80
81
  rmSync(fakeHome, { recursive: true, force: true });
81
82
  });
82
- test('on-read exposure survives a strict resume and resets for a fresh context', () => {
83
- const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
83
+ test('on-read exposure survives a strict resume and resets for a fresh context', async () => {
84
+ const node = (await spawnNode({ kind: 'general', cwd: work, parent: null })).node_id;
84
85
  // A file-routed substrate doc in a `.crouter/memory/` ancestor. The
85
86
  // `./`-anchored glob pins the repo-relative anchor: `src/file.ts` matches
86
87
  // only relative to the doc's owning repo dir, never absolutely.
@@ -111,8 +112,8 @@ test('on-read exposure survives a strict resume and resets for a fresh context',
111
112
  const stateFresh = loadContextExposureState(node);
112
113
  assert.ok(renderOnReadDocs(node, readFile, exposureTarget(stateFresh, 'transcript')).includes(FIXTURE_BODY), 'a fresh transcript surfaces the doc again');
113
114
  });
114
- test('an explicitly routed doc injects on-read under its frontmatter name, not the path-derived one', () => {
115
- const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
115
+ test('an explicitly routed doc injects on-read under its frontmatter name, not the path-derived one', async () => {
116
+ const node = (await spawnNode({ kind: 'general', cwd: work, parent: null })).node_id;
116
117
  const memDir = join(work, '.crouter', 'memory');
117
118
  declareRepositoryRoot(memDir);
118
119
  writeFileSync(join(memDir, 'physical-guide.md'), '---\nkind: knowledge\n' +
@@ -11,7 +11,7 @@ import assert from 'node:assert/strict';
11
11
  import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
12
12
  import { tmpdir } from 'node:os';
13
13
  import { join } from 'node:path';
14
- import { closeDb } from '../canvas/db.js';
14
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
15
15
  import { resetScopeCache } from '../scope.js';
16
16
  import { spawnNode } from '../runtime/nodes.js';
17
17
  import { renderOnReadDocs, renderWorkspaceOpenDocs } from '../substrate/on-read-node.js';
@@ -36,6 +36,7 @@ beforeEach(() => {
36
36
  process.env['HOME'] = fakeHome;
37
37
  canvasHome = mkdtempSync(join(tmpdir(), 'crtr-nested-canvas-'));
38
38
  process.env['CRTR_HOME'] = canvasHome;
39
+ migrate(openDb());
39
40
  });
40
41
  after(() => {
41
42
  closeDb();
@@ -76,9 +77,9 @@ function makeWorkTree() {
76
77
  'NESTED NEIGHBOUR BODY\n');
77
78
  return work;
78
79
  }
79
- test('a nested store front door surfaces on a read beneath its owning dir, not outside it', () => {
80
+ test('a nested store front door surfaces on a read beneath its owning dir, not outside it', async () => {
80
81
  const work = makeWorkTree();
81
- const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
82
+ const node = (await spawnNode({ kind: 'general', cwd: work, parent: null })).node_id;
82
83
  const inside = join(work, 'pkg', 'lib', 'file.ts');
83
84
  mkdirSync(join(work, 'pkg', 'lib'), { recursive: true });
84
85
  writeFileSync(inside, 'export const x = 1;\n');
@@ -93,9 +94,9 @@ test('a nested store front door surfaces on a read beneath its owning dir, not o
93
94
  const renderedOutside = renderOnReadDocs(node, outside, freshTranscriptTarget());
94
95
  assert.ok(!renderedOutside.includes(NESTED_BODY), `read outside pkg must not surface the nested front door; got: ${renderedOutside}`);
95
96
  });
96
- test('workspace-open and on-read share one exposure ledger', () => {
97
+ test('workspace-open and on-read share one exposure ledger', async () => {
97
98
  const work = makeWorkTree();
98
- const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
99
+ const node = (await spawnNode({ kind: 'general', cwd: work, parent: null })).node_id;
99
100
  const transcript = freshTranscriptTarget();
100
101
  const opened = renderWorkspaceOpenDocs(node, transcript);
101
102
  assert.ok(opened.includes(ROOT_BODY), 'workspace open delivers the mounted root front door');
@@ -109,7 +110,7 @@ test('workspace-open and on-read share one exposure ledger', () => {
109
110
  assert.ok(onRead.includes('name="work/pkg"'), 'the nested front door answers under the root namespace plus owner path');
110
111
  assert.ok(!onRead.includes(ROOT_BODY), 'the root front door already delivered at workspace open — never twice');
111
112
  });
112
- test('a hand-authored `./`-anchored read glob in a user-scope doc never matches a read', () => {
113
+ test('a hand-authored `./`-anchored read glob in a user-scope doc never matches a read', async () => {
113
114
  const userStore = join(fakeHome, '.crouter', 'memory');
114
115
  mkdirSync(userStore, { recursive: true });
115
116
  writeFileSync(join(userStore, 'control.md'), '---\nkind: knowledge\n' +
@@ -124,7 +125,7 @@ test('a hand-authored `./`-anchored read glob in a user-scope doc never matches
124
125
  'USER DOT BODY MUST NOT FIRE\n');
125
126
  const work = join(fakeHome, 'work');
126
127
  mkdirSync(work, { recursive: true });
127
- const node = spawnNode({ kind: 'general', cwd: work, parent: null }).node_id;
128
+ const node = (await spawnNode({ kind: 'general', cwd: work, parent: null })).node_id;
128
129
  const readFile = join(fakeHome, 'notes', 'thing.md');
129
130
  mkdirSync(join(fakeHome, 'notes'), { recursive: true });
130
131
  writeFileSync(readFile, '# a note\n');
@@ -1,49 +1,17 @@
1
- // End-to-end tests for passive subscriptions:
2
- // 1. A passive subscriber's pushes land in passive.jsonl, NOT inbox.jsonl
3
- // (so the inbox-watcher never wakes it); an active subscriber's land in
4
- // inbox.jsonl as before.
5
- // 2. the broker drain reads + clears the accumulator (surfaces exactly once).
6
- // 3. canvas-passive-context formats drained entries as timestamped XML and
7
- // transforms an `input` event into pre-text + the original message.
8
- //
9
- // Run: node --import tsx/esm --test src/core/__tests__/passive-subscription.test.ts
10
- import { test, before, beforeEach, afterEach, after } from 'node:test';
1
+ import { after, before, beforeEach, test } from 'node:test';
11
2
  import assert from 'node:assert/strict';
12
- import { mkdtempSync, mkdirSync, rmSync, existsSync, readFileSync, appendFileSync } from 'node:fs';
3
+ import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
13
4
  import { tmpdir } from 'node:os';
14
5
  import { basename, join } from 'node:path';
15
6
  import { createNode, subscribe } from '../canvas/canvas.js';
16
7
  import { armCron, getCron } from '../canvas/crons.js';
17
- import { closeDb } from '../canvas/db.js';
18
- import { inboxPath, passivePath, apiSocketPath } from '../canvas/paths.js';
19
- import { createApiServer } from '../../daemon/api/server.js';
8
+ import { closeDb, migrate, openDb } from '../canvas/db.js';
9
+ import { acknowledgeNodeMail, claimNodeMail, hasUnseenWakeMail } from '../canvas/deliveries.js';
10
+ import { beginExecution } from '../canvas/executions.js';
20
11
  import { push } from '../feed/feed.js';
21
- import { appendPassive } from '../feed/passive.js';
22
- import { readInboxSince } from '../feed/inbox.js';
23
- import { drainBrokerPassive } from '../runtime/broker/passive.js';
24
- import { readLeaf } from '../../commands/canvas-history/read.js';
25
- import registerCanvasPassiveContext from '../../pi-extensions/canvas-passive-context.js';
12
+ import { nodeMailEntry } from '../feed/inbox.js';
13
+ import { formatPassive } from '../../pi-extensions/canvas-passive-context.js';
26
14
  let home;
27
- function readPassiveEntries(nodeId) {
28
- const path = passivePath(nodeId);
29
- if (!existsSync(path))
30
- return [];
31
- return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
32
- }
33
- // The one test that resolves an emitted feed ref drives `canvas history read`'s
34
- // leaf (`readLeaf`), a thin CLI forwarder to crtrd since the API re-plumb. Stand
35
- // up a real in-process `createApiServer()` on the isolated CRTR_HOME's socket so
36
- // that read resolves through the genuine daemon seam — no autostarted
37
- // subprocess, no mock client.
38
- let server;
39
- async function waitForSocket(path) {
40
- for (let i = 0; i < 200; i++) {
41
- if (existsSync(path))
42
- return;
43
- await new Promise((r) => setTimeout(r, 25));
44
- }
45
- throw new Error(`api socket never bound at ${path}`);
46
- }
47
15
  function node(id, over = {}) {
48
16
  return {
49
17
  node_id: id,
@@ -57,53 +25,44 @@ function node(id, over = {}) {
57
25
  ...over,
58
26
  };
59
27
  }
28
+ function execution(nodeId) {
29
+ return beginExecution(nodeId, { daemonEpoch: 'test' });
30
+ }
60
31
  before(() => {
61
32
  home = mkdtempSync(join(tmpdir(), 'crtr-passive-'));
62
33
  process.env['CRTR_HOME'] = home;
63
34
  });
64
- beforeEach(async () => {
35
+ beforeEach(() => {
65
36
  closeDb();
66
37
  rmSync(home, { recursive: true, force: true });
67
38
  mkdirSync(home, { recursive: true });
68
- server = createApiServer();
69
- await waitForSocket(apiSocketPath());
70
- });
71
- afterEach(async () => {
72
- await server?.close();
39
+ migrate(openDb());
73
40
  });
74
41
  after(() => {
75
42
  closeDb();
76
43
  rmSync(home, { recursive: true, force: true });
77
44
  delete process.env['CRTR_HOME'];
78
- delete process.env['CRTR_NODE_ID'];
79
45
  });
80
- test('passive push accumulates in passive.jsonl, not inbox.jsonl', async () => {
46
+ test('a passive subscriber receives a non-waking SQL delivery with the canonical report ref', async () => {
81
47
  createNode(node('pub'));
82
48
  createNode(node('observer'));
83
- subscribe('observer', 'pub', false); // PASSIVE
49
+ subscribe('observer', 'pub', false);
84
50
  const result = await push('pub', { kind: 'update', body: 'first observation\nmore detail' });
85
- // The emitted feed ref is the same canonical handle accepted by `canvas
86
- // history read`, while PushResult.reportPath remains an absolute path.
87
- const acc = readPassiveEntries('observer');
88
- const ref = acc[0].ref;
89
- assert.equal(ref, `pub:reports/${basename(result.reportPath)}`);
90
- const resolved = await readLeaf.run({ ref });
91
- assert.equal(resolved?.['ref'], ref, 'canvas history read accepts the emitted ref verbatim');
92
- assert.equal(resolved?.['content'], 'first observation\nmore detail');
93
- assert.equal(result.reportPath.startsWith('/'), true);
94
- // No inbox entry → the inbox-watcher would never see it → no wake.
95
- assert.equal(existsSync(inboxPath('observer')), false);
96
- assert.equal(readInboxSince('observer').length, 0);
97
- // It landed in the passive accumulator instead.
98
- assert.equal(acc.length, 1);
99
- assert.equal(acc[0].from, 'pub');
100
- assert.equal(acc[0].label, 'first observation');
101
- assert.match(acc[0].ref ?? '', /^pub:reports\/\d{8}T\d{9}-[0-9a-f]{8}-update\.md$/, 'report refs use a compact random suffix');
51
+ const executionId = execution('observer');
52
+ const claim = claimNodeMail('observer', executionId, 'passive');
53
+ assert.equal(hasUnseenWakeMail('observer'), false, 'passive mail does not wake a node');
54
+ assert.equal(claim.action, 'claimed');
55
+ assert.equal(claim.deliveries.length, 1);
56
+ const entry = nodeMailEntry(claim.deliveries[0]);
57
+ assert.equal(entry.from, 'pub');
58
+ assert.equal(entry.label, 'first observation');
59
+ assert.equal(entry.ref, `pub:reports/${basename(result.reportPath)}`);
60
+ assert.match(entry.ref ?? '', /^pub:reports\/\d{8}T\d{9}-[0-9a-f]{8}-update\.md$/);
102
61
  });
103
- test('active push still lands in inbox.jsonl (wakes) and clears the manager deadline', async () => {
62
+ test('an active subscriber receives waking SQL mail and clears its deadline', async () => {
104
63
  createNode(node('pub'));
105
64
  createNode(node('worker-mgr'));
106
- subscribe('worker-mgr', 'pub', true); // ACTIVE
65
+ subscribe('worker-mgr', 'pub', true);
107
66
  armCron({
108
67
  cron_id: 'manager-deadline',
109
68
  name: 'manager deadline',
@@ -126,102 +85,36 @@ test('active push still lands in inbox.jsonl (wakes) and clears the manager dead
126
85
  tier: 'normal',
127
86
  });
128
87
  await push('pub', { kind: 'update', body: 'active report' });
129
- assert.equal(existsSync(passivePath('worker-mgr')), false);
130
- const inbox = readInboxSince('worker-mgr');
131
- assert.equal(inbox.length, 1);
132
- assert.equal(inbox[0].from, 'pub');
133
- assert.match(inbox[0].ref ?? '', /^pub:reports\/.+-update\.md$/);
134
- assert.equal(getCron('manager-deadline'), null, 'the child report won the deadline race');
135
- });
136
- test('a deferred update still lands in an active subscriber inbox at deferred tier', async () => {
137
- createNode(node('pub'));
138
- // Resident: deferred mail to a TERMINAL target is bumped to urgent by the
139
- // shipped tier bump, so only a resident subscriber shows the requested tier.
140
- createNode(node('worker-mgr', { lifecycle: 'resident' }));
141
- subscribe('worker-mgr', 'pub', true); // ACTIVE
142
- await push('pub', { kind: 'update', deliveryTier: 'deferred', body: 'active report' });
143
- assert.equal(existsSync(passivePath('worker-mgr')), false);
144
- const inbox = readInboxSince('worker-mgr');
145
- assert.equal(inbox.length, 1);
146
- assert.equal(inbox[0].from, 'pub');
147
- assert.equal(inbox[0].tier, 'deferred', 'requested delivery urgency reaches the subscriber unchanged');
148
- assert.equal(inbox[0].kind, 'update', 'the stored report kind is untouched by delivery urgency');
149
- assert.match(inbox[0].ref ?? '', /^pub:reports\/.+-update\.md$/);
88
+ const claim = claimNodeMail('worker-mgr', execution('worker-mgr'), 'wake');
89
+ assert.equal(hasUnseenWakeMail('worker-mgr'), true);
90
+ assert.equal(getCron('manager-deadline'), null, 'the report won the deadline race');
91
+ assert.equal(claim.deliveries.length, 1);
92
+ assert.equal(nodeMailEntry(claim.deliveries[0]).from, 'pub');
150
93
  });
151
- test('mixed active + passive subscribers route to their own stores', async () => {
94
+ test('a passive delivery is acknowledged only after it has been claimed', async () => {
152
95
  createNode(node('pub'));
153
- createNode(node('active-sub'));
154
- createNode(node('passive-sub'));
155
- subscribe('active-sub', 'pub', true);
156
- subscribe('passive-sub', 'pub', false);
157
- const res = await push('pub', { kind: 'urgent', body: 'something happened' });
158
- assert.deepEqual(new Set(res.deliveredTo), new Set(['active-sub', 'passive-sub']));
159
- assert.equal(readInboxSince('active-sub').length, 1);
160
- assert.equal(existsSync(passivePath('active-sub')), false);
161
- assert.equal(readPassiveEntries('passive-sub').length, 1);
162
- assert.equal(existsSync(inboxPath('passive-sub')), false);
163
- });
164
- test('broker passive drain reads then clears (surfaces exactly once)', () => {
165
- createNode(node('observer'));
166
- appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
167
- appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
168
- const drained = drainBrokerPassive('observer');
169
- assert.equal(drained.length, 2);
170
- assert.deepEqual(drained.map((e) => e.label), ['one', 'two']); // oldest first
171
- // Cleared — a second drain is empty.
172
- assert.equal(drainBrokerPassive('observer').length, 0);
173
- assert.equal(readPassiveEntries('observer').length, 0);
174
- });
175
- test('broker passive drain tolerates a corrupt line — keeps every good entry, loses only the bad one', () => {
176
96
  createNode(node('observer'));
177
- appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
178
- appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
179
- // A torn/garbage line lands between good entries (a partial write, a crash
180
- // mid-append). The whole feed must NOT be discarded over it.
181
- appendFileSync(passivePath('observer'), '{not valid json\n', 'utf8');
182
- appendPassive('observer', { from: 'c', tier: 'normal', kind: 'update', label: 'three' });
183
- const drained = drainBrokerPassive('observer');
184
- assert.equal(drained.length, 3, 'all three good entries survive the corrupt line');
185
- assert.deepEqual(drained.map((e) => e.label), ['one', 'two', 'three']);
186
- // And it still cleared: a second drain is empty (snapshot removed).
187
- assert.equal(drainBrokerPassive('observer').length, 0);
188
- assert.equal(existsSync(passivePath('observer')), false);
97
+ subscribe('observer', 'pub', false);
98
+ await push('pub', { kind: 'update', body: 'one' });
99
+ await push('pub', { kind: 'update', body: 'two' });
100
+ const executionId = execution('observer');
101
+ const claim = claimNodeMail('observer', executionId, 'passive');
102
+ assert.deepEqual(claim.deliveries.map((delivery) => nodeMailEntry(delivery).label), ['one', 'two']);
103
+ const receipt = acknowledgeNodeMail('observer', executionId, claim.deliveries.map((delivery) => ({
104
+ deliveryId: delivery.deliveryId,
105
+ attempts: delivery.attempts,
106
+ disposition: 'acknowledged',
107
+ })));
108
+ assert.deepEqual(receipt.results.map((result) => result.outcome), ['acknowledged', 'acknowledged']);
109
+ assert.deepEqual(claimNodeMail('observer', executionId, 'passive').deliveries, []);
189
110
  });
190
- function makeFakePi() {
191
- return { on(e, h) { if (e === 'input')
192
- this.handler = h; } };
193
- }
194
- test('input handler injects drained backlog as pre-text, then clears it', async () => {
111
+ test('passive SQL mail retains the existing timestamped backlog rendering', async () => {
195
112
  createNode(node('pub'));
196
113
  createNode(node('observer'));
197
114
  subscribe('observer', 'pub', false);
198
115
  await push('pub', { kind: 'update', body: 'the body of the report\nsecond line' });
199
- process.env['CRTR_NODE_ID'] = 'observer';
200
- const pi = makeFakePi();
201
- registerCanvasPassiveContext(pi);
202
- assert.ok(pi.handler, 'input handler registered');
203
- // First message → backlog drains in as pre-text before the user's text.
204
- const out = await pi.handler({ type: 'input', text: 'hey what happened', source: 'interactive' });
205
- assert.equal(out.action, 'transform');
206
- assert.match(out.text, /<passive-subscription-backlog/);
207
- assert.match(out.text, /the body of the report/); // dereferenced report body
208
- assert.match(out.text, /hey what happened$/); // original message preserved at the end
209
- // Second message → nothing accumulated → left untouched.
210
- const out2 = await pi.handler({ type: 'input', text: 'still there?', source: 'interactive' });
211
- assert.ok(out2 === undefined || out2.action === 'continue');
212
- // Oversized canonical reports must leave an actionable reader command in the
213
- // truncation tail, using the exact ref emitted into the passive feed.
214
- await push('pub', { kind: 'update', body: 'oversized report line '.repeat(250) });
215
- const oversizedRef = readPassiveEntries('observer')[0].ref;
216
- const out3 = await pi.handler({ type: 'input', text: 'show me the rest', source: 'interactive' });
217
- assert.equal(out3.action, 'transform');
218
- assert.ok(out3.text.includes(`crtr canvas history read ${oversizedRef}`), 'truncation tail names the canonical reader and emitted ref');
219
- });
220
- test('input handler is inert when nothing is accumulated', async () => {
221
- createNode(node('observer'));
222
- process.env['CRTR_NODE_ID'] = 'observer';
223
- const pi = makeFakePi();
224
- registerCanvasPassiveContext(pi);
225
- const out = await pi.handler({ type: 'input', text: 'plain message', source: 'interactive' });
226
- assert.ok(out === undefined || out.action === 'continue');
116
+ const claim = claimNodeMail('observer', execution('observer'), 'passive');
117
+ const text = formatPassive(claim.deliveries.map(nodeMailEntry));
118
+ assert.match(text, /<passive-subscription-backlog/);
119
+ assert.match(text, /the body of the report/);
227
120
  });