@north-light/crouter 0.3.227 → 0.3.229

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 (312) hide show
  1. package/dist/api/client.d.ts +5 -5
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/dto/broker-ops.d.ts +8 -0
  4. package/dist/api/dto/memory.d.ts +8 -7
  5. package/dist/api/dto/memory.js +2 -2
  6. package/dist/builtin-memory/04-base-worker.md +1 -1
  7. package/dist/builtin-memory/insights/init.md +5 -5
  8. package/dist/builtin-memory/internal/INDEX.md +1 -1
  9. package/dist/builtin-memory/internal/agent-shaping.md +11 -11
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/memory-loading.md +6 -6
  12. package/dist/builtin-memory/internal/plugins.md +1 -1
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
  14. package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
  15. package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
  16. package/dist/clients/attach/chrome/inbox-strip.js +11 -7
  17. package/dist/clients/attach/session/pane-tag.d.ts +28 -2
  18. package/dist/clients/attach/session/pane-tag.js +48 -22
  19. package/dist/clients/attach/session/profile-files.d.ts +6 -0
  20. package/dist/clients/attach/session/profile-files.js +44 -11
  21. package/dist/clients/attach/slash/dispatch.js +4 -7
  22. package/dist/clients/attach/viewer.js +588 -596
  23. package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
  24. package/dist/commands/api-client.d.ts +3 -7
  25. package/dist/commands/api-client.js +7 -12
  26. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
  27. package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
  28. package/dist/commands/memory/delete.js +51 -24
  29. package/dist/commands/memory/edit.js +50 -10
  30. package/dist/commands/memory/find.js +105 -56
  31. package/dist/commands/memory/history.js +51 -31
  32. package/dist/commands/memory/lint.d.ts +0 -9
  33. package/dist/commands/memory/lint.js +78 -322
  34. package/dist/commands/memory/list.d.ts +12 -4
  35. package/dist/commands/memory/list.js +64 -25
  36. package/dist/commands/memory/move.js +103 -73
  37. package/dist/commands/memory/origin.js +35 -5
  38. package/dist/commands/memory/read.d.ts +4 -0
  39. package/dist/commands/memory/read.js +132 -80
  40. package/dist/commands/memory/shared.d.ts +115 -25
  41. package/dist/commands/memory/shared.js +331 -74
  42. package/dist/commands/memory/write.js +72 -21
  43. package/dist/commands/memory.js +2 -2
  44. package/dist/commands/node/create.d.ts +1 -1
  45. package/dist/commands/node/create.js +1 -1
  46. package/dist/commands/node/inspect.js +1 -1
  47. package/dist/commands/node/lifecycle.js +5 -5
  48. package/dist/commands/pkg/market-manage.js +25 -24
  49. package/dist/commands/pkg/plugin-manage.d.ts +22 -6
  50. package/dist/commands/pkg/plugin-manage.js +118 -25
  51. package/dist/commands/pkg/shared.d.ts +8 -0
  52. package/dist/commands/pkg/shared.js +23 -20
  53. package/dist/commands/surface-reload.d.ts +5 -0
  54. package/dist/commands/surface-reload.js +9 -1
  55. package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
  56. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
  57. package/dist/commands/sys/migrate.js +126 -140
  58. package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
  59. package/dist/commands/sys/panels/profiles-panel.js +599 -0
  60. package/dist/commands/sys/settings-shell.d.ts +4 -2
  61. package/dist/commands/sys/settings-shell.js +35 -4
  62. package/dist/commands/sys/settings.js +21 -5
  63. package/dist/commands/sys/sync-deps.d.ts +2 -0
  64. package/dist/commands/sys/sync-deps.js +26 -15
  65. package/dist/commands/sys/sync-project-guidance.js +228 -145
  66. package/dist/commands/sys/sync-skills.js +28 -17
  67. package/dist/commands/sys/update.js +11 -3
  68. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
  69. package/dist/core/__tests__/config-change-delta.test.js +106 -0
  70. package/dist/core/__tests__/context-intro.test.js +13 -9
  71. package/dist/core/__tests__/daemon-boot.test.js +22 -16
  72. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
  73. package/dist/core/__tests__/human-deliver.test.js +19 -0
  74. package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
  75. package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
  76. package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
  77. package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
  78. package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
  79. package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
  80. package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
  81. package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
  82. package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
  83. package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
  84. package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
  85. package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
  86. package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
  87. package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
  88. package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
  89. package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
  90. package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
  91. package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
  92. package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
  93. package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
  94. package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
  95. package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
  96. package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
  97. package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
  98. package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
  99. package/dist/core/__tests__/kickoff.test.js +16 -5
  100. package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
  101. package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
  102. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  103. package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
  104. package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
  105. package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
  106. package/dist/core/__tests__/repository-association.test.d.ts +1 -0
  107. package/dist/core/__tests__/repository-association.test.js +153 -0
  108. package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
  109. package/dist/core/__tests__/repository-root-identity.test.js +219 -0
  110. package/dist/core/__tests__/review-model-floor.test.js +12 -4
  111. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
  112. package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
  113. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
  114. package/dist/core/canvas/canvas.d.ts +3 -0
  115. package/dist/core/canvas/canvas.js +30 -10
  116. package/dist/core/canvas/db.js +28 -1
  117. package/dist/core/canvas/paths.d.ts +4 -6
  118. package/dist/core/canvas/paths.js +8 -6
  119. package/dist/core/canvas/render-source.js +2 -2
  120. package/dist/core/canvas/types.d.ts +9 -13
  121. package/dist/core/config.d.ts +3 -3
  122. package/dist/core/config.js +3 -3
  123. package/dist/core/exclusive-lock.d.ts +2 -0
  124. package/dist/core/exclusive-lock.js +21 -0
  125. package/dist/core/git.d.ts +0 -1
  126. package/dist/core/git.js +0 -3
  127. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
  128. package/dist/core/human/feedback-companion.js +3 -0
  129. package/dist/core/human/scan.js +8 -1
  130. package/dist/core/keybindings/catalog.d.ts +2 -2
  131. package/dist/core/keybindings/catalog.js +2 -1
  132. package/dist/core/memory/doc-link-grammar.js +2 -2
  133. package/dist/core/memory/history.d.ts +24 -0
  134. package/dist/core/memory/history.js +66 -1
  135. package/dist/core/memory/identity.d.ts +65 -0
  136. package/dist/core/memory/identity.js +185 -0
  137. package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
  138. package/dist/core/memory/inline-ref-guidance.js +1 -1
  139. package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
  140. package/dist/core/memory/inline-ref-inventory.js +30 -62
  141. package/dist/core/memory/lint.d.ts +129 -0
  142. package/dist/core/memory/lint.js +517 -0
  143. package/dist/core/memory/project-namespace.d.ts +31 -0
  144. package/dist/core/memory/project-namespace.js +85 -0
  145. package/dist/core/memory/repository-association.d.ts +31 -0
  146. package/dist/core/memory/repository-association.js +129 -0
  147. package/dist/core/memory/tree.d.ts +39 -0
  148. package/dist/core/memory/tree.js +93 -0
  149. package/dist/core/memory-resolver.d.ts +193 -90
  150. package/dist/core/memory-resolver.js +461 -375
  151. package/dist/core/nested-stores.js +6 -13
  152. package/dist/core/profiles/select.d.ts +4 -1
  153. package/dist/core/profiles/select.js +108 -48
  154. package/dist/core/review/__tests__/capture-origin.test.js +2 -2
  155. package/dist/core/review/__tests__/stage-identity.test.js +2 -2
  156. package/dist/core/review/companion.js +11 -2
  157. package/dist/core/runtime/bearings.d.ts +2 -2
  158. package/dist/core/runtime/bearings.js +3 -3
  159. package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
  160. package/dist/core/runtime/broker/rebind.js +5 -0
  161. package/dist/core/runtime/broker-extension-render.d.ts +7 -3
  162. package/dist/core/runtime/broker-extension-render.js +10 -5
  163. package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
  164. package/dist/core/runtime/broker-persona-guidance.js +122 -27
  165. package/dist/core/runtime/deliver-live.d.ts +16 -4
  166. package/dist/core/runtime/deliver-live.js +29 -15
  167. package/dist/core/runtime/kickoff.d.ts +3 -3
  168. package/dist/core/runtime/kickoff.js +8 -16
  169. package/dist/core/runtime/launch.js +1 -1
  170. package/dist/core/runtime/lifecycle.js +2 -3
  171. package/dist/core/runtime/nodes.d.ts +3 -4
  172. package/dist/core/runtime/nodes.js +3 -4
  173. package/dist/core/runtime/persona.d.ts +8 -12
  174. package/dist/core/runtime/persona.js +24 -96
  175. package/dist/core/runtime/promote.d.ts +2 -2
  176. package/dist/core/runtime/promote.js +8 -21
  177. package/dist/core/runtime/revive.js +20 -17
  178. package/dist/core/runtime/spawn.js +18 -7
  179. package/dist/core/runtime/tmux-bindings.js +2 -3
  180. package/dist/core/runtime/warm-pool.js +3 -4
  181. package/dist/core/scope.js +2 -0
  182. package/dist/core/self-update.d.ts +0 -2
  183. package/dist/core/self-update.js +2 -35
  184. package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
  185. package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
  186. package/dist/core/substrate/index.d.ts +2 -2
  187. package/dist/core/substrate/index.js +1 -1
  188. package/dist/core/substrate/injected-store.d.ts +43 -27
  189. package/dist/core/substrate/injected-store.js +208 -104
  190. package/dist/core/substrate/listings.d.ts +19 -12
  191. package/dist/core/substrate/listings.js +75 -52
  192. package/dist/core/substrate/on-read-node.d.ts +4 -7
  193. package/dist/core/substrate/on-read-node.js +6 -8
  194. package/dist/core/substrate/on-read.d.ts +22 -25
  195. package/dist/core/substrate/on-read.js +103 -147
  196. package/dist/core/substrate/render-node.d.ts +4 -7
  197. package/dist/core/substrate/render-node.js +5 -7
  198. package/dist/core/substrate/render.d.ts +21 -3
  199. package/dist/core/substrate/render.js +291 -223
  200. package/dist/core/substrate/schema.d.ts +1 -13
  201. package/dist/core/substrate/schema.js +5 -40
  202. package/dist/core/substrate/session-cache.d.ts +14 -4
  203. package/dist/core/substrate/session-cache.js +40 -22
  204. package/dist/core/substrate/surface-match.d.ts +9 -7
  205. package/dist/core/substrate/surface-match.js +26 -25
  206. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
  207. package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
  208. package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
  209. package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
  210. package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
  211. package/dist/daemon/api/handlers/broker-ops.js +21 -16
  212. package/dist/daemon/api/handlers/memory.js +2 -0
  213. package/dist/daemon/crtrd.js +2 -0
  214. package/dist/daemon/human/finish.js +8 -1
  215. package/dist/daemon/manage.d.ts +0 -1
  216. package/dist/daemon/manage.js +7 -16
  217. package/dist/daemon/startup-policy.d.ts +1 -0
  218. package/dist/daemon/startup-policy.js +1 -0
  219. package/dist/migrations/001-surfaces-frontmatter.js +21 -109
  220. package/dist/migrations/002-profile-project-memory.js +1 -0
  221. package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
  222. package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
  223. package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
  224. package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
  225. package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
  226. package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
  227. package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
  228. package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
  229. package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
  230. package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
  231. package/dist/migrations/__tests__/activation.test.d.ts +1 -0
  232. package/dist/migrations/__tests__/activation.test.js +149 -0
  233. package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
  234. package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
  235. package/dist/migrations/activation.d.ts +16 -0
  236. package/dist/migrations/activation.js +78 -0
  237. package/dist/migrations/convergent.d.ts +14 -3
  238. package/dist/migrations/convergent.js +21 -10
  239. package/dist/migrations/corpus.d.ts +78 -0
  240. package/dist/migrations/corpus.js +497 -0
  241. package/dist/migrations/frontmatter-splice.d.ts +15 -0
  242. package/dist/migrations/frontmatter-splice.js +176 -0
  243. package/dist/migrations/registry.d.ts +6 -1
  244. package/dist/migrations/registry.js +7 -2
  245. package/dist/migrations/runner.d.ts +41 -0
  246. package/dist/migrations/runner.js +81 -0
  247. package/dist/migrations/types.d.ts +148 -9
  248. package/dist/migrations/types.js +9 -2
  249. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
  250. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
  251. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
  252. package/dist/pi-extensions/canvas-context-intro.js +50 -46
  253. package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
  254. package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
  255. package/dist/pi-extensions/canvas-stophook.js +6 -13
  256. package/dist/shared/generated-context.d.ts +0 -3
  257. package/dist/shared/generated-context.js +0 -57
  258. package/dist/shared/tool-groups.js +2 -3
  259. package/dist/types.d.ts +8 -11
  260. package/dist/types.js +4 -31
  261. package/package.json +5 -4
  262. package/runtime.lock.json +2 -2
  263. package/dist/builtin-memory/05-kinds/design/00-base.md +0 -17
  264. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +0 -15
  265. package/dist/builtin-memory/05-kinds/design/design-contract.md +0 -19
  266. package/dist/builtin-memory/05-kinds/developer/00-base.md +0 -17
  267. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +0 -15
  268. package/dist/builtin-memory/05-kinds/plan/00-base.md +0 -17
  269. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +0 -17
  270. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +0 -28
  271. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -15
  272. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -15
  273. package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +0 -13
  274. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -17
  275. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +0 -17
  276. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -17
  277. package/dist/builtin-memory/05-kinds/spec/00-base.md +0 -17
  278. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +0 -15
  279. package/dist/builtin-memory/05-kinds/spec/requirements.md +0 -15
  280. package/dist/builtin-memory/design/guide.md +0 -35
  281. package/dist/builtin-memory/design/roadmap.md +0 -21
  282. package/dist/builtin-memory/development.md +0 -113
  283. package/dist/builtin-memory/plan/roadmap.md +0 -25
  284. package/dist/builtin-memory/spec/guide.md +0 -53
  285. package/dist/builtin-memory/spec/requirements.md +0 -29
  286. package/dist/builtin-memory/spec/roadmap.md +0 -36
  287. package/dist/builtin-memory/testing.md +0 -39
  288. /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
  289. /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
  290. /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
  291. /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
  292. /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
  293. /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
  294. /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
  295. /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
  296. /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
  297. /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
  298. /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
  299. /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
  300. /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
  301. /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
  302. /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
  303. /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
  304. /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
  305. /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
  306. /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
  307. /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
  308. /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
  309. /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
  310. /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
  311. /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
  312. /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
@@ -14,19 +14,7 @@ export declare function rungAtLeast(r: Rung, min: Rung): boolean;
14
14
  /** The lower of two rungs — the clamp automatic delivery applies to an
15
15
  * authored rung against its project relationship's cap. */
16
16
  export declare function minRung(a: Rung, b: Rung): Rung;
17
- /** Strip an optional `NN-` ordering prefix from ONE path segment (file or
18
- * directory display name). `00-runtime-base/00-authoring` -> `runtime-base/authoring`; `spine` (no
19
- * prefix) is unchanged. */
20
- export declare function normalizeNameSegment(segment: string): string;
21
- /** Normalize a full slash-separated path-derived doc name by stripping the
22
- * optional numeric prefix from EVERY segment: `01-spine/00-has-manager` ->
23
- * `spine/has-manager`. This is the identity a doc displays, dedups, and
24
- * resolves under — distinct from its physical path, which keeps the prefix. */
25
- export declare function normalizeDocName(name: string): string;
26
- /** Resolve a doc's identity from its raw frontmatter record: an explicit
27
- * `name` field wins (trimmed + normalized), else `fallbackName` (the
28
- * normalized path-derived name). */
29
- export declare function resolveDocName(fm: Record<string, unknown> | null | undefined, fallbackName: string): string;
17
+ export { normalizeNameSegment, normalizeDocName, resolveLocalName as resolveDocName } from '../memory/identity.js';
30
18
  export declare const SURFACE_EVENTS: readonly ["boot", "workspace-open", "read", "memory-read", "command"];
31
19
  export type SurfaceEvent = (typeof SURFACE_EVENTS)[number];
32
20
  /** The rungs an entry may deliver at. There is no `none` — silence is the
@@ -42,47 +42,12 @@ export function minRung(a, b) {
42
42
  return rungRank(a) <= rungRank(b) ? a : b;
43
43
  }
44
44
  // ---------------------------------------------------------------------------
45
- // Display-name normalization — the optional `NN-` ordering pin. A doc's
46
- // physical path may carry a two-digit numeric prefix on a file or directory
47
- // segment (`00-runtime-base/`, `01-spine/`) purely to pin structural order
48
- // against natural sort (design: builtin prompt corpus, used sparingly). The
49
- // prefix is NEVER part of the doc's identity: every identity-facing surface —
50
- // MemoryDoc.name, direct lookup, leaf fallback, `crtr memory read`/`list`,
51
- // prompt render, on-read display — derives from the NORMALIZED segments, so
52
- // `00-runtime-base/00-authoring` displays/dedups/resolves as `runtime-base/authoring`. When a doc
53
- // sets an explicit frontmatter `name`, that normalized name wins instead of
54
- // the path-derived fallback. Only the physical path keeps the prefix.
45
+ // Identity — owned entirely by core/memory/identity.ts (ordering-prefix
46
+ // normalization, INDEX collapse, explicit-name-then-path local identity,
47
+ // namespace composition). Re-exported here so document parsing and identity
48
+ // derivation stay one rule with one implementation.
55
49
  // ---------------------------------------------------------------------------
56
- const NUMERIC_PREFIX_RE = /^\d{2}-/;
57
- /** Strip an optional `NN-` ordering prefix from ONE path segment (file or
58
- * directory display name). `00-runtime-base/00-authoring` -> `runtime-base/authoring`; `spine` (no
59
- * prefix) is unchanged. */
60
- export function normalizeNameSegment(segment) {
61
- return segment.replace(NUMERIC_PREFIX_RE, '');
62
- }
63
- /** Normalize a full slash-separated path-derived doc name by stripping the
64
- * optional numeric prefix from EVERY segment: `01-spine/00-has-manager` ->
65
- * `spine/has-manager`. This is the identity a doc displays, dedups, and
66
- * resolves under — distinct from its physical path, which keeps the prefix. */
67
- export function normalizeDocName(name) {
68
- return name.split('/').map(normalizeNameSegment).join('/');
69
- }
70
- // ---------------------------------------------------------------------------
71
- // Substrate identity — the ONE explicit-name-then-path-fallback rule. A doc's
72
- // resolver identity is its explicit frontmatter `name` when present (normalized),
73
- // otherwise the caller-supplied path-derived fallback. Every loader that turns a
74
- // physical .md file into a named doc (the resolver's `listMemoryDocsInDir` and
75
- // the explicit file-context loader) calls this ONE helper, so a doc's identity
76
- // never depends on which path loaded it.
77
- // ---------------------------------------------------------------------------
78
- /** Resolve a doc's identity from its raw frontmatter record: an explicit
79
- * `name` field wins (trimmed + normalized), else `fallbackName` (the
80
- * normalized path-derived name). */
81
- export function resolveDocName(fm, fallbackName) {
82
- const raw = fm?.['name'];
83
- const explicit = typeof raw === 'string' && raw.trim() !== '' ? normalizeDocName(raw.trim()) : '';
84
- return explicit !== '' ? explicit : fallbackName;
85
- }
50
+ export { normalizeNameSegment, normalizeDocName, resolveLocalName as resolveDocName } from '../memory/identity.js';
86
51
  // ---------------------------------------------------------------------------
87
52
  // Surfaces — explicit event routing (design: surfaces-design.md). A doc
88
53
  // declares WHEN it delivers as (event, match, rung) entries; matches are
@@ -2,6 +2,14 @@ import type { MemoryDoc } from '../memory-resolver.js';
2
2
  import type { SubstrateDoc } from './schema.js';
3
3
  type ListAllMemoryDocsFn = () => MemoryDoc[];
4
4
  type ParseSubstrateDocFn = (doc: MemoryDoc) => SubstrateDoc | null;
5
+ /** A parsed substrate document with the resolver descriptor it came from. The
6
+ * parsed doc carries canonical identity but not the physical form behind it,
7
+ * so the descriptor is what supplies the routing anchor a `./` memory-read
8
+ * glob matches against. */
9
+ export interface EventCorpusDoc {
10
+ doc: SubstrateDoc;
11
+ source: MemoryDoc;
12
+ }
5
13
  /** Called by canvas-doc-substrate.ts on every `session_start`.
6
14
  * Resets all cached values so the next access triggers a fresh scan. */
7
15
  export declare function clearSessionCache(): void;
@@ -13,13 +21,15 @@ export declare function clearSessionCache(): void;
13
21
  * that need every referenceable doc (inline memory-reference inventory) call
14
22
  * this directly instead of `cachedAllMemoryDocs`. */
15
23
  export declare function cachedAllMemoryDocsInclusive(listFn: ListAllMemoryDocsFn): MemoryDoc[];
16
- /** allMemoryDocsInclusive mapped through parseSubstrateDoc + null-filtered,
17
- * INCLUDING node scope. */
24
+ /** allMemoryDocsInclusive parsed and paired with its descriptors, INCLUDING
25
+ * node scope — the event corpus, and the module's only parse pass. */
26
+ export declare function cachedEventCorpusInclusive(listFn: ListAllMemoryDocsFn, parseFn: ParseSubstrateDocFn): EventCorpusDoc[];
27
+ /** The parsed docs of the inclusive event corpus, without their descriptors. */
18
28
  export declare function cachedSubstrateDocsInclusive(listFn: ListAllMemoryDocsFn, parseFn: ParseSubstrateDocFn): SubstrateDoc[];
19
29
  /** All memory docs (listAllMemoryDocs()), scanned once per session.
20
30
  * `listFn` is injected by the caller to avoid circular imports at module init. */
21
31
  export declare function cachedAllMemoryDocs(listFn: ListAllMemoryDocsFn): MemoryDoc[];
22
- /** allMemoryDocs mapped through parseSubstrateDoc + null-filtered.
23
- * Both render.ts and on-read.ts consume this. */
32
+ /** The node-excluded parsed view — render.ts's boot corpus. Projected out of
33
+ * the inclusive parse rather than parsed again. */
24
34
  export declare function cachedSubstrateDocs(listFn: ListAllMemoryDocsFn, parseFn: ParseSubstrateDocFn): SubstrateDoc[];
25
35
  export {};
@@ -18,27 +18,33 @@
18
18
  // Cached entries:
19
19
  // • allMemoryDocsInclusive — the full listAllMemoryDocs() corpus, UNFILTERED
20
20
  // (includes node scope). This is the ONE underlying
21
- // filesystem scan+parse the whole module performs per
22
- // session; every other entry below derives from it by
21
+ // filesystem scan the whole module performs per session;
22
+ // every other entry below derives from it by
23
23
  // filtering/mapping, never by calling listFn() again.
24
- // • substrateDocsInclusive — allMemoryDocsInclusive mapped through
25
- // parseSubstrateDoc and null-filtered, still including
26
- // node scope. Consumers that need every referenceable
27
- // doc (e.g. the inline memory-reference inventory) use
28
- // this pair.
24
+ // • eventCorpusInclusive — allMemoryDocsInclusive parsed once, each surviving
25
+ // SubstrateDoc paired with the resolver descriptor it
26
+ // parsed from. The ONE parse pass; both substrate views
27
+ // below project out of it. on-read.ts consumes the pairs
28
+ // because `./` memory-read globs anchor on the
29
+ // descriptor's canonical routing anchor.
30
+ // • substrateDocsInclusive — the parsed docs alone, still including node
31
+ // scope. Consumers that need every referenceable doc
32
+ // (e.g. the inline memory-reference inventory) use this
33
+ // pair with allMemoryDocsInclusive.
29
34
  // • allMemoryDocs — allMemoryDocsInclusive with node-scope docs excluded
30
35
  // (boot-only view — see the comment on
31
36
  // cachedAllMemoryDocs). Consumed by render.ts.
32
- // • substrateDocs — allMemoryDocs mapped through parseSubstrateDoc and
33
- // null-filtered. Used by render.ts's resolver corpus.
34
- // The event corpus (on-read.ts) uses the INCLUSIVE
35
- // pair instead: node docs' surfaces entries fire on
36
- // read/memory-read/command like any other store's.
37
+ // • substrateDocs — the same node-excluded view, parsed. Used by
38
+ // render.ts's resolver corpus. The event corpus
39
+ // (on-read.ts) uses the INCLUSIVE pairs instead: node
40
+ // docs' surfaces entries fire on read/memory-read/
41
+ // command like any other store's.
37
42
  // Node subjects are deliberately NOT cached: lifecycle, mode, kind, and other
38
43
  // gate inputs can change while a resident broker/session remains live. Rendering
39
44
  // must always gate against the current persisted node metadata.
40
45
  const _cache = {
41
46
  allMemoryDocsInclusive: null,
47
+ eventCorpusInclusive: null,
42
48
  substrateDocsInclusive: null,
43
49
  allMemoryDocs: null,
44
50
  substrateDocs: null,
@@ -47,6 +53,7 @@ const _cache = {
47
53
  * Resets all cached values so the next access triggers a fresh scan. */
48
54
  export function clearSessionCache() {
49
55
  _cache.allMemoryDocsInclusive = null;
56
+ _cache.eventCorpusInclusive = null;
50
57
  _cache.substrateDocsInclusive = null;
51
58
  _cache.allMemoryDocs = null;
52
59
  _cache.substrateDocs = null;
@@ -64,13 +71,24 @@ export function cachedAllMemoryDocsInclusive(listFn) {
64
71
  }
65
72
  return _cache.allMemoryDocsInclusive;
66
73
  }
67
- /** allMemoryDocsInclusive mapped through parseSubstrateDoc + null-filtered,
68
- * INCLUDING node scope. */
74
+ /** allMemoryDocsInclusive parsed and paired with its descriptors, INCLUDING
75
+ * node scope — the event corpus, and the module's only parse pass. */
76
+ export function cachedEventCorpusInclusive(listFn, parseFn) {
77
+ if (_cache.eventCorpusInclusive === null) {
78
+ const out = [];
79
+ for (const source of cachedAllMemoryDocsInclusive(listFn)) {
80
+ const doc = parseFn(source);
81
+ if (doc !== null)
82
+ out.push({ doc, source });
83
+ }
84
+ _cache.eventCorpusInclusive = out;
85
+ }
86
+ return _cache.eventCorpusInclusive;
87
+ }
88
+ /** The parsed docs of the inclusive event corpus, without their descriptors. */
69
89
  export function cachedSubstrateDocsInclusive(listFn, parseFn) {
70
90
  if (_cache.substrateDocsInclusive === null) {
71
- _cache.substrateDocsInclusive = cachedAllMemoryDocsInclusive(listFn)
72
- .map(parseFn)
73
- .filter((d) => d !== null);
91
+ _cache.substrateDocsInclusive = cachedEventCorpusInclusive(listFn, parseFn).map((e) => e.doc);
74
92
  }
75
93
  return _cache.substrateDocsInclusive;
76
94
  }
@@ -91,13 +109,13 @@ export function cachedAllMemoryDocs(listFn) {
91
109
  }
92
110
  return _cache.allMemoryDocs;
93
111
  }
94
- /** allMemoryDocs mapped through parseSubstrateDoc + null-filtered.
95
- * Both render.ts and on-read.ts consume this. */
112
+ /** The node-excluded parsed view — render.ts's boot corpus. Projected out of
113
+ * the inclusive parse rather than parsed again. */
96
114
  export function cachedSubstrateDocs(listFn, parseFn) {
97
115
  if (_cache.substrateDocs === null) {
98
- _cache.substrateDocs = cachedAllMemoryDocs(listFn)
99
- .map(parseFn)
100
- .filter((d) => d !== null);
116
+ _cache.substrateDocs = cachedEventCorpusInclusive(listFn, parseFn)
117
+ .filter((e) => e.doc.scope !== 'node')
118
+ .map((e) => e.doc);
101
119
  }
102
120
  return _cache.substrateDocs;
103
121
  }
@@ -15,9 +15,9 @@ export declare function underOwningRoot(doc: Pick<SubstrateDoc, 'path'>, absFile
15
15
  * for non-markdown files); an empty record never satisfies
16
16
  * `matchFrontmatter`. */
17
17
  export declare function matchesReadEntry(entry: SurfaceEntry, doc: Pick<SubstrateDoc, 'path' | 'scope'>, subject: NodeConfigSubject, absReadFile: string, readFrontmatter: Record<string, unknown>): boolean;
18
- /** Does a `memory-read` entry on the doc named `carrierName` fit a resolved
19
- * read of a doc known by `targetNames`? */
20
- export declare function matchesMemoryReadEntry(entry: SurfaceEntry, carrierName: string, subject: NodeConfigSubject | null, targetNames: string[]): boolean;
18
+ /** Does a `memory-read` entry on a carrier anchored at `routingAnchor` fit a
19
+ * resolved read of the doc known by its exact canonical `name`? */
20
+ export declare function matchesMemoryReadEntry(entry: SurfaceEntry, routingAnchor: string, subject: NodeConfigSubject | null, name: string): boolean;
21
21
  /** Does a `command` entry fit the executed command string? */
22
22
  export declare function matchesCommandEntry(entry: SurfaceEntry, subject: NodeConfigSubject, command: string): boolean;
23
23
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
@@ -25,9 +25,11 @@ export declare function readDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'p
25
25
  /** The doc's workspace-open rung. Presence of an entry is the match; callers
26
26
  * scope the corpus to project stores mounted by the subject's cwd/profile. */
27
27
  export declare function workspaceOpenRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject): Rung;
28
- /** The doc's delivery rung for a `crtr memory read` that resolved a doc
29
- * known by `targetNames` (canonical name, plus the mount-stripped name for
30
- * plugin-mounted docs). The doc's own `name` anchors `./` globs. */
31
- export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'name'>, subject: NodeConfigSubject | null, targetNames: string[]): Rung;
28
+ /** The doc's delivery rung for a `crtr memory read` that resolved a doc known
29
+ * by its exact canonical `name` — no mount-stripped or other alias.
30
+ * `routingAnchor` comes from the doc's resolver descriptor — it is not
31
+ * derivable from the canonical name, which cannot say whether the doc fronts
32
+ * its own directory. */
33
+ export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, routingAnchor: string, subject: NodeConfigSubject | null, name: string): Rung;
32
34
  /** The doc's delivery rung for an executed command string. */
33
35
  export declare function commandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject, command: string): Rung;
@@ -7,7 +7,7 @@
7
7
  // AND together — each present constraint must fit, a `match` list is satisfied
8
8
  // by any one glob — and multiple entries per event OR together. The `./`
9
9
  // prefix is the one relative spelling: it anchors a glob to the doc's own
10
- // container (its owning repo dir for `read`, its name-directory for
10
+ // container (its owning repo dir for `read`, its canonical routing anchor for
11
11
  // `memory-read`).
12
12
  import { basename, isAbsolute, matchesGlob, relative, sep } from 'node:path';
13
13
  import { CRTR_DIR_NAME } from '../../types.js';
@@ -77,31 +77,30 @@ export function matchesReadEntry(entry, doc, subject, absReadFile, readFrontmatt
77
77
  }
78
78
  return entry.match !== undefined || entry.matchFrontmatter !== undefined;
79
79
  }
80
- /** One memory-read glob vs the resolved doc's names: plain globs match any of
81
- * the caller-supplied target names (the full canonical name, plus the
82
- * mount-stripped name for plugin-mounted docs); a `./`-anchored glob matches
83
- * each target relative to the CARRIER doc's own name-directory. Names are
84
- * slash-separated, so path globbing applies segment-wise. */
85
- function memoryReadGlobMatches(glob, carrierName, targetNames) {
80
+ /** One memory-read glob vs the resolved doc's exact canonical name: plain
81
+ * globs match that name only — no mount-stripped or other alias, per the
82
+ * hard-cut canonical-only routing contract; a `./`-anchored glob matches
83
+ * the name relative to the CARRIER's routing anchor — its own canonical
84
+ * name when the carrier fronts a directory, its canonical parent when it is
85
+ * an ordinary leaf. Names are slash-separated, so path globbing applies
86
+ * segment-wise. */
87
+ function memoryReadGlobMatches(glob, routingAnchor, name) {
86
88
  if (glob.startsWith('./')) {
87
- const dir = carrierName.split('/').slice(0, -1).join('/');
88
89
  const stripped = glob.slice(2);
89
- return targetNames.some((name) => {
90
- if (dir === '')
91
- return safeGlob(name, stripped);
92
- if (!name.startsWith(dir + '/'))
93
- return false;
94
- return safeGlob(name.slice(dir.length + 1), stripped);
95
- });
90
+ if (routingAnchor === '')
91
+ return safeGlob(name, stripped);
92
+ if (!name.startsWith(routingAnchor + '/'))
93
+ return false;
94
+ return safeGlob(name.slice(routingAnchor.length + 1), stripped);
96
95
  }
97
- return targetNames.some((name) => safeGlob(name, glob));
96
+ return safeGlob(name, glob);
98
97
  }
99
- /** Does a `memory-read` entry on the doc named `carrierName` fit a resolved
100
- * read of a doc known by `targetNames`? */
101
- export function matchesMemoryReadEntry(entry, carrierName, subject, targetNames) {
98
+ /** Does a `memory-read` entry on a carrier anchored at `routingAnchor` fit a
99
+ * resolved read of the doc known by its exact canonical `name`? */
100
+ export function matchesMemoryReadEntry(entry, routingAnchor, subject, name) {
102
101
  if (entry.on !== 'memory-read' || entry.match === undefined || !surfaceEntryGatePasses(entry, subject))
103
102
  return false;
104
- return entry.match.some((g) => memoryReadGlobMatches(g, carrierName, targetNames));
103
+ return entry.match.some((g) => memoryReadGlobMatches(g, routingAnchor, name));
105
104
  }
106
105
  /** A command glob is a STRING glob over the whole command line, not a path
107
106
  * glob: `*` crosses every character (including `/` — command lines carry
@@ -154,13 +153,15 @@ export function workspaceOpenRung(doc, subject) {
154
153
  }
155
154
  return r;
156
155
  }
157
- /** The doc's delivery rung for a `crtr memory read` that resolved a doc
158
- * known by `targetNames` (canonical name, plus the mount-stripped name for
159
- * plugin-mounted docs). The doc's own `name` anchors `./` globs. */
160
- export function memoryReadDeliveryRung(doc, subject, targetNames) {
156
+ /** The doc's delivery rung for a `crtr memory read` that resolved a doc known
157
+ * by its exact canonical `name` — no mount-stripped or other alias.
158
+ * `routingAnchor` comes from the doc's resolver descriptor — it is not
159
+ * derivable from the canonical name, which cannot say whether the doc fronts
160
+ * its own directory. */
161
+ export function memoryReadDeliveryRung(doc, routingAnchor, subject, name) {
161
162
  let r = 'none';
162
163
  for (const e of doc.surfaces) {
163
- if (!matchesMemoryReadEntry(e, doc.name, subject, targetNames))
164
+ if (!matchesMemoryReadEntry(e, routingAnchor, subject, name))
164
165
  continue;
165
166
  if (rungRank(e.at) > rungRank(r))
166
167
  r = e.at;
@@ -0,0 +1,30 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export interface SourceDaemonFixture {
3
+ root: string;
4
+ home: string;
5
+ cwd: string;
6
+ canvasHome: string;
7
+ pidfile: string;
8
+ socket: string;
9
+ tcpPort: number;
10
+ dispose(): void;
11
+ }
12
+ export interface SourceDaemonProcess {
13
+ child: ChildProcess;
14
+ exited: Promise<{
15
+ code: number | null;
16
+ signal: NodeJS.Signals | null;
17
+ }>;
18
+ stdout(): string;
19
+ stderr(): string;
20
+ stop(): Promise<{
21
+ code: number | null;
22
+ signal: NodeJS.Signals | null;
23
+ }>;
24
+ }
25
+ export declare function createSourceDaemonFixture(): Promise<SourceDaemonFixture>;
26
+ export declare function startSourceDaemon(fixture: SourceDaemonFixture): SourceDaemonProcess;
27
+ export declare function healthRequest(port: number, timeoutMs?: number): Promise<boolean>;
28
+ export declare function waitForHealth(process: SourceDaemonProcess, port: number, timeoutMs?: number): Promise<void>;
29
+ export declare function healthWasReadyBeforeExit(process: SourceDaemonProcess, port: number): Promise<boolean>;
30
+ export declare function brokerPidsForCanvas(canvasHome: string): number[];
@@ -0,0 +1,174 @@
1
+ import { spawn, execFileSync } from 'node:child_process';
2
+ import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
3
+ import { createServer } from 'node:net';
4
+ import { request } from 'node:http';
5
+ import { join } from 'node:path';
6
+ import { createRequire } from 'node:module';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+ function delay(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ async function reserveTcpPort() {
12
+ const server = createServer();
13
+ await new Promise((resolve, reject) => {
14
+ server.once('error', reject);
15
+ server.listen(0, '127.0.0.1', resolve);
16
+ });
17
+ const address = server.address();
18
+ if (address === null || typeof address === 'string')
19
+ throw new Error('test TCP listener did not receive a port');
20
+ await new Promise((resolve, reject) => server.close((error) => error === undefined ? resolve() : reject(error)));
21
+ return address.port;
22
+ }
23
+ export async function createSourceDaemonFixture() {
24
+ const root = mkdtempSync('/tmp/crtr-migration-daemon-');
25
+ const home = join(root, 'home');
26
+ const cwd = join(home, 'work');
27
+ const canvasHome = join(root, 'canvas');
28
+ const pidfile = join(canvasHome, 'ready.pid');
29
+ const socket = join(canvasHome, 'crtrd.sock');
30
+ mkdirSync(cwd, { recursive: true });
31
+ mkdirSync(canvasHome, { recursive: true });
32
+ const tcpPort = await reserveTcpPort();
33
+ return {
34
+ root,
35
+ home,
36
+ cwd,
37
+ canvasHome,
38
+ pidfile,
39
+ socket,
40
+ tcpPort,
41
+ dispose: () => rmSync(root, { recursive: true, force: true }),
42
+ };
43
+ }
44
+ export function startSourceDaemon(fixture) {
45
+ const require = createRequire(import.meta.url);
46
+ const tsxLoader = pathToFileURL(require.resolve('tsx/esm')).href;
47
+ const entry = fileURLToPath(new URL('../../crtrd-cli.ts', import.meta.url));
48
+ const env = {
49
+ ...process.env,
50
+ HOME: fixture.home,
51
+ CRTR_HOME: fixture.canvasHome,
52
+ CRTR_PIDFILE: fixture.pidfile,
53
+ CRTRD_TCP: `127.0.0.1:${fixture.tcpPort}`,
54
+ XDG_CACHE_HOME: join(fixture.root, 'cache'),
55
+ XDG_CONFIG_HOME: join(fixture.root, 'config'),
56
+ XDG_DATA_HOME: join(fixture.root, 'data'),
57
+ };
58
+ for (const key of [
59
+ 'CRTR_NODE_ID',
60
+ 'CRTR_KIND',
61
+ 'CRTR_MODE',
62
+ 'CRTR_LIFECYCLE',
63
+ 'CRTR_NODE_CWD',
64
+ 'CRTR_CONTEXT_DIR',
65
+ 'CRTR_CYCLES',
66
+ 'CRTR_PROFILE_ID',
67
+ 'CRTR_PARENT_NODE_ID',
68
+ 'CRTR_FRONT_DOOR',
69
+ 'CRTR_BROKER_ENGINE',
70
+ 'CRTR_PI_BINARY',
71
+ 'NODE_OPTIONS',
72
+ ])
73
+ delete env[key];
74
+ const child = spawn(process.execPath, [
75
+ '--conditions=crtr-src',
76
+ '--import', tsxLoader,
77
+ entry,
78
+ '--canvas-home', fixture.canvasHome,
79
+ ], {
80
+ cwd: fixture.cwd,
81
+ env,
82
+ stdio: ['ignore', 'pipe', 'pipe'],
83
+ });
84
+ let stdout = '';
85
+ let stderr = '';
86
+ child.stdout.setEncoding('utf8');
87
+ child.stderr.setEncoding('utf8');
88
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
89
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
90
+ const exited = new Promise((resolve, reject) => {
91
+ child.once('error', reject);
92
+ child.once('close', (code, signal) => resolve({ code, signal }));
93
+ });
94
+ return {
95
+ child,
96
+ exited,
97
+ stdout: () => stdout,
98
+ stderr: () => stderr,
99
+ stop: async () => {
100
+ if (child.exitCode === null && child.signalCode === null)
101
+ child.kill('SIGTERM');
102
+ const timeout = setTimeout(() => {
103
+ if (child.exitCode === null && child.signalCode === null)
104
+ child.kill('SIGKILL');
105
+ }, 10_000);
106
+ try {
107
+ return await exited;
108
+ }
109
+ finally {
110
+ clearTimeout(timeout);
111
+ }
112
+ },
113
+ };
114
+ }
115
+ export function healthRequest(port, timeoutMs = 200) {
116
+ return new Promise((resolve) => {
117
+ let settled = false;
118
+ const finish = (ready) => {
119
+ if (settled)
120
+ return;
121
+ settled = true;
122
+ resolve(ready);
123
+ };
124
+ const req = request({ host: '127.0.0.1', port, path: '/healthz', method: 'GET', agent: false }, (response) => {
125
+ response.resume();
126
+ response.once('end', () => finish(response.statusCode === 200));
127
+ });
128
+ req.once('error', () => finish(false));
129
+ req.setTimeout(timeoutMs, () => {
130
+ req.destroy();
131
+ finish(false);
132
+ });
133
+ req.end();
134
+ });
135
+ }
136
+ export async function waitForHealth(process, port, timeoutMs = 20_000) {
137
+ const deadline = Date.now() + timeoutMs;
138
+ while (Date.now() < deadline) {
139
+ if (await healthRequest(port))
140
+ return;
141
+ if (process.child.exitCode !== null || process.child.signalCode !== null) {
142
+ const result = await process.exited;
143
+ throw new Error(`source daemon exited before health readiness (${JSON.stringify(result)}):\n${process.stderr()}`);
144
+ }
145
+ await delay(10);
146
+ }
147
+ throw new Error(`source daemon did not answer /healthz within ${timeoutMs}ms:\n${process.stderr()}`);
148
+ }
149
+ export async function healthWasReadyBeforeExit(process, port) {
150
+ let exited = false;
151
+ void process.exited.then(() => { exited = true; });
152
+ while (!exited) {
153
+ if (await healthRequest(port, 50))
154
+ return true;
155
+ await delay(2);
156
+ }
157
+ return false;
158
+ }
159
+ export function brokerPidsForCanvas(canvasHome) {
160
+ let table;
161
+ try {
162
+ table = execFileSync('ps', ['-ax', '-o', 'pid=,command='], { encoding: 'utf8', timeout: 2_000 });
163
+ }
164
+ catch {
165
+ return [];
166
+ }
167
+ const declaration = `--canvas-home ${canvasHome}`;
168
+ return table.split('\n').flatMap((line) => {
169
+ if (!line.includes('broker-cli') || !line.includes(declaration))
170
+ return [];
171
+ const pid = Number.parseInt(line.trim().split(/\s+/, 1)[0] ?? '', 10);
172
+ return Number.isInteger(pid) ? [pid] : [];
173
+ });
174
+ }
@@ -0,0 +1,97 @@
1
+ import assert from 'node:assert/strict';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, watch, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import test from 'node:test';
5
+ import { onDiskMigrationIdentity } from '../../../migrations/registry.js';
6
+ import { brokerPidsForCanvas, createSourceDaemonFixture, healthWasReadyBeforeExit, startSourceDaemon, waitForHealth, } from '../helpers/source-daemon.js';
7
+ const PRE_MIGRATION_DOC = `---
8
+ kind: knowledge
9
+ name: daemon-ordering
10
+ system-prompt-visibility: preview
11
+ file-read-visibility: content
12
+ applies-to:
13
+ - "**/*.ts"
14
+ read-when:
15
+ language: typescript
16
+ ---
17
+ # Daemon ordering
18
+
19
+ This document begins in the pre-surfaces schema.
20
+ `;
21
+ function writeUserMemory(home, name, source) {
22
+ const path = join(home, '.crouter', 'memory', name);
23
+ mkdirSync(join(home, '.crouter', 'memory'), { recursive: true });
24
+ writeFileSync(path, source, 'utf8');
25
+ return path;
26
+ }
27
+ test('source daemon migrates the pre-migration corpus and writes its marker before API readiness', { timeout: 30_000 }, async () => {
28
+ const fixture = await createSourceDaemonFixture();
29
+ const docPath = writeUserMemory(fixture.home, 'daemon-ordering.md', PRE_MIGRATION_DOC);
30
+ const markerPath = join(fixture.canvasHome, 'on-disk-migrations.json');
31
+ const dbPath = join(fixture.canvasHome, 'canvas.db');
32
+ assert.equal(existsSync(markerPath), false, 'fixture starts without a migration marker');
33
+ assert.equal(existsSync(dbPath), false, 'fixture starts without canvas.db');
34
+ assert.match(readFileSync(docPath, 'utf8'), /system-prompt-visibility:/, 'fixture is genuinely pre-migration');
35
+ const daemon = startSourceDaemon(fixture);
36
+ try {
37
+ const markerDeadline = Date.now() + 20_000;
38
+ while (!existsSync(markerPath) && Date.now() < markerDeadline) {
39
+ await new Promise((resolve) => setTimeout(resolve, 1));
40
+ }
41
+ assert.equal(existsSync(markerPath), true, `marker was not written; stderr:\n${daemon.stderr()}`);
42
+ const migratedAtMarker = readFileSync(docPath, 'utf8');
43
+ assert.doesNotMatch(migratedAtMarker, /system-prompt-visibility:|file-read-visibility:|applies-to:|read-when:/);
44
+ assert.equal(existsSync(fixture.pidfile), false, 'pidfile readiness is not published when the marker first appears');
45
+ assert.equal(existsSync(fixture.socket), false, 'API readiness is not published when the marker first appears');
46
+ await waitForHealth(daemon, fixture.tcpPort);
47
+ const migrated = readFileSync(docPath, 'utf8');
48
+ assert.doesNotMatch(migrated, /system-prompt-visibility:|file-read-visibility:|applies-to:|read-when:/);
49
+ assert.match(migrated, /surfaces:/);
50
+ assert.match(migrated, /on: boot/);
51
+ assert.match(migrated, /on: read/);
52
+ assert.deepEqual(JSON.parse(readFileSync(markerPath, 'utf8')), onDiskMigrationIdentity());
53
+ assert.equal(existsSync(fixture.pidfile), true, 'health readiness follows the pidfile readiness mirror');
54
+ assert.equal(existsSync(fixture.socket), true, 'the unique unix API listener is bound at readiness');
55
+ assert.equal(existsSync(dbPath), true, 'daemon ownership opens canvas.db only after migration');
56
+ assert.ok(statSync(markerPath).mtimeMs <= statSync(fixture.pidfile).mtimeMs, 'the completion marker is written no later than the readiness pidfile');
57
+ assert.ok(statSync(markerPath).mtimeMs <= statSync(fixture.socket).mtimeMs, 'the completion marker is written no later than the API listener');
58
+ }
59
+ finally {
60
+ const exit = await daemon.stop();
61
+ fixture.dispose();
62
+ assert.deepEqual(exit, { code: 0, signal: null }, `daemon must stop cleanly; stderr:\n${daemon.stderr()}`);
63
+ }
64
+ });
65
+ test('a real migration blocker exits before database, pidfile, listeners, or broker effects', { timeout: 30_000 }, async () => {
66
+ const fixture = await createSourceDaemonFixture();
67
+ const blockerPath = writeUserMemory(fixture.home, 'blocked.md', `---\nkind: knowledge\nbroken: [\n---\n# Blocked\n`);
68
+ const effectNames = new Set();
69
+ const effects = watch(fixture.canvasHome, (_event, filename) => {
70
+ if (filename !== null)
71
+ effectNames.add(filename.toString());
72
+ });
73
+ const daemon = startSourceDaemon(fixture);
74
+ const readiness = healthWasReadyBeforeExit(daemon, fixture.tcpPort);
75
+ const exit = await daemon.exited;
76
+ const wasReady = await readiness;
77
+ await new Promise((resolve) => setTimeout(resolve, 20));
78
+ effects.close();
79
+ try {
80
+ assert.equal(exit.signal, null);
81
+ assert.notEqual(exit.code, 0, `blocker must exit nonzero; stderr:\n${daemon.stderr()}`);
82
+ assert.match(daemon.stderr(), new RegExp(blockerPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
83
+ assert.match(daemon.stderr(), /Repair: crtr sys migrate/);
84
+ assert.equal(wasReady, false, 'the unique TCP endpoint never answered health');
85
+ const databaseFiles = readdirSync(fixture.canvasHome).filter((name) => name.startsWith('canvas.db'));
86
+ assert.deepEqual(databaseFiles, [], 'canvas.db and its journals were never created');
87
+ assert.equal(existsSync(fixture.pidfile), false, 'readiness pidfile was never created');
88
+ assert.equal(existsSync(fixture.socket), false, 'unix API listener was never created');
89
+ assert.equal(existsSync(join(fixture.canvasHome, 'on-disk-migrations.json')), false, 'blocker never gets a completion marker');
90
+ assert.equal(existsSync(join(fixture.canvasHome, 'nodes')), false, 'no node/broker state was created');
91
+ assert.deepEqual(brokerPidsForCanvas(fixture.canvasHome), [], 'no broker process declares the isolated canvas');
92
+ assert.deepEqual([...effectNames].filter((name) => name.startsWith('canvas.db') || name === 'ready.pid' || name === 'crtrd.sock' || name === 'nodes'), [], `filesystem watcher saw no transient daemon effects (all events: ${[...effectNames].join(', ')})`);
93
+ }
94
+ finally {
95
+ fixture.dispose();
96
+ }
97
+ });