@north-light/crouter 0.3.227 → 0.3.228

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 (286) 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/05-kinds/design/00-base.md +3 -4
  7. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
  8. package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
  9. package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
  10. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
  11. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
  12. package/dist/builtin-memory/design/guide.md +38 -16
  13. package/dist/builtin-memory/design/roadmap.md +4 -4
  14. package/dist/builtin-memory/insights/init.md +5 -5
  15. package/dist/builtin-memory/internal/INDEX.md +1 -1
  16. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  17. package/dist/builtin-memory/internal/memory-loading.md +6 -6
  18. package/dist/builtin-memory/internal/plugins.md +1 -1
  19. package/dist/builtin-memory/plan/guide.md +53 -0
  20. package/dist/builtin-memory/plan/roadmap.md +10 -8
  21. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
  22. package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
  23. package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
  24. package/dist/clients/attach/chrome/inbox-strip.js +11 -7
  25. package/dist/clients/attach/session/pane-tag.d.ts +28 -2
  26. package/dist/clients/attach/session/pane-tag.js +48 -22
  27. package/dist/clients/attach/session/profile-files.d.ts +6 -0
  28. package/dist/clients/attach/session/profile-files.js +44 -11
  29. package/dist/clients/attach/slash/dispatch.js +4 -7
  30. package/dist/clients/attach/viewer.js +588 -596
  31. package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
  32. package/dist/commands/api-client.d.ts +3 -7
  33. package/dist/commands/api-client.js +7 -12
  34. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
  35. package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
  36. package/dist/commands/memory/delete.js +51 -24
  37. package/dist/commands/memory/edit.js +50 -10
  38. package/dist/commands/memory/find.js +105 -56
  39. package/dist/commands/memory/history.js +51 -31
  40. package/dist/commands/memory/lint.d.ts +0 -9
  41. package/dist/commands/memory/lint.js +78 -322
  42. package/dist/commands/memory/list.d.ts +12 -4
  43. package/dist/commands/memory/list.js +64 -25
  44. package/dist/commands/memory/move.js +103 -73
  45. package/dist/commands/memory/origin.js +35 -5
  46. package/dist/commands/memory/read.d.ts +4 -0
  47. package/dist/commands/memory/read.js +132 -80
  48. package/dist/commands/memory/shared.d.ts +115 -25
  49. package/dist/commands/memory/shared.js +331 -74
  50. package/dist/commands/memory/write.js +72 -21
  51. package/dist/commands/memory.js +2 -2
  52. package/dist/commands/node/lifecycle.js +2 -2
  53. package/dist/commands/pkg/market-manage.js +25 -24
  54. package/dist/commands/pkg/plugin-manage.d.ts +22 -6
  55. package/dist/commands/pkg/plugin-manage.js +118 -25
  56. package/dist/commands/pkg/shared.d.ts +8 -0
  57. package/dist/commands/pkg/shared.js +23 -20
  58. package/dist/commands/surface-reload.d.ts +5 -0
  59. package/dist/commands/surface-reload.js +9 -1
  60. package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
  61. package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
  62. package/dist/commands/sys/migrate.js +126 -140
  63. package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
  64. package/dist/commands/sys/panels/profiles-panel.js +599 -0
  65. package/dist/commands/sys/settings-shell.d.ts +4 -2
  66. package/dist/commands/sys/settings-shell.js +35 -4
  67. package/dist/commands/sys/settings.js +21 -5
  68. package/dist/commands/sys/sync-deps.d.ts +2 -0
  69. package/dist/commands/sys/sync-deps.js +26 -15
  70. package/dist/commands/sys/sync-project-guidance.js +228 -145
  71. package/dist/commands/sys/sync-skills.js +28 -17
  72. package/dist/commands/sys/update.js +11 -3
  73. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
  74. package/dist/core/__tests__/config-change-delta.test.js +106 -0
  75. package/dist/core/__tests__/context-intro.test.js +13 -9
  76. package/dist/core/__tests__/daemon-boot.test.js +22 -16
  77. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
  78. package/dist/core/__tests__/human-deliver.test.js +19 -0
  79. package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
  80. package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
  81. package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
  82. package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
  83. package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
  84. package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
  85. package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
  86. package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
  87. package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
  88. package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
  89. package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
  90. package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
  91. package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
  92. package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
  93. package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
  94. package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
  95. package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
  96. package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
  97. package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
  98. package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
  99. package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
  100. package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
  101. package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
  102. package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
  103. package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
  104. package/dist/core/__tests__/kickoff.test.js +16 -5
  105. package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
  106. package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
  107. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
  108. package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
  109. package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
  110. package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
  111. package/dist/core/__tests__/repository-association.test.d.ts +1 -0
  112. package/dist/core/__tests__/repository-association.test.js +153 -0
  113. package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
  114. package/dist/core/__tests__/repository-root-identity.test.js +219 -0
  115. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
  116. package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
  117. package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
  118. package/dist/core/canvas/canvas.d.ts +3 -0
  119. package/dist/core/canvas/canvas.js +30 -10
  120. package/dist/core/canvas/db.js +28 -1
  121. package/dist/core/canvas/paths.d.ts +4 -6
  122. package/dist/core/canvas/paths.js +8 -6
  123. package/dist/core/canvas/render-source.js +2 -2
  124. package/dist/core/canvas/types.d.ts +9 -13
  125. package/dist/core/exclusive-lock.d.ts +2 -0
  126. package/dist/core/exclusive-lock.js +21 -0
  127. package/dist/core/git.d.ts +0 -1
  128. package/dist/core/git.js +0 -3
  129. package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
  130. package/dist/core/human/feedback-companion.js +3 -0
  131. package/dist/core/human/scan.js +8 -1
  132. package/dist/core/keybindings/catalog.d.ts +2 -2
  133. package/dist/core/keybindings/catalog.js +2 -1
  134. package/dist/core/memory/doc-link-grammar.js +2 -2
  135. package/dist/core/memory/history.d.ts +24 -0
  136. package/dist/core/memory/history.js +66 -1
  137. package/dist/core/memory/identity.d.ts +65 -0
  138. package/dist/core/memory/identity.js +185 -0
  139. package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
  140. package/dist/core/memory/inline-ref-guidance.js +1 -1
  141. package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
  142. package/dist/core/memory/inline-ref-inventory.js +30 -62
  143. package/dist/core/memory/lint.d.ts +129 -0
  144. package/dist/core/memory/lint.js +517 -0
  145. package/dist/core/memory/project-namespace.d.ts +31 -0
  146. package/dist/core/memory/project-namespace.js +85 -0
  147. package/dist/core/memory/repository-association.d.ts +31 -0
  148. package/dist/core/memory/repository-association.js +129 -0
  149. package/dist/core/memory/tree.d.ts +39 -0
  150. package/dist/core/memory/tree.js +93 -0
  151. package/dist/core/memory-resolver.d.ts +193 -90
  152. package/dist/core/memory-resolver.js +461 -375
  153. package/dist/core/nested-stores.js +6 -13
  154. package/dist/core/profiles/select.d.ts +4 -1
  155. package/dist/core/profiles/select.js +108 -48
  156. package/dist/core/review/__tests__/capture-origin.test.js +2 -2
  157. package/dist/core/review/__tests__/stage-identity.test.js +2 -2
  158. package/dist/core/review/companion.js +11 -2
  159. package/dist/core/runtime/bearings.d.ts +2 -2
  160. package/dist/core/runtime/bearings.js +3 -3
  161. package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
  162. package/dist/core/runtime/broker/rebind.js +5 -0
  163. package/dist/core/runtime/broker-extension-render.d.ts +7 -3
  164. package/dist/core/runtime/broker-extension-render.js +10 -5
  165. package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
  166. package/dist/core/runtime/broker-persona-guidance.js +122 -27
  167. package/dist/core/runtime/deliver-live.d.ts +16 -4
  168. package/dist/core/runtime/deliver-live.js +29 -15
  169. package/dist/core/runtime/kickoff.d.ts +3 -3
  170. package/dist/core/runtime/kickoff.js +8 -16
  171. package/dist/core/runtime/lifecycle.js +2 -3
  172. package/dist/core/runtime/nodes.d.ts +3 -4
  173. package/dist/core/runtime/nodes.js +3 -4
  174. package/dist/core/runtime/persona.d.ts +8 -12
  175. package/dist/core/runtime/persona.js +24 -96
  176. package/dist/core/runtime/promote.d.ts +2 -2
  177. package/dist/core/runtime/promote.js +8 -21
  178. package/dist/core/runtime/revive.js +20 -17
  179. package/dist/core/runtime/spawn.js +18 -7
  180. package/dist/core/runtime/tmux-bindings.js +2 -3
  181. package/dist/core/runtime/warm-pool.js +3 -4
  182. package/dist/core/scope.js +2 -0
  183. package/dist/core/self-update.d.ts +0 -2
  184. package/dist/core/self-update.js +2 -35
  185. package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
  186. package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
  187. package/dist/core/substrate/index.d.ts +2 -2
  188. package/dist/core/substrate/index.js +1 -1
  189. package/dist/core/substrate/injected-store.d.ts +43 -27
  190. package/dist/core/substrate/injected-store.js +208 -104
  191. package/dist/core/substrate/listings.d.ts +19 -12
  192. package/dist/core/substrate/listings.js +75 -52
  193. package/dist/core/substrate/on-read-node.d.ts +4 -7
  194. package/dist/core/substrate/on-read-node.js +6 -8
  195. package/dist/core/substrate/on-read.d.ts +22 -25
  196. package/dist/core/substrate/on-read.js +103 -147
  197. package/dist/core/substrate/render-node.d.ts +4 -7
  198. package/dist/core/substrate/render-node.js +5 -7
  199. package/dist/core/substrate/render.d.ts +21 -3
  200. package/dist/core/substrate/render.js +291 -223
  201. package/dist/core/substrate/schema.d.ts +1 -13
  202. package/dist/core/substrate/schema.js +5 -40
  203. package/dist/core/substrate/session-cache.d.ts +14 -4
  204. package/dist/core/substrate/session-cache.js +40 -22
  205. package/dist/core/substrate/surface-match.d.ts +9 -7
  206. package/dist/core/substrate/surface-match.js +26 -25
  207. package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
  208. package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
  209. package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
  210. package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
  211. package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
  212. package/dist/daemon/api/handlers/broker-ops.js +21 -16
  213. package/dist/daemon/api/handlers/memory.js +2 -0
  214. package/dist/daemon/crtrd.js +2 -0
  215. package/dist/daemon/human/finish.js +8 -1
  216. package/dist/daemon/manage.d.ts +0 -1
  217. package/dist/daemon/manage.js +7 -16
  218. package/dist/daemon/startup-policy.d.ts +1 -0
  219. package/dist/daemon/startup-policy.js +1 -0
  220. package/dist/migrations/001-surfaces-frontmatter.js +21 -109
  221. package/dist/migrations/002-profile-project-memory.js +1 -0
  222. package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
  223. package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
  224. package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
  225. package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
  226. package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
  227. package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
  228. package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
  229. package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
  230. package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
  231. package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
  232. package/dist/migrations/__tests__/activation.test.d.ts +1 -0
  233. package/dist/migrations/__tests__/activation.test.js +149 -0
  234. package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
  235. package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
  236. package/dist/migrations/activation.d.ts +16 -0
  237. package/dist/migrations/activation.js +78 -0
  238. package/dist/migrations/convergent.d.ts +14 -3
  239. package/dist/migrations/convergent.js +21 -10
  240. package/dist/migrations/corpus.d.ts +78 -0
  241. package/dist/migrations/corpus.js +497 -0
  242. package/dist/migrations/frontmatter-splice.d.ts +15 -0
  243. package/dist/migrations/frontmatter-splice.js +176 -0
  244. package/dist/migrations/registry.d.ts +6 -1
  245. package/dist/migrations/registry.js +7 -2
  246. package/dist/migrations/runner.d.ts +41 -0
  247. package/dist/migrations/runner.js +81 -0
  248. package/dist/migrations/types.d.ts +148 -9
  249. package/dist/migrations/types.js +9 -2
  250. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
  251. package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
  252. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
  253. package/dist/pi-extensions/canvas-context-intro.js +50 -46
  254. package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
  255. package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
  256. package/dist/pi-extensions/canvas-stophook.js +6 -13
  257. package/dist/shared/generated-context.d.ts +0 -3
  258. package/dist/shared/generated-context.js +0 -57
  259. package/dist/shared/tool-groups.js +2 -3
  260. package/package.json +5 -4
  261. package/runtime.lock.json +2 -2
  262. /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
  263. /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
  264. /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
  265. /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
  266. /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
  267. /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
  268. /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
  269. /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
  270. /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
  271. /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
  272. /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
  273. /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
  274. /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
  275. /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
  276. /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
  277. /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
  278. /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
  279. /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
  280. /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
  281. /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
  282. /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
  283. /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
  284. /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
  285. /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
  286. /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
@@ -6,18 +6,33 @@ class FakeSocket extends EventEmitter {
6
6
  pings = 0;
7
7
  terminated = 0;
8
8
  ping() { this.pings += 1; }
9
- terminate() { this.terminated += 1; }
9
+ terminate() {
10
+ this.terminated += 1;
11
+ this.emit('terminated');
12
+ }
10
13
  }
11
14
  function sleep(ms) {
12
15
  return new Promise((resolve) => setTimeout(resolve, ms));
13
16
  }
17
+ function waitForTermination(ws, timeoutMs = 1_000) {
18
+ return new Promise((resolve, reject) => {
19
+ const timeout = setTimeout(() => reject(new Error('heartbeat did not terminate the peer')), timeoutMs);
20
+ timeout.unref();
21
+ ws.once('terminated', () => {
22
+ clearTimeout(timeout);
23
+ resolve();
24
+ });
25
+ });
26
+ }
14
27
  test('a quiet attach puts nothing on the wire', async () => {
15
28
  // Regression: an unconditional 25s ping made a hosted guest's connection never
16
29
  // idle, so the sandbox never suspended and whole fleets billed for 15h awake.
17
30
  const ws = new FakeSocket();
18
- const heartbeat = startRemoteAttachHeartbeat(ws, 10, 15);
19
- await sleep(60);
20
- assert.equal(ws.pings, 1, 'only the first tick inside the quiet window pings');
31
+ // The first timer tick is deliberately after the quiet window, so this test
32
+ // observes the contract without depending on scheduler timing.
33
+ const heartbeat = startRemoteAttachHeartbeat(ws, 20, 15);
34
+ await sleep(40);
35
+ assert.equal(ws.pings, 0, 'a quiet attach sends no heartbeat after its quiet window');
21
36
  assert.equal(ws.terminated, 0, 'a quiet attach is left open, never terminated by the heartbeat');
22
37
  heartbeat.stop();
23
38
  });
@@ -35,8 +50,9 @@ test('relayed traffic arms the heartbeat and a pong keeps a relaying observer al
35
50
  test('an unresponsive peer is dropped only while the relay is active', async () => {
36
51
  const ws = new FakeSocket();
37
52
  const heartbeat = startRemoteAttachHeartbeat(ws, 5, 10_000);
53
+ const terminated = waitForTermination(ws);
38
54
  heartbeat.noteActivity();
39
- await sleep(16);
55
+ await terminated;
40
56
  assert.equal(ws.terminated, 1, 'two missed heartbeat intervals terminate the detached viewer');
41
57
  heartbeat.stop();
42
58
  });
@@ -1,8 +1,7 @@
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 { getNode, isWarmSpare, listNodes, recordPid, setPresence, subscriptionsOf, updateNode } from '../../../core/canvas/canvas.js';
5
- import { isSafeNodeId } from '../../../core/canvas/paths.js';
4
+ import { canonicalSessionFile, findNodeBySessionFile, getNode, isWarmSpare, listNodes, recordPid, setPresence, subscriptionsOf, updateNode } from '../../../core/canvas/canvas.js';
6
5
  import { editorLabel } from '../../../core/canvas/labels.js';
7
6
  import { assembleNodeSubject } from '../../../core/substrate/subject.js';
8
7
  import { withCanvasWrite } from '../../../core/canvas/db.js';
@@ -128,9 +127,9 @@ function extensionState(nodeId) {
128
127
  .filter((child) => child !== null && child.kind !== 'human')
129
128
  .sort((a, b) => (a.status === b.status ? 0 : a.status === 'active' ? -1 : b.status === 'active' ? 1 : 0))
130
129
  .map(extensionNode);
131
- const source = node.fork_from === undefined || node.fork_from === null || node.fork_from === '' || !isSafeNodeId(node.fork_from)
132
- ? null
133
- : getNode(node.fork_from);
130
+ const source = typeof node.fork_source_file === 'string'
131
+ ? findNodeBySessionFile(node.fork_source_file)
132
+ : null;
134
133
  const subject = assembleNodeSubject(nodeId);
135
134
  if (subject === null)
136
135
  throw notFound(`unknown node: ${nodeId}`, { received: nodeId });
@@ -146,7 +145,7 @@ function extensionState(nodeId) {
146
145
  };
147
146
  }
148
147
  function samePersona(a, b) {
149
- return a?.mode === b.mode && a?.lifecycle === b.lifecycle;
148
+ return a?.kind === b.kind && a?.mode === b.mode && a?.lifecycle === b.lifecycle;
150
149
  }
151
150
  function parseGeneratedNameBody(body) {
152
151
  const value = objectBody(body, 'broker generated-name');
@@ -169,6 +168,7 @@ function parsePersonaAckBody(body) {
169
168
  const parse = (key) => {
170
169
  const persona = value[key];
171
170
  if (typeof persona !== 'object' || persona === null || Array.isArray(persona)
171
+ || typeof persona['kind'] !== 'string'
172
172
  || (persona['mode'] !== 'base' && persona['mode'] !== 'orchestrator')
173
173
  || (persona['lifecycle'] !== 'terminal' && persona['lifecycle'] !== 'resident')) {
174
174
  throw usage(`${key} must be a persona state`);
@@ -180,6 +180,7 @@ function parsePersonaAckBody(body) {
180
180
  function handleSessionBound(ctx) {
181
181
  const nodeId = ctx.params['id'];
182
182
  const request = parseSessionBoundBody(ctx.body);
183
+ const sessionFile = request.sessionFile === null ? null : canonicalSessionFile(request.sessionFile);
183
184
  const result = withCanvasWrite(() => {
184
185
  const before = requireNode(nodeId);
185
186
  // The broker reports the markers present in Pi's current branch; crtrd owns
@@ -196,10 +197,10 @@ function handleSessionBound(ctx) {
196
197
  action = 'relaunch_root';
197
198
  }
198
199
  else {
199
- handleNewSession(nodeId, request.piSessionId, request.sessionFile);
200
+ handleNewSession(nodeId, request.piSessionId, sessionFile);
200
201
  }
201
202
  }
202
- else if (before.fork_from != null && before.pi_session_id == null && request.sessionFile === null) {
203
+ else if (before.fork_from != null && before.pi_session_id == null && sessionFile === null) {
203
204
  // Pi has not exposed complete writable coordinates for this ordinary fork.
204
205
  // Leave it unbound so recovery cannot fork provenance twice.
205
206
  action = 'shutdown';
@@ -210,7 +211,7 @@ function handleSessionBound(ctx) {
210
211
  clearYieldMessage(nodeId);
211
212
  updateNode(nodeId, {
212
213
  pi_session_id: request.piSessionId,
213
- pi_session_file: request.sessionFile,
214
+ pi_session_file: sessionFile,
214
215
  cycle_pending: false,
215
216
  });
216
217
  recordPid(nodeId, request.pid);
@@ -378,20 +379,24 @@ function handleGeneratedName(ctx) {
378
379
  });
379
380
  return { status: 200, body: result };
380
381
  }
381
- /** Ack after Pi accepted transition guidance. The expected transition prevents a
382
- * stale broker from acknowledging a later persona change it did not describe. */
382
+ /** Ack after the matching baseline or transition context is durable. The
383
+ * expected pair prevents a stale broker from acknowledging a later change. */
383
384
  function handlePersonaAck(ctx) {
384
385
  const nodeId = ctx.params['id'];
385
386
  const request = parsePersonaAckBody(ctx.body);
386
- withCanvasWrite(() => {
387
+ const result = withCanvasWrite(() => {
387
388
  const node = requireNode(nodeId);
388
- const current = { mode: node.mode, lifecycle: node.lifecycle };
389
- const acknowledged = node.persona_ack ?? current;
389
+ const current = { kind: node.kind, mode: node.mode, lifecycle: node.lifecycle };
390
+ // A pre-kind ack reads its kind as the node's current one, matching how the
391
+ // broker resolved the `from` it is acknowledging.
392
+ const ack = node.persona_ack;
393
+ const acknowledged = ack === undefined ? current : { kind: ack.kind ?? node.kind, mode: ack.mode, lifecycle: ack.lifecycle };
390
394
  if (!samePersona(current, request.to) || !samePersona(acknowledged, request.from))
391
- return;
395
+ return { applied: false };
392
396
  updateNode(nodeId, { persona_ack: request.to });
397
+ return { applied: true };
393
398
  });
394
- return { status: 204 };
399
+ return { status: 200, body: result };
395
400
  }
396
401
  export const brokerOperationRoutes = [
397
402
  { method: 'POST', pattern: '/v1/nodes/:id/broker/session-bound', handler: handleSessionBound },
@@ -39,6 +39,8 @@ function handleResolve(ctx) {
39
39
  name: doc.name,
40
40
  scope: doc.scope,
41
41
  path: doc.path,
42
+ store_root: doc.store.storeRoot,
43
+ physical_relative_path: doc.physicalRelativePath,
42
44
  ...(doc.plugin === undefined ? {} : { plugin: doc.plugin }),
43
45
  };
44
46
  return { status: 200, body };
@@ -75,6 +75,7 @@ import { migrateLegacyInboxIdentities } from '../core/feed/inbox-migrate.js';
75
75
  import { emitEvent } from '../core/events/emit.js';
76
76
  import { operationIdContext } from '../core/events/operation-id.js';
77
77
  import { bindDaemonEventSource } from '../core/events/source.js';
78
+ import { ensureOnDiskMigrations } from '../migrations/activation.js';
78
79
  // A broker DECLARES its owning canvas in argv at spawn (host.ts launch:
79
80
  // `broker-cli.js --canvas-home <home> --epoch <id> <nodeId>`), so ownership is
80
81
  // an exact match on this daemon's own canvas home — no path inference. An
@@ -350,6 +351,7 @@ export async function superviseTick(now = Date.now(), lifecycle = directTickLife
350
351
  * quiesces admitted work, removes its readiness mirror, and releases its exact
351
352
  * claim. */
352
353
  export async function runDaemon(opts = {}) {
354
+ await ensureOnDiskMigrations();
353
355
  const ownership = claimDaemonOwnership(randomBytes(16).toString('hex'));
354
356
  if (!ownership.claimed) {
355
357
  // A losing claim isn't always a singleton race: `own_identity_indeterminate`
@@ -282,7 +282,14 @@ export async function deliverTerminalResult(ticketId) {
282
282
  }
283
283
  if (failedTo.length > 0)
284
284
  throw new Error(`cancellation notice failed for ${failedTo.join(', ')}`);
285
- transition(bridgeNodeId, 'finish');
285
+ // Parking the bridge done is the cancellation's END STATE, not a step it
286
+ // owes: a bridge already done — parked by an earlier cancel replay, or by a
287
+ // close that finished it while the page was still pending — is already
288
+ // there, and `finish` is illegal from done. Re-running it would turn a
289
+ // recorded, delivered cancellation into delivery_failed and make every
290
+ // retry repeat that error.
291
+ if (node.status !== 'done')
292
+ transition(bridgeNodeId, 'finish');
286
293
  return true;
287
294
  }
288
295
  if (result.kind === 'page') {
@@ -47,7 +47,6 @@ export interface SpawnDaemonResult {
47
47
  /** PID of the already-running daemon, if it was already up. */
48
48
  existing_pid?: number;
49
49
  }
50
- export declare const DAEMON_VERIFY_WINDOW_MS = 20000;
51
50
  export interface DaemonWaitDeps {
52
51
  readPidfile?: () => number | null;
53
52
  isPidAlive?: (pid: number) => boolean;
@@ -14,6 +14,7 @@ import { hostExecPath } from '../core/runtime/branded-host.js';
14
14
  // Pulled from the lean db-free pidfile module (NOT crtrd.js, whose module graph
15
15
  // reaches openDb) so the CLI daemon front door stays canvas.db-free (plan B-0).
16
16
  import { isDaemonRunning, readPidfile, isPidAlive } from './pidfile.js';
17
+ import { DAEMON_STARTUP_WINDOW_MS } from './startup-policy.js';
17
18
  // ---------------------------------------------------------------------------
18
19
  // Daemon env sanitization
19
20
  // ---------------------------------------------------------------------------
@@ -204,18 +205,8 @@ export async function execDaemon() {
204
205
  const launchArgs = await resolveDaemonLaunch();
205
206
  return execve(executable, [executable, ...launchArgs, '--canvas-home', crtrHome()], sanitizedDaemonEnv());
206
207
  }
207
- // Daemon lifecycle transitions are bounded so callers never claim a state
208
- // change before the pidfile and process liveness agree.
209
- //
210
- // A cold boot right after `install-runtime` (empty module cache, fresh
211
- // generation, possible pending db migration) can take several seconds to reach
212
- // writePidfile(), so the window is wide enough to tolerate that slow-but-valid
213
- // cold start rather than report a false "did not become ready"; a process exit
214
- // (signal/non-zero code) still fails immediately regardless of this window.
215
- // Exported so `commands/api-client.ts` can align the CLI-side `CrtrClient`'s
216
- // cold-start `/healthz` poll window with this authoritative valid-startup bound
217
- // instead of the two windows drifting independently.
218
- export const DAEMON_VERIFY_WINDOW_MS = 20_000;
208
+ // Shutdown stays short even though startup may include corpus migration.
209
+ const DAEMON_SHUTDOWN_WINDOW_MS = 20_000;
219
210
  const DAEMON_VERIFY_POLL_MS = 10;
220
211
  function sleepMs(ms) {
221
212
  return new Promise((resolve) => {
@@ -235,7 +226,7 @@ function sleepMs(ms) {
235
226
  *
236
227
  * Returns null when the spawned pid owns the pidfile; returns a different live
237
228
  * daemon pid when startup lost the race to an already-running daemon. */
238
- export async function verifyDaemonStartup(pid, timeoutMs = DAEMON_VERIFY_WINDOW_MS, deps = {}) {
229
+ export async function verifyDaemonStartup(pid, timeoutMs = DAEMON_STARTUP_WINDOW_MS, deps = {}) {
239
230
  const read = deps.readPidfile ?? readPidfile;
240
231
  const alive = deps.isPidAlive ?? isPidAlive;
241
232
  const exited = deps.childExited;
@@ -265,7 +256,7 @@ export async function verifyDaemonStartup(pid, timeoutMs = DAEMON_VERIFY_WINDOW_
265
256
  /** Wait until a signaled daemon has exited and relinquished its pidfile. This
266
257
  * makes a following start safe to claim the singleton rather than racing its
267
258
  * previous owner. */
268
- export async function waitForDaemonExit(pid, timeoutMs = DAEMON_VERIFY_WINDOW_MS, deps = {}) {
259
+ export async function waitForDaemonExit(pid, timeoutMs = DAEMON_SHUTDOWN_WINDOW_MS, deps = {}) {
269
260
  const read = deps.readPidfile ?? readPidfile;
270
261
  const alive = deps.isPidAlive ?? isPidAlive;
271
262
  const sleep = deps.sleepMs ?? sleepMs;
@@ -298,7 +289,7 @@ const DAEMON_KILL_WINDOW_MS = 2_000;
298
289
  * waits on process death alone after a kill, never on pidfile release.
299
290
  *
300
291
  * Returns how the daemon went down, for a truthful report. */
301
- export async function stopDaemonProcess(pid, timeoutMs = DAEMON_VERIFY_WINDOW_MS, deps = {}) {
292
+ export async function stopDaemonProcess(pid, timeoutMs = DAEMON_SHUTDOWN_WINDOW_MS, deps = {}) {
302
293
  const alive = deps.isPidAlive ?? isPidAlive;
303
294
  const sleep = deps.sleepMs ?? sleepMs;
304
295
  const now = deps.now ?? Date.now;
@@ -377,7 +368,7 @@ export async function spawnDaemon() {
377
368
  exitState = { code, signal };
378
369
  });
379
370
  child.unref();
380
- const existingPid = await verifyDaemonStartup(pid, DAEMON_VERIFY_WINDOW_MS, {
371
+ const existingPid = await verifyDaemonStartup(pid, DAEMON_STARTUP_WINDOW_MS, {
381
372
  childExited: () => exitState,
382
373
  });
383
374
  if (existingPid !== null) {
@@ -0,0 +1 @@
1
+ export declare const DAEMON_STARTUP_WINDOW_MS: number;
@@ -0,0 +1 @@
1
+ export const DAEMON_STARTUP_WINDOW_MS = 5 * 60 * 1_000;
@@ -1,9 +1,7 @@
1
1
  // 001 — the surfaces cut's frontmatter codemod. Folds the four old routing
2
2
  // fields (`system-prompt-visibility`, `file-read-visibility`, `applies-to`,
3
- // `read-when`) into explicit `surfaces` entries, bakes INDEX boot ceilings into
4
- // each doc's boot rung (per store), and points bare-directory `[[refs]]` at
5
- // their `[[dir/INDEX]]` docs (a bare-dir ref used to deliver the INDEX body;
6
- // after the cut it returns the directory listing).
3
+ // `read-when`) into explicit `surfaces` entries and bakes INDEX boot ceilings
4
+ // into each doc's boot rung (per store).
7
5
  //
8
6
  // Only docs with a valid substrate `kind` are touched. The old-schema parsing
9
7
  // and ceiling walk are FROZEN COPIES of the pre-cut substrate code: the live
@@ -13,14 +11,13 @@
13
11
  // Serialization is a textual splice: the dead keys' exact line spans are cut
14
12
  // from the raw frontmatter and a rendered `surfaces:` block is appended, so
15
13
  // every other byte survives verbatim and a migrated doc's diff shows only the
16
- // dead fields dying, `surfaces` appearing, and rule-5 ref rewrites — never
17
- // unrelated reformatting (re-serializing through the Document API would unfold
18
- // folded scalars). Each splice is validated by re-parsing: the result must
14
+ // dead fields dying and `surfaces` appearing — never unrelated reformatting
15
+ // (re-serializing through the Document API would unfold folded scalars). Each splice is validated by re-parsing: the result must
19
16
  // equal the original record minus the dead fields plus `surfaces`, or the
20
17
  // migration throws naming the doc.
21
- import { isMap, parse as parseYaml, parseDocument, stringify } from 'yaml';
18
+ import { parse as parseYaml } from 'yaml';
22
19
  import { parseFrontmatterGeneric } from '../core/frontmatter.js';
23
- import { findDocLinks } from '../core/memory/doc-link-grammar.js';
20
+ import { appendRenderedKey, dropTopLevelKeys, proveFrontmatterRoundTrip, rebuildDocSource, } from './frontmatter-splice.js';
24
21
  const OLD_FIELDS = ['system-prompt-visibility', 'file-read-visibility', 'applies-to', 'read-when'];
25
22
  // ---------------------------------------------------------------------------
26
23
  // Frozen old-schema parsing (pre-cut substrate/schema.ts semantics).
@@ -133,122 +130,37 @@ function planEntries(d, bootRung) {
133
130
  return entries;
134
131
  }
135
132
  // ---------------------------------------------------------------------------
136
- // Rule 5: bare-dir `[[ref]]` → `[[dir/INDEX]]`. A ref is rewritten only when
137
- // the owning target corpus proves the old bare-directory convenience and the
138
- // explicit INDEX spelling resolve to the same file. The decision cache belongs
139
- // to one migration target; profile-local names never leak across stores.
133
+ // Serialization: cut the dead keys' exact line spans out of the raw
134
+ // frontmatter text, append the rendered `surfaces:` block, re-parse to prove
135
+ // the splice is meaning-preserving.
140
136
  // ---------------------------------------------------------------------------
141
- function rewriteBareDirRefs(body, context, decisions) {
142
- const links = findDocLinks(body);
143
- if (links.length === 0)
144
- return body;
145
- let out = '';
146
- let pos = 0;
147
- for (const link of links) {
148
- let repl = decisions.get(link.name);
149
- if (repl === undefined) {
150
- repl = context?.legacyDirectoryIndex(link.name) ?? null;
151
- decisions.set(link.name, repl);
152
- }
153
- if (repl === null)
154
- continue;
155
- out += body.slice(pos, link.start) + `[[${repl}]]`;
156
- pos = link.end;
157
- }
158
- return out + body.slice(pos);
159
- }
160
- function deepEqual(a, b) {
161
- if (a === b)
162
- return true;
163
- if (Array.isArray(a) || Array.isArray(b)) {
164
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
165
- return false;
166
- return a.every((v, i) => deepEqual(v, b[i]));
167
- }
168
- if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') {
169
- const ak = Object.keys(a);
170
- const bk = Object.keys(b);
171
- if (ak.length !== bk.length)
172
- return false;
173
- return ak.every((k) => deepEqual(a[k], b[k]));
174
- }
175
- return false;
176
- }
177
137
  function migratedYaml(rawYaml, entries, docPath) {
178
- const ydoc = parseDocument(rawYaml);
179
- if (ydoc.errors.length > 0 || !isMap(ydoc.contents)) {
180
- throw new Error(`surfaces migration: unparseable frontmatter in ${docPath}`);
181
- }
182
- // Whole-line spans of the dead top-level keys (multi-line values included:
183
- // the value's node-end bounds its last continuation line).
184
- const spans = [];
185
- for (const item of ydoc.contents.items) {
186
- if (!OLD_FIELDS.includes(String(item.key)))
187
- continue;
188
- const keyRange = item.key.range;
189
- const valueRange = item.value?.range;
190
- if (keyRange === undefined)
191
- throw new Error(`surfaces migration: rangeless key in ${docPath}`);
192
- const start = rawYaml.lastIndexOf('\n', keyRange[0] - 1) + 1;
193
- let end = valueRange?.[2] ?? keyRange[2];
194
- // Consume through end-of-line unless a block value already owns its newline.
195
- if (!(end > 0 && rawYaml[end - 1] === '\n')) {
196
- while (end < rawYaml.length && (rawYaml[end] === ' ' || rawYaml[end] === '\t' || rawYaml[end] === '\r'))
197
- end++;
198
- if (rawYaml[end] === '\n')
199
- end++;
200
- }
201
- spans.push([start, end]);
202
- }
203
- spans.sort((a, b) => b[0] - a[0]);
204
- let out = rawYaml;
205
- for (const [s, e] of spans)
206
- out = out.slice(0, s) + out.slice(e);
207
- if (entries.length > 0) {
208
- // lineWidth 0: render each entry's scalars on one line, never folded.
209
- const block = stringify({ surfaces: entries }, { lineWidth: 0 });
210
- out = out === '' ? block : (out.endsWith('\n') ? out : out + '\n') + block;
211
- }
212
- out = out.replace(/\r?\n$/, '');
138
+ let out = dropTopLevelKeys(rawYaml, OLD_FIELDS, docPath);
139
+ if (entries.length > 0)
140
+ out = appendRenderedKey(out, 'surfaces', entries, docPath);
141
+ else
142
+ out = out.replace(/\r?\n$/, '');
213
143
  const expected = { ...parseYaml(rawYaml) };
214
144
  for (const k of OLD_FIELDS)
215
145
  delete expected[k];
216
146
  if (entries.length > 0)
217
147
  expected['surfaces'] = entries;
218
- const actual = parseYaml(out === '' ? '{}' : out);
219
- if (!deepEqual(actual ?? {}, expected)) {
220
- throw new Error(`surfaces migration: spliced frontmatter does not round-trip in ${docPath}`);
221
- }
148
+ proveFrontmatterRoundTrip(out, expected, docPath);
222
149
  return out;
223
150
  }
224
- function rebuildSource(d, newYaml, newBody) {
225
- const { doc } = d;
226
- const bodyStart = doc.source.length - doc.body.length;
227
- const fmBlock = doc.source.slice(0, bodyStart);
228
- if (newYaml === null)
229
- return fmBlock + newBody;
230
- const { raw } = parseFrontmatterGeneric(doc.source);
231
- const at = fmBlock.indexOf(raw);
232
- if (raw === '' || at < 0)
233
- throw new Error(`surfaces migration: cannot locate frontmatter block in ${doc.path}`);
234
- return fmBlock.slice(0, at) + newYaml + fmBlock.slice(at + raw.length) + newBody;
235
- }
236
151
  export const surfacesFrontmatterMigration = {
152
+ id: '001-surfaces-frontmatter',
237
153
  lane: 'convergent',
238
- description: 'surfaces frontmatter: fold visibility/applies-to/read-when routing into surfaces entries; rewrite bare-directory [[refs]] to [[dir/INDEX]]',
239
- apply(store, context) {
154
+ description: 'surfaces frontmatter: fold visibility/applies-to/read-when routing into surfaces entries',
155
+ apply(store) {
240
156
  const docs = store.docs.map(classify).filter((d) => d !== null);
241
157
  const ceiling = buildCeiling(docs);
242
- const decisions = new Map();
243
158
  const changes = [];
244
159
  for (const d of docs) {
245
- const newBody = rewriteBareDirRefs(d.doc.body, context, decisions);
246
- if (!d.hasOldFields && newBody === d.doc.body)
160
+ if (!d.hasOldFields)
247
161
  continue;
248
- const newYaml = d.hasOldFields
249
- ? migratedYaml(parseFrontmatterGeneric(d.doc.source).raw, planEntries(d, effectiveBootRung(d, ceiling)), d.doc.path)
250
- : null;
251
- changes.push({ path: d.doc.path, after: rebuildSource(d, newYaml, newBody) });
162
+ const newYaml = migratedYaml(parseFrontmatterGeneric(d.doc.source).raw, planEntries(d, effectiveBootRung(d, ceiling)), d.doc.path);
163
+ changes.push({ path: d.doc.path, after: rebuildDocSource(d.doc.source, newYaml, d.doc.path) });
252
164
  }
253
165
  return changes;
254
166
  },
@@ -32,6 +32,7 @@ function isMemory(value) {
32
32
  return typeof value === 'string' && PROFILE_PROJECT_MEMORY_VALUES.includes(value);
33
33
  }
34
34
  export const profileProjectMemoryMigration = {
35
+ id: '002-profile-project-memory',
35
36
  lane: 'profile-manifest',
36
37
  description: 'profile projects carry an explicit memory value',
37
38
  apply(input) {
@@ -0,0 +1,26 @@
1
+ import type { CorpusBlocker, CorpusSnapshot, FrontDoorAction } from '../types.js';
2
+ import type { RepositoryDecision, StorePrefixFacts } from './repository-facts.js';
3
+ export interface FrontDoorPlan {
4
+ repositoryRoot: string;
5
+ /** The repository-root store's key. */
6
+ storeKey: string;
7
+ rootDocPath: string;
8
+ rootAction: 'kept' | 'created' | 'completed';
9
+ /** The unique legacy front door whose workspace-open entries this run removes. It may live
10
+ * in a nested store of the same repository, so it carries its own store key. */
11
+ leafDocPath: string | null;
12
+ leafStoreKey: string | null;
13
+ /** The leaf's FINAL canonical name, for the created root body's `[[link]]`. */
14
+ leafCanonicalName: string | null;
15
+ action: FrontDoorAction;
16
+ }
17
+ export declare function planFrontDoors(repositories: readonly RepositoryDecision[], prefixes: ReadonlyMap<string, StorePrefixFacts>, corpus: CorpusSnapshot, finalNameOf: (realPath: string) => string | undefined): {
18
+ plans: FrontDoorPlan[];
19
+ blockers: CorpusBlocker[];
20
+ };
21
+ /** New repository-root INDEX.md source. `leafCanonicalName` null yields the generic minimal root body. */
22
+ export declare function rootIndexSource(namespace: string, leafCanonicalName: string | null): string;
23
+ /** Add the missing half or halves of the root front-door pair to a complete document source. */
24
+ export declare function completeRootFrontDoor(source: string, docPath: string): string;
25
+ /** Remove every workspace-open entry while preserving the body and every retained authored YAML byte. */
26
+ export declare function demoteLeafSurfaces(source: string, docPath: string): string;