@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
@@ -35,9 +35,11 @@ import { createAttachKit } from '../../../../core/__tests__/helpers/broker-clien
35
35
  import { ApiError, CrtrClient } from '../../../../api/index.js';
36
36
  import { createApiServer } from '../../server.js';
37
37
  import { createNode } from '../../../../core/canvas/canvas.js';
38
- import { apiSocketPath, inboxPath } from '../../../../core/canvas/paths.js';
38
+ import { apiSocketPath } from '../../../../core/canvas/paths.js';
39
+ import { beginExecution, recordExecutionPid } from '../../../../core/canvas/executions.js';
40
+ import { closeTurn, openTurn } from '../../../../core/canvas/recovery.js';
39
41
  import { isPidAlive } from '../../../../core/canvas/pid.js';
40
- import { clearBusy, markBusy } from '../../../../core/runtime/busy.js';
42
+ import { initializeFreshScopedStore } from '../../../../core/scoped-state/migrate.js';
41
43
  let h;
42
44
  let server;
43
45
  let client;
@@ -64,17 +66,6 @@ function seedAssistantSession(sessionFile) {
64
66
  mkdirSync(dirname(sessionFile), { recursive: true });
65
67
  writeFileSync(sessionFile, JSON.stringify({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }) + '\n');
66
68
  }
67
- // Count non-empty lines in a jsonl file (raw, cursor-independent — the live
68
- // broker's inbox-watcher advances a SEPARATE cursor sidecar, so reading the
69
- // append-only file directly is stable regardless of consumption).
70
- function countLines(path) {
71
- try {
72
- return readFileSync(path, 'utf8').split('\n').filter((l) => l.trim() !== '').length;
73
- }
74
- catch {
75
- return 0;
76
- }
77
- }
78
69
  // A1 — /healthz ok + socket mode 0600
79
70
  test('A1: /healthz is ok and the unix socket is bound mode 0600', async () => {
80
71
  const health = await client.healthz();
@@ -83,10 +74,10 @@ test('A1: /healthz is ok and the unix socket is bound mode 0600', async () => {
83
74
  assert.equal(mode, 0o600, `socket mode is 0600 (fs-perms-as-auth), got 0o${mode.toString(8)}`);
84
75
  });
85
76
  test('status counts only live, busy non-human engine rows', async () => {
86
- const agent = h.fabricateBrokerNode({ pi_pid: process.pid });
77
+ const agent = h.fabricateBrokerNode();
87
78
  const deadPid = spawnSync(process.execPath, ['-e', '']).pid;
88
79
  assert.ok(deadPid !== undefined, 'dead-pid fixture process started');
89
- const deadAgent = h.fabricateBrokerNode({ pi_pid: deadPid });
80
+ const deadAgent = h.fabricateBrokerNode();
90
81
  const bridge = `human-status-${process.pid}`;
91
82
  createNode({
92
83
  node_id: bridge,
@@ -101,16 +92,21 @@ test('status counts only live, busy non-human engine rows', async () => {
101
92
  pi_pid: process.pid,
102
93
  });
103
94
  const baseline = (await client.status()).busy_node_count;
104
- markBusy(agent);
105
- markBusy(deadAgent);
106
- markBusy(bridge);
95
+ const executions = [agent, deadAgent, bridge].map((nodeId) => beginExecution(nodeId, { daemonEpoch: null }));
96
+ for (const [executionId, nodeId, pid] of [
97
+ [executions[0], agent, process.pid],
98
+ [executions[1], deadAgent, deadPid],
99
+ [executions[2], bridge, process.pid],
100
+ ]) {
101
+ recordExecutionPid(executionId, nodeId, pid);
102
+ openTurn(executionId, { reason: 'agent-start' });
103
+ }
107
104
  try {
108
105
  assert.equal((await client.status()).busy_node_count, baseline + 1);
109
106
  }
110
107
  finally {
111
- clearBusy(agent);
112
- clearBusy(deadAgent);
113
- clearBusy(bridge);
108
+ for (const executionId of executions)
109
+ closeTurn(executionId, { endReason: 'settled' });
114
110
  }
115
111
  });
116
112
  test('attention counts reads only the requested node slice', async () => {
@@ -172,18 +168,15 @@ process.stdin.on('end', () => {
172
168
  chmodSync(executable, 0o755);
173
169
  return { started, release };
174
170
  }
175
- // A3 — POST .../messages appends inbox.jsonl; POST .../reports {tier:'final'}
171
+ // A3 — POST .../messages creates a durable SQL delivery; POST .../reports {tier:'final'}
176
172
  // writes the report, notifies subscribers, transitions the row
177
- test('A3: messages append inbox.jsonl; final report writes + notifies subscriber + transitions row', async () => {
173
+ test('A3: messages create SQL deliveries; final report writes + notifies subscriber + transitions row', async () => {
178
174
  const A = h.spawnRoot('a3 mgr');
179
175
  const B = (await client.createNode({ kind: 'developer', parent: A, prompt: 'a3 worker' })).node_id;
180
176
  await h.awaitBoot(B);
181
- // messages append inbox.jsonl (raw line count is cursor-independent)
182
- const inboxFile = inboxPath(B);
183
- const before = countLines(inboxFile);
184
177
  const msg = await client.sendMessage(B, { body: 'hello inbox' });
185
178
  assert.equal(msg.delivered, true, 'message reports delivered');
186
- await h.waitFor(() => countLines(inboxFile) > before, { label: 'inbox.jsonl appended', timeoutMs: 10_000 });
179
+ await h.waitFor(() => h.inbox(B).some((entry) => entry.data?.['body'] === 'hello inbox'), { label: 'durable SQL delivery', timeoutMs: 10_000 });
187
180
  // final report: file written + subscriber notified + row transitioned
188
181
  const res = await client.pushReport(B, { tier: 'final', body: 'a3 done result' });
189
182
  assert.ok(existsSync(res.report_path), `report file written at ${res.report_path}`);
@@ -195,12 +188,10 @@ test('A3: messages append inbox.jsonl; final report writes + notifies subscriber
195
188
  test('A3b: a durable send acknowledges while the lifecycle tick is blocked launching its idle target', async () => {
196
189
  const target = h.fabricateBrokerNode({ lifecycle: 'resident', status: 'idle', intent: 'idle-release' });
197
190
  const hook = installBlockedStartHook(target);
198
- const inboxFile = inboxPath(target);
199
- const before = countLines(inboxFile);
200
191
  const sending = client.sendMessage(target, { body: 'wake after durable acknowledgment' });
201
192
  let tick;
202
193
  try {
203
- await h.waitFor(() => countLines(inboxFile) === before + 1, { label: 'durable inbox append' });
194
+ await h.waitFor(() => h.inbox(target).some((entry) => entry.data?.['body'] === 'wake after durable acknowledgment'), { label: 'durable SQL delivery' });
204
195
  assert.equal(h.node(target)?.cycles ?? 0, 0, 'the send has not launched the target');
205
196
  tick = h.tick();
206
197
  await h.waitFor(() => existsSync(hook.started), { label: 'blocked lifecycle launch' });
@@ -210,11 +201,11 @@ test('A3b: a durable send acknowledges while the lifecycle tick is blocked launc
210
201
  ]);
211
202
  assert.equal(acknowledged.delivered, true, 'the API acknowledges the durable append');
212
203
  assert.equal(acknowledged.revived, false, 'the response honestly reports no synchronous revive');
213
- assert.equal(countLines(inboxFile), before + 1, 'exactly one inbox entry is durable while launch is blocked');
204
+ assert.equal(h.inbox(target).filter((entry) => entry.data?.['body'] === 'wake after durable acknowledgment').length, 1, 'exactly one SQL delivery is durable while launch is blocked');
214
205
  writeFileSync(hook.release, 'release');
215
206
  await tick;
216
207
  assert.equal(h.node(target)?.cycles, 1, 'the one queued entry drove lifecycle revival');
217
- assert.equal(countLines(inboxFile), before + 1, 'revival did not append a second inbox entry');
208
+ assert.equal(h.inbox(target).filter((entry) => entry.data?.['body'] === 'wake after durable acknowledgment').length, 1, 'revival did not append a second SQL delivery');
218
209
  }
219
210
  finally {
220
211
  if (!existsSync(hook.release))
@@ -383,6 +374,7 @@ test('A6b: PUT /model-auth/{provider} writes auth.json (reload fan asserted as f
383
374
  const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
384
375
  const piDir = mkdtempSync(join(tmpdir(), 'crtr-apisrv-pi-'));
385
376
  process.env['PI_CODING_AGENT_DIR'] = piDir;
377
+ initializeFreshScopedStore({ kind: 'provider', root: piDir });
386
378
  try {
387
379
  const res = await client.installCredential('anthropic', { kind: 'api_key', api_key: 'sk-test-xyz' });
388
380
  assert.equal(res.installed, true, 'credential reports installed');
@@ -35,6 +35,7 @@ import { tmpdir } from 'node:os';
35
35
  import { join } from 'node:path';
36
36
  import { readAuthCredentials, setAuthCredential } from '../../../../core/auth-file.js';
37
37
  import { readSubscriptionPool, writeSubscriptionPool } from '../../../../core/subscription-state.js';
38
+ import { initializeFreshScopedStore } from '../../../../core/scoped-state/migrate.js';
38
39
  import { createHeadlessHarness } from '../../../../core/__tests__/helpers/harness.js';
39
40
  import { createAttachKit } from '../../../../core/__tests__/helpers/broker-clients.js';
40
41
  import { CrtrClient } from '../../../../api/index.js';
@@ -118,6 +119,7 @@ test('B2-modelauth: installCredential writes auth.json and the reload fan acks a
118
119
  const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
119
120
  const apiDir = mkdtempSync(join(tmpdir(), 'crtr-b10-api-'));
120
121
  process.env['PI_CODING_AGENT_DIR'] = apiDir;
122
+ initializeFreshScopedStore({ kind: 'provider', root: apiDir });
121
123
  let liveId;
122
124
  try {
123
125
  // A live broker to receive the fan.
@@ -190,8 +192,9 @@ test('B2-modelauth-oauth: installCredential(oauth) preserves extra fields (enter
190
192
  // clears exactly its provider's durable store.
191
193
  test('B2-modelauth-status: list is sanitized and delete clears only the selected provider', async () => {
192
194
  const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
193
- const apiDir = mkdtempSync(join(tmpdir(), 'crtr-b10-status-'));
195
+ const apiDir = mkdtempSync(join(h.agentHome, 'crtr-b10-status-'));
194
196
  process.env['PI_CODING_AGENT_DIR'] = apiDir;
197
+ initializeFreshScopedStore({ kind: 'provider', root: apiDir });
195
198
  const expires = 1_900_000_000_000;
196
199
  const cooldown = 1_900_000_100_000;
197
200
  try {
@@ -30,7 +30,7 @@ import { registerPush } from '../../../../commands/push.js';
30
30
  import { registerNode } from '../../../../commands/node.js';
31
31
  import { createNode, getNode } from '../../../../core/canvas/canvas.js';
32
32
  import { branchIsPushedAtCurrentTip, createManagedWorktree } from '../../../../core/worktree.js';
33
- import { readInboxSince } from '../../../../core/feed/inbox.js';
33
+ import { openDb } from '../../../../core/canvas/db.js';
34
34
  import { gitSync } from '../../../../core/git.js';
35
35
  import { InputError } from '../../../../core/io.js';
36
36
  // A terminal fabricated node (no broker needed) that OWNS a managed worktree —
@@ -161,9 +161,10 @@ test('`crtr push update --tier deferred` fans a deferred pointer while preservin
161
161
  await h.awaitBoot(child);
162
162
  const out = await runPushAs(child, 'update', 'deferred progress', { tier: 'deferred' });
163
163
  assert.ok(out['notified'].includes(parent), 'the subscribed parent receives the report pointer');
164
- const entry = readInboxSince(parent).find((candidate) => candidate.from === child && candidate.label === 'deferred progress');
165
- assert.equal(entry?.kind, 'update', 'the stored report remains an update');
166
- assert.equal(entry?.tier, 'deferred', 'the requested delivery tier reaches the parent');
164
+ const entry = openDb().prepare(`SELECT m.kind, d.urgency FROM deliveries d JOIN messages m ON m.message_id = d.message_id
165
+ WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND m.sender_node_id = ? AND m.label = ?`).get(parent, child, 'deferred progress');
166
+ assert.equal(entry?.kind, 'update', 'the stored SQL message remains an update');
167
+ assert.equal(entry?.urgency, 'deferred', 'the requested delivery urgency reaches the parent');
167
168
  });
168
169
  // Re-homed worktree guard/auto-drop (ambiguity-2 follow-up): the pre-B-5 CLI
169
170
  // `push final` worktree behavior now lives SERVER-side in handlers/reports.ts,
@@ -11,10 +11,12 @@ import { createHeadlessHarness } from '../../../../core/__tests__/helpers/harnes
11
11
  import { updateNode } from '../../../../core/canvas/canvas.js';
12
12
  import { armCron, getCron } from '../../../../core/canvas/crons.js';
13
13
  import { closeDb } from '../../../../core/canvas/db.js';
14
+ import { closeWorktree, openWorktree } from '../../../../core/canvas/worktrees.js';
14
15
  import { getDefaultProfileId, setDefaultProfileId, } from '../../../../core/profiles/default-binding.js';
15
16
  import { loadProfileManifest } from '../../../../core/profiles/manifest.js';
16
17
  import { createComment, getComment, listReviewEvents } from '../../../../core/review/comments.js';
17
- import { commitBindingReview, getReview } from '../../../../core/review/store.js';
18
+ import { commitBindingRequest, openRequest } from '../../../../core/human/requests.js';
19
+ import { getReview } from '../../../../core/review/store.js';
18
20
  import { apiSocketPath, nodeDir, nodesRoot, reviewDir, } from '../../../../core/canvas/paths.js';
19
21
  import { isPidAlive } from '../../../../core/canvas/pid.js';
20
22
  import { spawnNode } from '../../../../core/runtime/nodes.js';
@@ -66,47 +68,32 @@ function seedTicket(dir, sourceNodeId) {
66
68
  writeFileSync(join(dir, 'page.json'), JSON.stringify({ source: { nodeId: sourceNodeId } }));
67
69
  writeFileSync(join(dir, 'reply-route.json'), JSON.stringify({ nodeId: sourceNodeId }));
68
70
  }
69
- function reviewRow(reviewId, originNodeId, sourceFile) {
71
+ function reviewRequest(reviewId, originNodeId, sourceFile) {
70
72
  const dir = reviewDir(reviewId);
71
73
  return {
72
- review_id: reviewId,
74
+ request_id: reviewId,
73
75
  idempotency_key: `request-${reviewId}`,
74
- origin_kind: 'inline',
75
- origin_node_id: originNodeId,
76
- bridge_node_id: null,
76
+ requester_node_id: originNodeId,
77
77
  companion_node_id: `companion-${reviewId}`,
78
- review_dir: dir,
78
+ request_dir: dir,
79
79
  file: sourceFile,
80
- origin_session_file: join(h.agentHome, `${reviewId}-origin.jsonl`),
81
- origin_session_id: `session-${reviewId}`,
82
- origin_leaf_id: `leaf-${reviewId}`,
83
- model_spec: 'anthropic/sonnet',
84
- branch_file: join(dir, 'branch-point.jsonl'),
85
- baseline_sha256: 'baseline',
86
- baseline_bytes: 6,
80
+ specification: {
81
+ origin_kind: 'inline',
82
+ origin_session_file: join(h.agentHome, `${reviewId}-origin.jsonl`),
83
+ origin_session_id: `session-${reviewId}`,
84
+ origin_leaf_id: `leaf-${reviewId}`,
85
+ model_spec: 'anthropic/sonnet',
86
+ branch_file: join(dir, 'branch-point.jsonl'),
87
+ baseline_sha256: 'baseline',
88
+ baseline_bytes: 6,
89
+ captured_at: new Date().toISOString(),
90
+ output_path: null,
91
+ },
87
92
  anchor_base_sha256: 'anchor',
88
93
  anchor_base_bytes: Buffer.from('source'),
89
- state: 'binding',
90
- created: new Date().toISOString(),
91
- captured_at: new Date().toISOString(),
92
- opened_at: null,
93
- submit_requested_at: null,
94
- output_path: null,
94
+ created_at: new Date().toISOString(),
95
95
  subtitle: null,
96
- approved_at: null,
97
- approved_sha256: null,
98
- changed: null,
99
- comments_total: null,
100
- comments_unresolved: null,
101
- result_json: null,
102
- canceled_at: null,
103
- cancel_reason: null,
104
- cancel_actor: null,
105
- delivery_entry_id: null,
106
- delivered_at: null,
107
- delivery_error: null,
108
- projected_at: null,
109
- projection_error: null,
96
+ inbox_listed: false,
110
97
  };
111
98
  }
112
99
  function seedReview(reviewId, originNodeId) {
@@ -115,7 +102,8 @@ function seedReview(reviewId, originNodeId) {
115
102
  writeFileSync(sourceFile, 'source\n');
116
103
  mkdirSync(dir, { recursive: true });
117
104
  writeFileSync(join(dir, 'branch-point.jsonl'), '{}\n');
118
- commitBindingReview(reviewRow(reviewId, originNodeId, sourceFile));
105
+ commitBindingRequest(reviewRequest(reviewId, originNodeId, sourceFile));
106
+ assert.equal(openRequest(reviewId, new Date().toISOString()), true, 'fixture review becomes visible before its comment is created');
119
107
  const { comment } = createComment({
120
108
  review_id: reviewId,
121
109
  text: 'keep or delete with its review',
@@ -167,10 +155,8 @@ test('forced profile deletion owns the complete daemon lifecycle', { timeout: 90
167
155
  // An open managed worktree blocks before any profile-owned state mutates.
168
156
  const blockedProfile = await client.ensureProfile('delete-blocked');
169
157
  const blockedNode = h.spawnRoot('profile delete blocker', { id: 'blocked-profile-deadbeef' });
170
- updateNode(blockedNode, {
171
- profile_id: blockedProfile.id,
172
- managed_worktree: managedWorktree(),
173
- });
158
+ updateNode(blockedNode, { profile_id: blockedProfile.id });
159
+ openWorktree(blockedNode, managedWorktree());
174
160
  const blockedPin = join(h.agentHome, 'blocked-pin');
175
161
  mkdirSync(blockedPin, { recursive: true });
176
162
  setDefaultProfileId(blockedPin, blockedProfile.id);
@@ -247,13 +233,13 @@ test('forced profile deletion owns the complete daemon lifecycle', { timeout: 90
247
233
  assert.equal(readFileSync(hardReview.sourceFile, 'utf8'), 'source\n', 'the reviewed source document is never removed');
248
234
  assert.equal(getDefaultProfileId(hardPin), null);
249
235
  await assert.rejects(client.getProfile(hardProfile.id), (error) => error instanceof ApiError && error.status === 404);
250
- assert.throws(() => spawnNode({
236
+ await assert.rejects(() => spawnNode({
251
237
  kind: 'developer',
252
238
  cwd: process.cwd(),
253
239
  parent: null,
254
240
  profile_id: hardProfile.id,
255
241
  nodeId: 'hard-stale-birth-deadbeef',
256
- }), /profile not found/, 'the final synchronous birth gate rejects a profile resolved before a completed hard deletion');
242
+ }), /profile not found/, 'the final birth gate rejects a profile resolved before a completed hard deletion');
257
243
  assert.equal(h.node('hard-stale-birth-deadbeef'), null);
258
244
  // Detach preserves nodes, topology, ticket/review state, and conversations;
259
245
  // matching brokers restart with no profile identity and matching crons become
@@ -368,12 +354,12 @@ test('forced profile deletion owns the complete daemon lifecycle', { timeout: 90
368
354
  });
369
355
  assert.equal(h.node(selfNode)?.profile_id, selfProfile.id, 'the 202 response settles before node mutation');
370
356
  assert.equal(isPidAlive(selfPid), true, 'the 202 response settles before broker teardown');
371
- updateNode(selfNode, { managed_worktree: managedWorktree() });
357
+ openWorktree(selfNode, managedWorktree());
372
358
  const repeatedAccepted = await rawDelete(selfProfile.id, {
373
359
  force: true,
374
360
  caller_node_id: selfNode,
375
361
  });
376
- updateNode(selfNode, { managed_worktree: null });
362
+ closeWorktree(selfNode, { cleanup: 'complete', closedAt: new Date().toISOString() });
377
363
  assert.equal(repeatedAccepted.status, 202, 'a retry returns the accepted result before observing new blockers');
378
364
  assert.deepEqual(repeatedAccepted.body, accepted.body, 'a pending self-deletion is idempotent by profile identity');
379
365
  await h.waitFor(() => h.node(selfNode) === null, {
@@ -7,8 +7,8 @@ import { after, before, beforeEach, test } from 'node:test';
7
7
  import assert from 'node:assert/strict';
8
8
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
9
9
  import { dirname } from 'node:path';
10
- import { createNode, getNode, openDb } from '../../../core/canvas/index.js';
11
- import { closeDb } from '../../../core/canvas/db.js';
10
+ import { createNode, getNode } from '../../../core/canvas/index.js';
11
+ import { closeDb, migrate, openDb } from '../../../core/canvas/db.js';
12
12
  import { InputError } from '../../../core/io.js';
13
13
  import { outputResultPath, outputSchemaPath, writeOutputSchema, } from '../../../core/runtime/structured-output.js';
14
14
  import { reportRoutes } from '../handlers/reports.js';
@@ -23,6 +23,7 @@ before(() => {
23
23
  beforeEach(() => {
24
24
  closeDb();
25
25
  rmSync(home, { recursive: true, force: true });
26
+ migrate(openDb());
26
27
  });
27
28
  after(() => {
28
29
  closeDb();
@@ -25,7 +25,7 @@ import { isSafeNodeId, viewSocketPath } from '../../core/canvas/paths.js';
25
25
  import { getNode } from '../../core/canvas/index.js';
26
26
  import { BROKER_READ_CAPS, CLIENT_READ_CAPS, FrameDecoder, FrameOverflowError, } from '../../core/runtime/broker-protocol.js';
27
27
  import { classify } from '../../core/fault-classifier.js';
28
- import { clearFault, recordFault } from '../../core/runtime/fault.js';
28
+ import { clearFault, recordFault } from '../../core/canvas/recovery.js';
29
29
  import { decideAttachRevive } from '../../core/runtime/revive.js';
30
30
  import { probeViewSocket, waitForBrokerViewSocket } from '../../core/runtime/view-socket.js';
31
31
  /** Our own noServer WS server: A-9 routes matched attach upgrades to
@@ -1,10 +1,9 @@
1
1
  // Daemon-owned read and stop surfaces for one node's file-backed background
2
2
  // bash jobs. The attach roster continues to read the same directory directly;
3
3
  // these routes exist for remote presenters that cannot see the node host.
4
- import { appendInbox } from '../../../core/feed/inbox.js';
4
+ import { enqueueNodeMail } from '../../../core/feed/messages.js';
5
5
  import { contextDir, isSafeNodeId } from '../../../core/canvas/paths.js';
6
6
  import { getNode } from '../../../core/canvas/canvas.js';
7
- import { cancelCronsOnWake } from '../../../core/canvas/crons.js';
8
7
  import { isBrokerLive } from '../../../core/runtime/model-swap.js';
9
8
  import { assertNotFinalized } from '../../../core/runtime/reopen.js';
10
9
  import { reviveNode } from '../../../core/runtime/revive.js';
@@ -68,14 +67,14 @@ async function handleStop(ctx) {
68
67
  assertNotFinalized(nodeId);
69
68
  const paths = bashJobPaths(dir, jobId);
70
69
  const body = `Background bash job ${jobId} was stopped by the user before it finished. Log: ${paths.jobLog} Command: ${paths.cmdSh}`;
71
- appendInbox(nodeId, {
72
- from: null,
73
- tier: 'urgent',
70
+ enqueueNodeMail(nodeId, {
71
+ senderNodeId: null,
72
+ urgency: 'urgent',
73
+ wake: 1,
74
74
  kind: 'message',
75
75
  label: body.split('\n')[0].slice(0, 120),
76
- data: { body },
76
+ bodyJson: { body },
77
77
  });
78
- cancelCronsOnWake(nodeId);
79
78
  const node = getNode(nodeId);
80
79
  if (node !== null && !isBrokerLive(node)) {
81
80
  try {
@@ -1,26 +1,28 @@
1
1
  // Broker-originated canvas operations. The broker sends Pi lifecycle facts;
2
2
  // crtrd validates and commits their durable effects while the handler stays
3
3
  // strictly canvas-write-only — no broker launch, kill, or view.sock round trip.
4
- import { canonicalSessionFile, findNodeBySessionFile, getNode, isWarmSpare, listNodeIdentities, recordPid, recordSettledTurn, setPresence, subscriptionsOf, updateNode } from '../../../core/canvas/canvas.js';
4
+ import { canonicalSessionFile, findNodeBySessionFile, getNode, isWarmSpare, listNodeIdentities, recordSettledTurn, setPresence, subscriptionsOf, updateNode } from '../../../core/canvas/canvas.js';
5
+ import { recordExecutionPid } from '../../../core/canvas/executions.js';
6
+ import { bindSession, readLiveSession, transferSession } from '../../../core/canvas/sessions.js';
5
7
  import { isSafeNodeId } from '../../../core/canvas/paths.js';
6
8
  import { isPidAlive } from '../../../core/canvas/pid.js';
7
9
  import { editorLabel } from '../../../core/canvas/labels.js';
8
10
  import { assembleNodeSubject } from '../../../core/substrate/subject.js';
9
11
  import { withCanvasWrite } from '../../../core/canvas/db.js';
10
12
  import { notFound, usage } from '../../../core/errors.js';
11
- import { hasUnseenWakeMail, readCursor, readInboxSince, writeCursor } from '../../../core/feed/inbox.js';
13
+ import { hasUnseenWakeMail } from '../../../core/canvas/deliveries.js';
12
14
  import { clearYieldMessage } from '../../../core/runtime/kickoff.js';
13
15
  import { transition } from '../../../core/runtime/lifecycle.js';
14
16
  import { focusOf, tearDownNode } from '../../../core/runtime/placement.js';
15
17
  import { hasAttachedBrokerViewers } from '../../../core/runtime/broker/client-registry.js';
16
- import { handleNewSession } from '../../../core/runtime/reset.js';
17
18
  import { evaluateStop } from '../../../core/runtime/stop-guard.js';
18
- import { clearFault, recordFault } from '../../../core/runtime/fault.js';
19
+ import { clearFault, recordFault } from '../../../core/canvas/recovery.js';
19
20
  import { updateModelRecipe } from '../../../core/runtime/model-swap.js';
20
21
  import { completeCompanionRequestedSubmit } from '../../review/finish.js';
21
22
  import { emitEvent } from '../../../core/events/emit.js';
22
23
  import { consumePendingPark, recordPendingParkActivity } from '../../park-pending.js';
23
24
  import { shouldParkAfterSummary } from '../../park-activity.js';
25
+ import { requireCurrentExecution } from './broker-recovery.js';
24
26
  function objectBody(body, operation) {
25
27
  if (typeof body !== 'object' || body === null || Array.isArray(body)) {
26
28
  throw usage(`${operation} requires a JSON body`);
@@ -29,6 +31,9 @@ function objectBody(body, operation) {
29
31
  }
30
32
  function parseSessionBoundBody(body) {
31
33
  const value = objectBody(body, 'broker session-bound');
34
+ if (typeof value['executionId'] !== 'string' || value['executionId'] === '') {
35
+ throw usage('executionId must be a non-empty string');
36
+ }
32
37
  if (typeof value['piSessionId'] !== 'string' || value['piSessionId'] === '') {
33
38
  throw usage('piSessionId must be a non-empty string');
34
39
  }
@@ -64,18 +69,6 @@ function parseParkCompleteBody(body) {
64
69
  }
65
70
  return value;
66
71
  }
67
- function parseInboxCursorBody(body) {
68
- const value = objectBody(body, 'broker inbox-cursor');
69
- const throughEntryId = value['throughEntryId'];
70
- if (typeof throughEntryId !== 'string' || !/^(?!0{32}$)[0-9a-f]{32}$/.test(throughEntryId)) {
71
- throw usage('throughEntryId must be a non-zero 32-character lowercase hexadecimal inbox entry id');
72
- }
73
- const brokerPid = value['brokerPid'];
74
- if (!Number.isSafeInteger(brokerPid) || brokerPid <= 0) {
75
- throw usage('brokerPid must be a positive safe integer');
76
- }
77
- return { throughEntryId, brokerPid: brokerPid };
78
- }
79
72
  function parseModelCommitBody(body) {
80
73
  const value = objectBody(body, 'broker model');
81
74
  if (typeof value['spec'] !== 'string' || value['spec'].trim() === '')
@@ -167,14 +160,14 @@ function parseGeneratedNameBody(body) {
167
160
  throw usage('description, title, and icon must be strings');
168
161
  }
169
162
  if (value['kind'] === 'initial') {
170
- return { kind: 'initial', description: value['description'], title: value['title'], icon: value['icon'] };
163
+ return value;
171
164
  }
172
165
  const expected = value['expected'];
173
166
  if (typeof expected !== 'object' || expected === null || Array.isArray(expected)
174
167
  || ['name', 'description', 'title', 'icon', 'kind'].some((key) => typeof expected[key] !== 'string')) {
175
168
  throw usage('recap expected must contain name, description, title, icon, and kind strings');
176
169
  }
177
- return { kind: 'recap', description: value['description'], title: value['title'], icon: value['icon'], expected: expected };
170
+ return value;
178
171
  }
179
172
  function parsePersonaAckBody(body) {
180
173
  const value = objectBody(body, 'broker persona-ack');
@@ -188,7 +181,7 @@ function parsePersonaAckBody(body) {
188
181
  }
189
182
  return persona;
190
183
  };
191
- return { from: parse('from'), to: parse('to') };
184
+ return { expected_execution_id: value['expected_execution_id'], from: parse('from'), to: parse('to') };
192
185
  }
193
186
  function handleSessionBound(ctx) {
194
187
  const nodeId = ctx.params['id'];
@@ -196,9 +189,18 @@ function handleSessionBound(ctx) {
196
189
  const sessionFile = request.sessionFile === null ? null : canonicalSessionFile(request.sessionFile);
197
190
  const result = withCanvasWrite(() => {
198
191
  const before = requireNode(nodeId);
199
- // A transcript is one broker's identity. Check ownership before every
200
- // session-bound mutation: a live owner wins, and the claimant must stop
201
- // rather than overwrite its own process-scoped identity onto that file.
192
+ if (recordExecutionPid(request.executionId, nodeId, request.pid) === 'shutdown') {
193
+ return { action: 'shutdown' };
194
+ }
195
+ const reviewId = before.review_binding?.review_id;
196
+ if (reviewId !== undefined && !request.reviewBoundaryIds.includes(reviewId)) {
197
+ return { action: 'shutdown' };
198
+ }
199
+ if (request.reason === 'new' && before.parent == null)
200
+ return { action: 'relaunch_root' };
201
+ if (request.reason !== 'new' && before.fork_from != null && before.pi_session_id == null && sessionFile === null) {
202
+ return { action: 'shutdown' };
203
+ }
202
204
  const owner = sessionFile === null ? null : findNodeBySessionFile(sessionFile);
203
205
  if (owner !== null && owner.node_id !== nodeId) {
204
206
  if (isPidAlive(owner.pi_pid)) {
@@ -211,46 +213,29 @@ function handleSessionBound(ctx) {
211
213
  });
212
214
  return { action: 'shutdown' };
213
215
  }
214
- // The prior owner is dead, so transfer the durable coordinate before the
215
- // claimant writes it; the UNIQUE projection and both meta files agree.
216
- updateNode(owner.node_id, { pi_session_id: null, pi_session_file: null });
216
+ const source = readLiveSession(owner.node_id);
217
+ if (source === null || transferSession({
218
+ fromNodeId: owner.node_id,
219
+ fromBindingId: source.bindingId,
220
+ toNodeId: nodeId,
221
+ executionId: request.executionId,
222
+ sessionId: request.piSessionId,
223
+ transcriptPath: sessionFile,
224
+ }) !== 'transferred')
225
+ return { action: 'shutdown' };
217
226
  }
218
- // The broker reports the markers present in Pi's current branch; crtrd owns
219
- // the review binding and decides whether that branch belongs to this node.
220
- const reviewId = before.review_binding?.review_id;
221
- if (reviewId !== undefined && !request.reviewBoundaryIds.includes(reviewId)) {
227
+ else if (bindSession(nodeId, {
228
+ executionId: request.executionId,
229
+ sessionId: request.piSessionId,
230
+ transcriptPath: sessionFile,
231
+ }) === 'shutdown')
222
232
  return { action: 'shutdown' };
223
- }
224
- let action = 'none';
225
- if (request.reason === 'new') {
226
- if (before.parent == null) {
227
- // Root relaunch launches and tears down brokers. Return a directive for
228
- // the calling extension rather than re-entering that broker here.
229
- action = 'relaunch_root';
230
- }
231
- else {
232
- handleNewSession(nodeId, request.piSessionId, sessionFile);
233
- }
234
- }
235
- else if (before.fork_from != null && before.pi_session_id == null && sessionFile === null) {
236
- // Pi has not exposed complete writable coordinates for this ordinary fork.
237
- // Leave it unbound so recovery cannot fork provenance twice.
238
- action = 'shutdown';
239
- }
240
- else {
241
- // Confirmed boot: consume the one-shot yield note, bind the new Pi session
242
- // coordinates, stamp broker liveness, and commit any pending refresh.
233
+ if (request.reason !== 'new') {
243
234
  clearYieldMessage(nodeId);
244
- updateNode(nodeId, {
245
- pi_session_id: request.piSessionId,
246
- pi_session_file: sessionFile,
247
- cycle_pending: false,
248
- });
249
- recordPid(nodeId, request.pid);
250
235
  if (before.intent === 'refresh')
251
236
  transition(nodeId, 'revive');
252
237
  }
253
- return { action };
238
+ return { action: 'none' };
254
239
  });
255
240
  return { status: 200, body: result };
256
241
  }
@@ -324,6 +309,7 @@ function settleBroker(nodeId, request) {
324
309
  function handleSettle(ctx) {
325
310
  const nodeId = ctx.params['id'];
326
311
  const request = parseSettleBody(ctx.body);
312
+ requireCurrentExecution(nodeId, request.expected_execution_id);
327
313
  const directive = withCanvasWrite(() => settleBroker(nodeId, request));
328
314
  // A review companion that just went quiet releases any submit the human left
329
315
  // waiting on it. A reprompt is not going quiet, so it waits for the turn that
@@ -340,6 +326,7 @@ function handleSettle(ctx) {
340
326
  function handleParkComplete(ctx) {
341
327
  const nodeId = ctx.params['id'];
342
328
  const request = parseParkCompleteBody(ctx.body);
329
+ requireCurrentExecution(nodeId, request.expected_execution_id);
343
330
  const directive = withCanvasWrite(() => {
344
331
  const pending = consumePendingPark(nodeId);
345
332
  if (pending === undefined)
@@ -367,41 +354,16 @@ function handleParkComplete(ctx) {
367
354
  * never terminalize a viewer or live-delivery input that won admission. */
368
355
  function handleParkActivity(ctx) {
369
356
  const nodeId = ctx.params['id'];
357
+ requireCurrentExecution(nodeId, objectBody(ctx.body, 'broker park-activity')['expected_execution_id']);
370
358
  const result = withCanvasWrite(() => ({
371
359
  activity: recordPendingParkActivity(nodeId) ? 'recorded' : 'none',
372
360
  }));
373
361
  return { status: 200, body: result };
374
362
  }
375
- /** Commit a watcher cursor only while this broker generation remains current.
376
- * A refresh or a replacement binding must leave the cursor untouched so the
377
- * replacement replays every handoff that may have reached the discarded Pi
378
- * conversation. */
379
- function handleInboxCursor(ctx) {
380
- const nodeId = ctx.params['id'];
381
- const request = parseInboxCursorBody(ctx.body);
382
- const directive = withCanvasWrite(() => {
383
- const node = requireNode(nodeId);
384
- if (node.intent === 'refresh' || node.pi_pid !== request.brokerPid)
385
- return { action: 'hold_refresh' };
386
- const entries = readInboxSince(nodeId);
387
- const throughIndex = entries.findIndex((entry) => entry.entry_id === request.throughEntryId);
388
- if (throughIndex < 0)
389
- throw usage('throughEntryId must identify an entry in this node inbox');
390
- const cursor = readCursor(nodeId);
391
- const cursorIndex = cursor === undefined ? -1 : entries.findIndex((entry) => entry.entry_id === cursor);
392
- if (cursor !== undefined && cursorIndex < 0)
393
- throw new Error(`inbox cursor entry_id not found: ${cursor}`);
394
- if (throughIndex < cursorIndex)
395
- throw usage('throughEntryId must not precede the durable inbox cursor');
396
- if (throughIndex > cursorIndex)
397
- writeCursor(nodeId, request.throughEntryId);
398
- return { action: 'advanced' };
399
- });
400
- return { status: 200, body: directive };
401
- }
402
363
  function handleModelCommit(ctx) {
403
364
  const nodeId = ctx.params['id'];
404
365
  const request = parseModelCommitBody(ctx.body);
366
+ requireCurrentExecution(nodeId, request.expected_execution_id);
405
367
  const updated = withCanvasWrite(() => updateModelRecipe(requireNode(nodeId), request.spec, request.pinnedOverride, request.userSelected));
406
368
  return { status: 200, body: { modelOverride: updated.model_override } };
407
369
  }
@@ -414,6 +376,7 @@ function handleExtensionState(ctx) {
414
376
  function handleGeneratedName(ctx) {
415
377
  const nodeId = ctx.params['id'];
416
378
  const request = parseGeneratedNameBody(ctx.body);
379
+ requireCurrentExecution(nodeId, request.expected_execution_id);
417
380
  const result = withCanvasWrite(() => {
418
381
  const node = requireNode(nodeId);
419
382
  if (request.kind === 'initial') {
@@ -457,6 +420,7 @@ function handleGeneratedName(ctx) {
457
420
  function handlePersonaAck(ctx) {
458
421
  const nodeId = ctx.params['id'];
459
422
  const request = parsePersonaAckBody(ctx.body);
423
+ requireCurrentExecution(nodeId, request.expected_execution_id);
460
424
  const result = withCanvasWrite(() => {
461
425
  const node = requireNode(nodeId);
462
426
  const current = { kind: node.kind, mode: node.mode, lifecycle: node.lifecycle };
@@ -476,7 +440,6 @@ export const brokerOperationRoutes = [
476
440
  { method: 'POST', pattern: '/v1/nodes/:id/broker/settle', handler: handleSettle },
477
441
  { method: 'POST', pattern: '/v1/nodes/:id/broker/park-complete', handler: handleParkComplete },
478
442
  { method: 'POST', pattern: '/v1/nodes/:id/broker/park-activity', handler: handleParkActivity },
479
- { method: 'POST', pattern: '/v1/nodes/:id/broker/inbox-cursor', handler: handleInboxCursor },
480
443
  { method: 'POST', pattern: '/v1/nodes/:id/broker/model', handler: handleModelCommit },
481
444
  { method: 'GET', pattern: '/v1/nodes/:id/broker/extension-state', handler: handleExtensionState },
482
445
  { method: 'POST', pattern: '/v1/nodes/:id/broker/generated-name', handler: handleGeneratedName },