@crouton-kit/crouter 0.3.28 → 0.3.30

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 (324) hide show
  1. package/dist/build-root.d.ts +15 -7
  2. package/dist/build-root.js +48 -34
  3. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  4. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  6. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  7. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  8. package/dist/builtin-memory/design.md +1 -1
  9. package/dist/builtin-memory/development.md +1 -1
  10. package/dist/builtin-memory/internal/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  12. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  13. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  14. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  15. package/dist/builtin-memory/planning.md +1 -1
  16. package/dist/builtin-memory/spec.md +6 -4
  17. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  18. package/dist/builtin-personas/runtime-base.md +5 -4
  19. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  20. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  21. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  22. package/dist/builtin-views/canvas/core.mjs +586 -0
  23. package/dist/builtin-views/canvas/text.mjs +58 -0
  24. package/dist/builtin-views/canvas/tui.mjs +165 -0
  25. package/dist/builtin-views/canvas/web.jsx +120 -0
  26. package/dist/builtin-views/git-pr/core.mjs +673 -0
  27. package/dist/builtin-views/git-pr/text.mjs +84 -0
  28. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  29. package/dist/builtin-views/git-pr/web.jsx +216 -0
  30. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  31. package/dist/builtin-views/inbox/core.mjs +1273 -0
  32. package/dist/builtin-views/inbox/text.mjs +73 -0
  33. package/dist/builtin-views/inbox/tui.mjs +312 -0
  34. package/dist/builtin-views/inbox/web.jsx +188 -0
  35. package/dist/builtin-views/linkedin/core.mjs +906 -0
  36. package/dist/builtin-views/linkedin/text.mjs +69 -0
  37. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  38. package/dist/builtin-views/linkedin/web.jsx +206 -0
  39. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  40. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  41. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  42. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  43. package/dist/cli.js +22 -17
  44. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  45. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  46. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  47. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  48. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  51. package/dist/clients/attach/attach-cmd.js +282 -46
  52. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  53. package/dist/clients/attach/auth-pickers.js +194 -0
  54. package/dist/clients/attach/canvas-panels.d.ts +4 -2
  55. package/dist/clients/attach/canvas-panels.js +45 -17
  56. package/dist/clients/attach/chat-view.d.ts +27 -2
  57. package/dist/clients/attach/chat-view.js +98 -9
  58. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  59. package/dist/clients/attach/clipboard-image.js +150 -26
  60. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  61. package/dist/clients/attach/clipboard-text.js +39 -0
  62. package/dist/clients/attach/config-load.d.ts +54 -7
  63. package/dist/clients/attach/config-load.js +99 -3
  64. package/dist/clients/attach/context-message.d.ts +12 -0
  65. package/dist/clients/attach/context-message.js +72 -0
  66. package/dist/clients/attach/git-info.d.ts +20 -0
  67. package/dist/clients/attach/git-info.js +44 -0
  68. package/dist/clients/attach/graph-overlay.d.ts +13 -4
  69. package/dist/clients/attach/graph-overlay.js +59 -19
  70. package/dist/clients/attach/input-controller.d.ts +75 -13
  71. package/dist/clients/attach/input-controller.js +305 -67
  72. package/dist/clients/attach/pickers.d.ts +58 -0
  73. package/dist/clients/attach/pickers.js +171 -0
  74. package/dist/clients/attach/slash-commands.d.ts +22 -0
  75. package/dist/clients/attach/slash-commands.js +91 -25
  76. package/dist/clients/attach/titled-editor.d.ts +33 -0
  77. package/dist/clients/attach/titled-editor.js +84 -0
  78. package/dist/clients/attach/view-socket.d.ts +18 -1
  79. package/dist/clients/attach/view-socket.js +70 -1
  80. package/dist/clients/web/dev-server.d.ts +7 -0
  81. package/dist/clients/web/dev-server.js +59 -0
  82. package/dist/clients/web/events.d.ts +14 -0
  83. package/dist/clients/web/events.js +151 -0
  84. package/dist/clients/web/server.d.ts +18 -0
  85. package/dist/clients/web/server.js +450 -0
  86. package/dist/clients/web/web-cmd.d.ts +2 -0
  87. package/dist/clients/web/web-cmd.js +120 -0
  88. package/dist/commands/canvas.js +1 -2
  89. package/dist/commands/chord.js +4 -25
  90. package/dist/commands/dashboard.js +5 -1
  91. package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
  92. package/dist/commands/memory/lint.js +14 -2
  93. package/dist/commands/memory/read.js +2 -2
  94. package/dist/commands/memory/shared.d.ts +7 -1
  95. package/dist/commands/memory/shared.js +18 -3
  96. package/dist/commands/memory/write.js +8 -4
  97. package/dist/commands/memory.js +4 -4
  98. package/dist/commands/node.d.ts +1 -1
  99. package/dist/commands/node.js +64 -65
  100. package/dist/commands/pkg.js +1 -2
  101. package/dist/commands/revive.js +11 -3
  102. package/dist/commands/sys/sync.d.ts +1 -0
  103. package/dist/commands/sys/sync.js +187 -0
  104. package/dist/commands/sys.js +3 -2
  105. package/dist/commands/view-cycle.js +2 -2
  106. package/dist/commands/view-list.js +8 -8
  107. package/dist/commands/view-new.js +21 -17
  108. package/dist/commands/view-pick.js +1 -1
  109. package/dist/commands/view-run.js +35 -14
  110. package/dist/commands/view.js +8 -6
  111. package/dist/commands/workspace.d.ts +2 -0
  112. package/dist/commands/workspace.js +161 -0
  113. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  114. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  115. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  116. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  117. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  118. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  119. package/dist/core/__tests__/close.test.js +14 -20
  120. package/dist/core/__tests__/context-intro.test.js +19 -19
  121. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  122. package/dist/core/__tests__/draw-style.test.js +1 -1
  123. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  124. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  125. package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
  126. package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
  127. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  128. package/dist/core/__tests__/focuses.test.js +25 -13
  129. package/dist/core/__tests__/fork.test.js +22 -5
  130. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  131. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  132. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  133. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  134. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  135. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  136. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  137. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  138. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  139. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  140. package/dist/core/__tests__/helpers/harness.js +19 -36
  141. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  142. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  143. package/dist/core/__tests__/lifecycle.test.js +1 -19
  144. package/dist/core/__tests__/listing-completeness.test.js +2 -2
  145. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  146. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  147. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
  148. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  149. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  150. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  151. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  152. package/dist/core/__tests__/reset.test.js +19 -79
  153. package/dist/core/__tests__/steer-note.test.js +9 -7
  154. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  155. package/dist/core/bootstrap.d.ts +0 -3
  156. package/dist/core/bootstrap.js +1 -143
  157. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  158. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  159. package/dist/core/canvas/browse/app.js +131 -8
  160. package/dist/core/canvas/browse/model.d.ts +23 -5
  161. package/dist/core/canvas/browse/model.js +68 -13
  162. package/dist/core/canvas/browse/render.d.ts +7 -1
  163. package/dist/core/canvas/browse/render.js +178 -47
  164. package/dist/core/canvas/canvas.js +1 -1
  165. package/dist/core/canvas/focuses.d.ts +4 -4
  166. package/dist/core/canvas/focuses.js +7 -7
  167. package/dist/core/canvas/nav-model.js +8 -7
  168. package/dist/core/canvas/render.d.ts +68 -9
  169. package/dist/core/canvas/render.js +195 -61
  170. package/dist/core/canvas/types.d.ts +16 -23
  171. package/dist/core/config.js +1 -4
  172. package/dist/core/memory-resolver.js +43 -5
  173. package/dist/core/runtime/bearings.d.ts +1 -1
  174. package/dist/core/runtime/bearings.js +8 -8
  175. package/dist/core/runtime/branded-host.d.ts +16 -0
  176. package/dist/core/runtime/branded-host.js +127 -0
  177. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  178. package/dist/core/runtime/broker.d.ts +4 -1
  179. package/dist/core/runtime/broker.js +494 -58
  180. package/dist/core/runtime/close.d.ts +12 -2
  181. package/dist/core/runtime/close.js +37 -16
  182. package/dist/core/runtime/front-door.js +6 -14
  183. package/dist/core/runtime/host.d.ts +14 -34
  184. package/dist/core/runtime/host.js +14 -50
  185. package/dist/core/runtime/launch.d.ts +12 -9
  186. package/dist/core/runtime/launch.js +27 -15
  187. package/dist/core/runtime/lifecycle.d.ts +1 -1
  188. package/dist/core/runtime/lifecycle.js +15 -19
  189. package/dist/core/runtime/nodes.d.ts +0 -55
  190. package/dist/core/runtime/nodes.js +6 -74
  191. package/dist/core/runtime/placement.d.ts +91 -335
  192. package/dist/core/runtime/placement.js +291 -858
  193. package/dist/core/runtime/promote.d.ts +2 -0
  194. package/dist/core/runtime/promote.js +24 -6
  195. package/dist/core/runtime/recap.d.ts +8 -0
  196. package/dist/core/runtime/recap.js +107 -0
  197. package/dist/core/runtime/recycle.js +25 -61
  198. package/dist/core/runtime/reset.d.ts +43 -37
  199. package/dist/core/runtime/reset.js +131 -218
  200. package/dist/core/runtime/revive.d.ts +9 -29
  201. package/dist/core/runtime/revive.js +40 -139
  202. package/dist/core/runtime/spawn.d.ts +15 -16
  203. package/dist/core/runtime/spawn.js +165 -194
  204. package/dist/core/runtime/surface-bg.d.ts +11 -0
  205. package/dist/core/runtime/surface-bg.js +68 -0
  206. package/dist/core/runtime/tmux.d.ts +15 -52
  207. package/dist/core/runtime/tmux.js +35 -99
  208. package/dist/core/scope.d.ts +1 -2
  209. package/dist/core/scope.js +1 -5
  210. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  211. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  212. package/dist/core/skill-sync/builtins.d.ts +42 -0
  213. package/dist/core/skill-sync/builtins.js +112 -0
  214. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  215. package/dist/core/skill-sync/claude-plugins.js +71 -0
  216. package/dist/core/skill-sync/engine.d.ts +42 -0
  217. package/dist/core/skill-sync/engine.js +633 -0
  218. package/dist/core/skill-sync/export.d.ts +23 -0
  219. package/dist/core/skill-sync/export.js +86 -0
  220. package/dist/core/skill-sync/manifest.d.ts +64 -0
  221. package/dist/core/skill-sync/manifest.js +181 -0
  222. package/dist/core/skill-sync/profile.d.ts +76 -0
  223. package/dist/core/skill-sync/profile.js +173 -0
  224. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  225. package/dist/core/skill-sync/snapshot.js +120 -0
  226. package/dist/core/substrate/index.d.ts +1 -1
  227. package/dist/core/substrate/index.js +1 -1
  228. package/dist/core/substrate/injected-store.js +3 -3
  229. package/dist/core/substrate/on-read.js +68 -6
  230. package/dist/core/substrate/render.d.ts +8 -11
  231. package/dist/core/substrate/render.js +29 -43
  232. package/dist/core/substrate/schema.d.ts +10 -3
  233. package/dist/core/substrate/schema.js +6 -3
  234. package/dist/core/tui/host.d.ts +12 -4
  235. package/dist/core/tui/host.js +280 -149
  236. package/dist/core/view/bridge.d.ts +10 -0
  237. package/dist/core/view/bridge.js +31 -0
  238. package/dist/core/view/chrome.d.ts +9 -0
  239. package/dist/core/view/chrome.js +22 -0
  240. package/dist/core/view/contract.d.ts +171 -0
  241. package/dist/core/view/contract.js +23 -0
  242. package/dist/core/view/loader.d.ts +31 -0
  243. package/dist/core/view/loader.js +188 -0
  244. package/dist/core/view/transport-local.d.ts +7 -0
  245. package/dist/core/view/transport-local.js +70 -0
  246. package/dist/core/view/transport.d.ts +4 -0
  247. package/dist/core/view/transport.js +15 -0
  248. package/dist/daemon/crtrd.d.ts +38 -27
  249. package/dist/daemon/crtrd.js +303 -376
  250. package/dist/daemon/manage.js +6 -1
  251. package/dist/index.js +1 -1
  252. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  253. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  254. package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
  255. package/dist/pi-extensions/canvas-nav.js +5 -0
  256. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  257. package/dist/pi-extensions/canvas-recap.js +236 -0
  258. package/dist/pi-extensions/canvas-resume.js +2 -1
  259. package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
  260. package/dist/pi-extensions/canvas-stophook.js +87 -101
  261. package/dist/pi-extensions/canvas-view.js +2 -1
  262. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  263. package/dist/pi-extensions/widget-order-bus.js +34 -0
  264. package/dist/prompts/view.d.ts +2 -2
  265. package/dist/prompts/view.js +145 -73
  266. package/dist/types.d.ts +1 -6
  267. package/dist/types.js +1 -3
  268. package/dist/web/ViewChrome.d.ts +7 -0
  269. package/dist/web/ViewChrome.js +28 -0
  270. package/dist/web/ViewPane.d.ts +39 -0
  271. package/dist/web/ViewPane.js +48 -0
  272. package/dist/web/index.d.ts +6 -0
  273. package/dist/web/index.js +16 -0
  274. package/dist/web/runtime.d.ts +39 -0
  275. package/dist/web/runtime.js +133 -0
  276. package/dist/web/states.d.ts +24 -0
  277. package/dist/web/states.js +24 -0
  278. package/dist/web/transport-http.d.ts +5 -0
  279. package/dist/web/transport-http.js +28 -0
  280. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  281. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  282. package/dist/web-client/index.html +13 -0
  283. package/package.json +20 -6
  284. package/dist/builtin-views/canvas/client.mjs +0 -303
  285. package/dist/builtin-views/canvas/view.mjs +0 -576
  286. package/dist/builtin-views/git-pr/client.mjs +0 -440
  287. package/dist/builtin-views/git-pr/view.mjs +0 -675
  288. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  289. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  290. package/dist/builtin-views/inbox/view.mjs +0 -889
  291. package/dist/builtin-views/linkedin/client.mjs +0 -610
  292. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  293. package/dist/commands/pkg/bridge.d.ts +0 -1
  294. package/dist/commands/pkg/bridge.js +0 -137
  295. package/dist/commands/skill/author.d.ts +0 -3
  296. package/dist/commands/skill/author.js +0 -140
  297. package/dist/commands/skill/shared.d.ts +0 -3
  298. package/dist/commands/skill/shared.js +0 -19
  299. package/dist/commands/skill.d.ts +0 -2
  300. package/dist/commands/skill.js +0 -21
  301. package/dist/commands/tmux-spread.d.ts +0 -2
  302. package/dist/commands/tmux-spread.js +0 -144
  303. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  304. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  305. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  306. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  307. package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
  308. package/dist/core/__tests__/home-session.test.js +0 -186
  309. package/dist/core/__tests__/relaunch.test.js +0 -335
  310. package/dist/core/bridge-map.d.ts +0 -19
  311. package/dist/core/bridge-map.js +0 -73
  312. package/dist/core/tui/contract.d.ts +0 -83
  313. package/dist/core/tui/contract.js +0 -8
  314. package/dist/core/tui/loader.d.ts +0 -16
  315. package/dist/core/tui/loader.js +0 -94
  316. package/dist/prompts/skill.d.ts +0 -2
  317. package/dist/prompts/skill.js +0 -650
  318. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  319. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  320. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  321. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  322. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  323. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
  324. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
@@ -1,46 +1,26 @@
1
1
  // Run with: node --import tsx/esm --test src/core/__tests__/child-death-wake.test.ts
2
2
  //
3
- // HEADLESS RETARGET (foundation-spec §C.7 + §E). D-1 BUG REGRESSION "the
4
- // runtime wakes a dormant parent on EVERY terminal child outcome, and ONLY on a
5
- // genuine one." Drives the REAL daemon decision pass (superviseTick) + the REAL
6
- // closeNode against canvas rows fabricated DIRECTLY in an isolated home — NO real
7
- // tmux session, NO remain-on-exit pane, NO broker boot.
3
+ // DOCTRINE WAKE "a parent that delegated and went dormant is still woken when
4
+ // its child reaches a genuine terminal outcome, and is NEVER spuriously woken on
5
+ // healthy dormancy." Drives the REAL daemon decision pass (superviseTick) + the
6
+ // REAL closeNode against canvas rows fabricated DIRECTLY in an isolated home — NO
7
+ // real tmux session, NO broker boot.
8
8
  //
9
- // (1) BUG LOCKED the D-1 finding (context/d1-finding.md): a parent that
10
- // delegated and then JUST STOPPED (inbox-wait retained, no self-wake armed)
11
- // was woken ONLY when a child `push final`d or never booted. A child that
12
- // CRASHED after booting, was daemon-FINALIZED on a quiet turn, or was `node
13
- // close`d marked the child dead/done/canceled with NO push to the parent — so
14
- // a purely-inbox-waiting parent hung dormant forever. The fix fans a system
15
- // inbox entry to subscribersOf(child) on those three previously-silent
16
- // terminal outcomes (surfaceChildDeath crash/finalize + close.ts), mirroring
17
- // surfaceBootFailure. THE CRUX: it must fire ONLY on genuine death NEVER on
18
- // healthy dormancy (a child that ended its turn still awaiting a LIVE
19
- // grandchild, or holding a pending self-wake, is alive-and-dormant; waking its
20
- // parent then re-creates the spurious-wake storm the doctrine exists to kill).
21
- //
22
- // (2) WHY MODEL-LEVEL, NOT TMUX CHROME — the wake travels through ONE pure
23
- // data-layer channel the d1-finding identified: a system entry appended to
24
- // the parent's inbox.jsonl (push() → appendInbox, no tmux). The death-vs-
25
- // dormancy boundary is pure daemon logic — finishedTurn (isBusy) +
26
- // hasActiveLiveSubscription || hasPendingSelfWake, the SAME boundary the
27
- // stop-guard draws (crtrd.ts). The daemon reaches the pane-gone routing for
28
- // ANY node whose pane is not alive; a row carrying a bogus pane id reads
29
- // "gone" (paneExists echoes empty for an unknown/absent pane — and with no
30
- // tmux server at all, the probe simply fails to false) with NO live session
31
- // to stand up. So the exact "booted child, pane gone, branch on what it was
32
- // doing" state reproduces with no real pane, and the parent stays an inline
33
- // node (no placement → the supervise loop's inline-root carve-out skips it),
34
- // so nothing in the test opens or needs a window.
35
- //
36
- // (3) HOW THE FABRICATED DRIVE STILL FAILS IF THE BUG REGRESSES — each direction
37
- // asserts the inbox channel: a genuine terminal outcome (crash/finalize/close)
38
- // MUST land a system entry from the child in the parent's inbox; healthy
39
- // dormancy MUST leave it empty. Drop the crash-branch surfaceChildDeath call
40
- // and the CRASH assertion goes RED (no entry); drop the stillWaiting guard and
41
- // a healthy-dormant child finalizes → the CRUX asserts go RED (a spurious
42
- // entry appears). Verified by reverting the crash-branch surfaceChildDeath
43
- // call (see bug-injection report).
9
+ // BROKER CUT (this worktree): the daemon's OLD parent-wake mechanism is GONE.
10
+ // U7 deleted surfaceChildDeath and the entire pane-gone reaping block (the
11
+ // crash/finalize/release routing that used to mark a pane-gone child dead/done/
12
+ // idle and fan a system inbox entry to its subscribers). Liveness is now PID-ONLY
13
+ // a viewer pane/window closing is NOT a node death — so a booted child whose
14
+ // engine pid is dead is REVIVED on its saved session, not surfaced as dead. The
15
+ // doctrine wake therefore RELOCATED off the daemon:
16
+ // child finishes `crtr push final` wakes subscribers via the PUSH itself;
17
+ // child crashes daemon grace-REVIVES it (it comes back and pushes later);
18
+ // child never boots surfaceBootFailure still pushes urgent (daemon-boot.test);
19
+ // child `node close`d close.ts fans the child-closed wake (tested below).
20
+ // So the daemon now fans NO liveness wake at all, which makes the old CRUX
21
+ // (healthy dormancy must not wake the parent) hold by construction. This file
22
+ // locks in (a) the new pid-only non-reaping + no-daemon-wake contract, and (b)
23
+ // the one genuine-terminal wake that still lives in core: close.ts.
44
24
  import { test, before, after, beforeEach } from 'node:test';
45
25
  import assert from 'node:assert/strict';
46
26
  import { mkdtempSync, rmSync } from 'node:fs';
@@ -48,10 +28,8 @@ import { tmpdir } from 'node:os';
48
28
  import { join } from 'node:path';
49
29
  import { spawnSync } from 'node:child_process';
50
30
  import { createNode, getNode, subscribe } from '../canvas/canvas.js';
51
- import { armWake } from '../canvas/wakeups.js';
52
31
  import { closeDb } from '../canvas/db.js';
53
32
  import { readInboxSince } from '../feed/inbox.js';
54
- import { markBusy, clearBusy } from '../runtime/busy.js';
55
33
  import { closeNode } from '../runtime/close.js';
56
34
  import { superviseTick } from '../../daemon/crtrd.js';
57
35
  let home;
@@ -73,16 +51,14 @@ function deadPid() {
73
51
  const r = spawnSync('true', [], { stdio: 'ignore' });
74
52
  return r.pid ?? 0x7ffffffe;
75
53
  }
76
- // A pane id no tmux server knows: paneExists echoes empty for it isNodePaneAlive
77
- // is false the supervise loop routes the row down its pane-gone branch with NO
78
- // live session required (and with no server at all the probe fails to false too).
54
+ // A pane id no tmux server knows. In the broker cut the daemon IGNORES pane state
55
+ // entirely (liveness is pid-only) this just stands in for "the node once had a
56
+ // viewer pane that is now gone," which must NOT count as a death.
79
57
  const GONE_PANE = '%999999';
80
- /** A booted child whose pane is GONE the daemon's pane-gone routing input.
81
- * `intent:null` so it takes the user-close branch (neither refresh nor
82
- * idle-release); pi dead so the zombie-kill is skipped; pi_session_id set so it
83
- * is "booted" (not the never-booted boot-failure leg). The caller layers on the
84
- * busy marker / subscriptions that decide crash vs finalize vs release. */
85
- function paneGoneChild(id, over = {}) {
58
+ /** A booted child whose engine pid is DEAD and whose viewer pane is gone the
59
+ * shape a crashed/finished child leaves behind. `pi_session_id` set so it is
60
+ * "booted"; `intent:null` so it takes the crash (grace-revive RESUME) branch. */
61
+ function deadEngineChild(id, over = {}) {
86
62
  createNode(node(id, {
87
63
  pane: GONE_PANE,
88
64
  tmux_session: 'crtr-cdw',
@@ -94,9 +70,8 @@ function paneGoneChild(id, over = {}) {
94
70
  ...over,
95
71
  }));
96
72
  }
97
- /** A dormant inbox-waiting PARENT — an INLINE node (no tmux placement), so the
98
- * supervise loop's inline-root carve-out skips it entirely: it is only the wake
99
- * TARGET, asserted via its inbox. */
73
+ /** A dormant inbox-waiting PARENT — only ever the wake TARGET, asserted via its
74
+ * inbox. */
100
75
  function inboxWaitingParent(id) {
101
76
  createNode(node(id, { status: 'active' }));
102
77
  }
@@ -115,32 +90,27 @@ after(() => {
115
90
  delete process.env['CRTR_HOME'];
116
91
  });
117
92
  // ===========================================================================
118
- // POSITIVEa genuine terminal child outcome WAKES the inbox-waiting parent.
93
+ // BROKER CUT pid-only liveness REPLACES daemon pane-gone reaping. A booted
94
+ // child whose engine pid is dead (and whose viewer pane is gone) is REVIVABLE,
95
+ // not reaped, and the daemon fans NO wake to the dormant parent. This single
96
+ // case subsumes the four deleted scenarios (crash / quiet-finalize / awaiting a
97
+ // live grandchild / pending self-wake): with pane state ignored and
98
+ // surfaceChildDeath gone, the daemon treats them all identically — grace-revive,
99
+ // no liveness wake — so the old CRUX (no spurious wake on healthy dormancy)
100
+ // holds by construction.
119
101
  // ===========================================================================
120
- test('CRASH after boot (mid-generation, pane gone) wakes the inbox-waiting parent', async () => {
102
+ test('pid-only liveness: a dead-engine booted child is revivable, NOT reaped, and the daemon fans NO wake to its dormant parent', async () => {
121
103
  inboxWaitingParent('PARENT');
122
- paneGoneChild('CHILD');
104
+ deadEngineChild('CHILD');
123
105
  subscribe('PARENT', 'CHILD', true); // the spawn-time spine edge
124
- markBusy('CHILD'); // pane died INSIDE a turn → genuine mid-run crash
125
- await superviseTick();
126
- assert.equal(getNode('CHILD').status, 'dead', 'CHILD crashed → dead');
127
- // THE WAKE CHANNEL: a system entry from CHILD lands in PARENT's inbox.
128
- const wake = wakeFromChild('PARENT', 'CHILD');
129
- assert.ok(wake, 'crash fanned a system inbox entry to the dormant parent (mirrors surfaceBootFailure)');
130
- assert.match(wake.label, /died/i, 'the entry tells the parent WHICH child died and how');
131
- clearBusy('CHILD');
132
- });
133
- test('quiet-turn FINALIZE (pane gone, nothing live to wait for) wakes the inbox-waiting parent', async () => {
134
- inboxWaitingParent('PARENT');
135
- // Booted, turn finished (NO busy marker), no live subscription, no pending
136
- // self-wake → the daemon finalizes it (done): a dismissal of a finished node.
137
- paneGoneChild('CHILD');
138
- subscribe('PARENT', 'CHILD', true);
139
106
  await superviseTick();
140
- assert.equal(getNode('CHILD').status, 'done', 'CHILD finalized done');
141
- const wake = wakeFromChild('PARENT', 'CHILD');
142
- assert.ok(wake, 'finalize fanned a system inbox entry to the dormant parent');
143
- assert.match(wake.label, /without a final report/i, 'the entry tells the parent the child ended with no final');
107
+ // A dead engine pid is REVIVABLE (grace-revive RESUME on the saved session),
108
+ // not reaped to dead/done/idle — a viewer pane closing is not a node death.
109
+ assert.equal(getNode('CHILD').status, 'active', 'CHILD stays revivable, NOT reaped');
110
+ // surfaceChildDeath is deleted: the daemon raises no liveness wake. The
111
+ // doctrine wake now rides the child's own `push final` (or close.ts), so a
112
+ // purely-inbox-waiting parent is never spuriously woken on dormancy/crash.
113
+ assert.equal(readInboxSince('PARENT').length, 0, 'PARENT inbox EMPTY — no daemon liveness wake');
144
114
  });
145
115
  test('node close of a child wakes its SURVIVING manager (the parent outside the closing set)', async () => {
146
116
  inboxWaitingParent('PARENT');
@@ -156,38 +126,13 @@ test('node close of a child wakes its SURVIVING manager (the parent outside the
156
126
  assert.ok(wake, 'node close fanned a child-closed entry to the surviving manager (D-1: previously none)');
157
127
  assert.match(wake.label, /closed/i, 'the entry names the closed child');
158
128
  });
159
- // ===========================================================================
160
- // NEGATIVE HEALTHY DORMANCY must NOT wake the parent (the correctness crux).
161
- // ===========================================================================
162
- test('CRUX: a child dormant while awaiting its OWN live grandchild does NOT wake the parent', async () => {
163
- inboxWaitingParent('PARENT');
164
- // CHILD finished its turn (no busy) and its pane is gone, BUT it actively
165
- // subscribes to a LIVE grandchild exactly the developer-spawned-reviewer
166
- // case. This is HEALTHY dormancy, not death.
167
- paneGoneChild('CHILD');
168
- createNode(node('GRANDCHILD', { status: 'active', pi_session_id: 'booted' })); // live, inline
169
- subscribe('PARENT', 'CHILD', true);
170
- subscribe('CHILD', 'GRANDCHILD', true); // CHILD awaits a LIVE grandchild
171
- await superviseTick();
172
- // CHILD is RELEASED (revivable), NOT finalized — the daemon must not orphan
173
- // its in-flight grandchild and must not wake the parent.
174
- assert.equal(getNode('CHILD').status, 'idle', 'CHILD released (revivable), NOT finalized');
175
- assert.equal(getNode('CHILD').intent, 'idle-release', 'CHILD routed to idle-release');
176
- assert.equal(readInboxSince('PARENT').length, 0, 'PARENT inbox EMPTY — healthy dormancy raises no wake');
177
- });
178
- test('CRUX: a child dormant on a pending self-wake (no live sub) does NOT wake the parent', async () => {
179
- inboxWaitingParent('PARENT');
180
- paneGoneChild('CHILD');
181
- subscribe('PARENT', 'CHILD', true);
182
- // CHILD has NO live subscription, but holds a pending self-wake (far-future so
183
- // the wakeups pass never fires it this tick). hasPendingSelfWake(CHILD) ⇒ the
184
- // daemon treats it as STILL WAITING (the stop-guard's boundary), so it RELEASES
185
- // instead of finalizing. NON-VACUOUS: drop the hasPendingSelfWake guard and
186
- // CHILD finalizes → PARENT is spuriously woken.
187
- const future = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();
188
- armWake({ wakeup_id: 'wk-cdw-child', node_id: 'CHILD', owner_id: 'CHILD', fire_at: future, kind: 'bare' });
189
- await superviseTick();
190
- assert.equal(getNode('CHILD').status, 'idle', 'CHILD with a pending self-wake is RELEASED, not finalized');
191
- assert.equal(getNode('CHILD').intent, 'idle-release', 'CHILD routed to idle-release (its clock will wake it)');
192
- assert.equal(readInboxSince('PARENT').length, 0, 'PARENT inbox EMPTY — a pending clock is healthy dormancy');
193
- });
129
+ // NOTE (broker cut): the two NEGATIVE "CRUX" tests that used to live here — a
130
+ // child dormant while awaiting a live grandchild, and a child dormant on a
131
+ // pending self-wake — asserted the daemon's pane-gone routing chose idle-release
132
+ // (status 'idle', intent 'idle-release') over finalize, so it would not spuriously
133
+ // wake the parent. U7 DELETED that routing entirely: pane state is ignored, the
134
+ // daemon never finalizes/releases on liveness, and surfaceChildDeath is gone, so
135
+ // the daemon raises no liveness wake at all. The no-spurious-wake invariant they
136
+ // protected now holds by construction and is covered by the pid-only test above
137
+ // (PARENT inbox stays empty). Removed rather than adapted because the
138
+ // idle-release-vs-finalize distinction they asserted no longer exists.
@@ -4,7 +4,6 @@ import { mkdtempSync, rmSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { createNode, getNode, subscribe } from '../canvas/canvas.js';
7
- import { openFocusRow, getFocusByNode } from '../canvas/focuses.js';
8
7
  import { closeDb } from '../canvas/db.js';
9
8
  import { closeNode } from '../runtime/close.js';
10
9
  import { readInboxSince } from '../feed/inbox.js';
@@ -44,7 +43,7 @@ test('closes the root and its exclusive descendants; spares a shared node', () =
44
43
  // N ─▶ A ─▶ C (A, C exclusive to the N subtree)
45
44
  // N ─▶ B ◀─ M (B also subscribed to by external manager M)
46
45
  for (const id of ['N', 'A', 'B', 'C', 'M'])
47
- createNode(node(id, { pane: `%${id.toLowerCase()}` }));
46
+ createNode(node(id));
48
47
  spawnEdge('N', 'A');
49
48
  spawnEdge('N', 'B');
50
49
  spawnEdge('A', 'C');
@@ -52,17 +51,15 @@ test('closes the root and its exclusive descendants; spares a shared node', () =
52
51
  const res = closeNode('N');
53
52
  assert.deepEqual([...res.closed].sort(), ['A', 'C', 'N']);
54
53
  assert.deepEqual(res.spared, ['B']);
55
- // Closed nodes → canceled, intent cleared, presence (incl. the pane) dropped.
54
+ // Closed nodes → canceled + intent cleared. (Broker-host cut: nodes carry NO
55
+ // engine pane/window/session — the viewer lives in the focuses table and is
56
+ // torn down asynchronously when the broker socket drops, so close no longer
57
+ // nulls a meta `pane`; viewer teardown is covered in the full-tier broker
58
+ // lifecycle suite.)
56
59
  for (const id of ['N', 'A', 'C']) {
57
60
  const m = getNode(id);
58
61
  assert.equal(m.status, 'canceled', `${id} canceled`);
59
62
  assert.equal(m.intent, null, `${id} intent cleared`);
60
- assert.equal(m.window ?? null, null, `${id} window cleared`);
61
- assert.equal(m.tmux_session ?? null, null, `${id} session cleared`);
62
- // Step 7: tearDownNode is pane-keyed and nulls the durable pane handle too.
63
- // Non-vacuous: each node was given a pane (`%n`/`%a`/`%c`) above, so an impl
64
- // that didn't route close through tearDownNode would leave it set.
65
- assert.equal(m.pane ?? null, null, `${id} pane cleared`);
66
63
  }
67
64
  // Spared node and the unrelated manager are untouched.
68
65
  assert.equal(getNode('B').status, 'active');
@@ -135,14 +132,11 @@ test('closing a leaf node closes only itself', () => {
135
132
  test('throws on an unknown node', () => {
136
133
  assert.throws(() => closeNode('ghost'), /unknown node/);
137
134
  });
138
- test('Step 7: closing a FOCUSED node closes its focus row + nulls its pane (tearDownNode)', () => {
139
- createNode(node('N', { pane: '%x' }));
140
- openFocusRow('fN', '%x', 'Sa', 'N');
141
- closeNode('N');
142
- // tearDownNode closes the focus row the node occupied and nulls its LOCATION.
143
- // Non-vacuous: pre-Step-7 close used closeWindow and never touched the focuses
144
- // table, so getFocusByNode('N') would still return fN.
145
- assert.equal(getFocusByNode('N'), null, 'focus row closed by tearDownNode');
146
- assert.equal(getNode('N').pane ?? null, null, 'pane nulled (pane-keyed teardown)');
147
- assert.equal(getNode('N').status, 'canceled', 'node canceled as before');
148
- });
135
+ // NOTE (broker-host cut): the former "Step 7: closing a FOCUSED node closes its
136
+ // focus row + nulls its pane (tearDownNode)" test was DELETED. close.ts no longer
137
+ // routes through the synchronous tearDownNode — it sends the broker `shutdown`
138
+ // frame (headlessBrokerHost.teardown) and the viewer pane/focus row close on
139
+ // their own when the broker socket drops (GC'd by the daemon + focusOf prune).
140
+ // That async viewer teardown is covered by the full-tier broker-crash-teardown /
141
+ // broker-lifecycle suite, which runs a real broker; it cannot be unit-tested
142
+ // without one.
@@ -1,6 +1,6 @@
1
1
  // Tests for the <crtr-context> bearings preamble:
2
- // 1. Worker and orchestrator bearings carry the `<references>` block
3
- // (substrate reference docs + node-local docs as a file tree).
2
+ // 1. Worker and orchestrator bearings carry the `<knowledge>` block
3
+ // (substrate knowledge docs + node-local docs as a file tree).
4
4
  // Orchestrators add the across-cycles framing; promotion delivers that
5
5
  // same orchestrator context-dir framing to a node that spawned base.
6
6
  // 2. canvas-context-intro injects the block as its own session message at
@@ -34,27 +34,27 @@ after(() => {
34
34
  delete process.env['CRTR_HOME'];
35
35
  delete process.env['CRTR_NODE_ID'];
36
36
  });
37
- test('worker bearings: base framing + <references> block, NO across-cycles framing', () => {
37
+ test('worker bearings: base framing + <knowledge> block, NO across-cycles framing', () => {
38
38
  // Bug-regression: review finding M1 — buildContextBearings renders the
39
- // <references> block (renderReferencesBlock): a kind-wrapped file tree of
40
- // reference docs. This test locks in that contract.
39
+ // <knowledge> block (renderKnowledgeBlock): a kind-wrapped file tree of
40
+ // knowledge docs. This test locks in that contract.
41
41
  const meta = spawnNode({ kind: 'general', cwd: '/tmp/work', parent: null });
42
- // Seed a node-local substrate doc so the ## References block is non-empty.
42
+ // Seed a node-local substrate doc so the knowledge block is non-empty.
43
43
  const dir = memoryDir(meta.node_id);
44
44
  mkdirSync(dir, { recursive: true });
45
- writeFileSync(join(dir, 'test-ref.md'), '---\nkind: reference\nwhen-and-why-to-read: When testing, this reference should be read because it is a regression fixture\nsystem-prompt-visibility: preview\n---\nTest body.\n');
45
+ writeFileSync(join(dir, 'test-ref.md'), '---\nkind: knowledge\nwhen-and-why-to-read: When testing, this reference should be read because it is a regression fixture\nsystem-prompt-visibility: preview\n---\nTest body.\n');
46
46
  const block = buildContextIntro(meta.node_id);
47
47
  assert.match(block, new RegExp(`<crtr-context dir="${contextDir(meta.node_id)}">`));
48
48
  assert.match(block, /shared document store, not a task tracker/, 'base = shared docs, not tasks');
49
- // Reference content renders ONLY as the <references> file-tree block.
49
+ // Knowledge content renders ONLY as the <knowledge> file-tree block.
50
50
  assert.doesNotMatch(block, /<memory>/, 'no <memory> block');
51
51
  // Per-store stanza headers (label · dir) never appear.
52
52
  assert.doesNotMatch(block, /user-global · /, 'no user-global label·dir stanza');
53
53
  assert.doesNotMatch(block, /node-local · /, 'no node-local label·dir stanza');
54
54
  // No (empty) placeholder marker.
55
55
  assert.doesNotMatch(block, /\(empty\)/, 'no (empty) placeholder');
56
- assert.match(block, /<references>/, '<references> block present');
57
- assert.match(block, /\nreferences\n/, 'tree headed by the `references` root label');
56
+ assert.match(block, /<knowledge>/, '<knowledge> block present');
57
+ assert.match(block, /\nknowledge\n/, 'tree headed by the `knowledge` root label');
58
58
  // The node-local preview-rung doc renders as a tree entry with a `# read when:`
59
59
  // routing line (verbatim previewLine output).
60
60
  assert.match(block, /test-ref {2}# read when: When testing, this reference should be read because it is a regression fixture\./, 'node-local preview doc renders its routing line');
@@ -62,8 +62,8 @@ test('worker bearings: base framing + <references> block, NO across-cycles frami
62
62
  assert.doesNotMatch(block, /refresh cycles/, 'no across-cycles framing for a terminal worker');
63
63
  assert.match(block, /<\/crtr-context>/);
64
64
  });
65
- test('orchestrator bearings: across-cycles framing + node-local substrate docs ride into <references>; a non-substrate .md file is never inlined', () => {
66
- // Bug-regression: review finding M1 — buildContextBearings renders <references>.
65
+ test('orchestrator bearings: across-cycles framing + node-local substrate docs ride into <knowledge>; a non-substrate .md file is never inlined', () => {
66
+ // Bug-regression: review finding M1 — buildContextBearings renders <knowledge>.
67
67
  // Node-local substrate docs render as tree entries at their rung; a
68
68
  // non-substrate .md file (no frontmatter `kind`) never surfaces.
69
69
  const meta = spawnNode({ kind: 'general', cwd: '/tmp/work', parent: null });
@@ -73,13 +73,13 @@ test('orchestrator bearings: across-cycles framing + node-local substrate docs r
73
73
  // Write a non-substrate MEMORY.md file to verify it is never surfaced in the block.
74
74
  const legacyIndexPath = join(dir, 'MEMORY.md');
75
75
  writeFileSync(legacyIndexPath, '# memory index — one pointer line per memory; how-to in "Your long-term memory".\n\n- [Flaky build](flaky-build.md) — first run fails\n');
76
- // A node-local substrate doc DOES ride into ## References at its rung.
77
- writeFileSync(join(dir, 'flaky-build.md'), '---\nkind: reference\nwhen-and-why-to-read: When the build flakes, this reference should be read because the first run fails\nsystem-prompt-visibility: preview\n---\nFirst run always fails; rerun once.\n');
76
+ // A node-local substrate doc DOES ride into the knowledge block at its rung.
77
+ writeFileSync(join(dir, 'flaky-build.md'), '---\nkind: knowledge\nwhen-and-why-to-read: When the build flakes, this reference should be read because the first run fails\nsystem-prompt-visibility: preview\n---\nFirst run always fails; rerun once.\n');
78
78
  const block = buildContextIntro(meta.node_id);
79
79
  assert.match(block, /shared document store, not a task tracker/, 'still carries the base framing');
80
80
  assert.match(block, /refresh cycles/, 'orchestrator gets the across-cycles framing');
81
81
  assert.doesNotMatch(block, /<memory>/, 'no <memory> block');
82
- assert.match(block, /<references>/, 'references block present');
82
+ assert.match(block, /<knowledge>/, 'knowledge block present');
83
83
  assert.match(block, /flaky-build {2}# read when: When the build flakes, this reference should be read because the first run fails\./, 'node-local doc renders as a tree entry with its routing line');
84
84
  // The non-substrate file never renders: no header line, no pointer line, no path.
85
85
  assert.ok(!block.includes('# memory index'), 'the index header comment is NOT inlined');
@@ -89,16 +89,16 @@ test('orchestrator bearings: across-cycles framing + node-local substrate docs r
89
89
  assert.match(block, /<\/crtr-context>/);
90
90
  });
91
91
  test('orchestrator bearings: no per-store stanzas or (empty) markers; a rung-none node-local doc still surfaces as a bare-name tree entry', () => {
92
- // Bug-regression: review findings M1 + M6 — the <references> block carries no
92
+ // Bug-regression: review findings M1 + M6 — the <knowledge> block carries no
93
93
  // per-store `label · dir` stanzas or (empty) markers, and node-local docs are
94
- // NOT filtered on rung: a migrated node-local reference defaults
95
- // system-prompt-visibility: none and must still ride into <references> as
94
+ // NOT filtered on rung: a migrated node-local doc defaults
95
+ // system-prompt-visibility: none and must still ride into <knowledge> as
96
96
  // its bare name (floored to the `name` rung; never its body).
97
97
  const meta = spawnNode({ kind: 'general', cwd: '/tmp/work', parent: null });
98
98
  promote(meta.node_id); // flip to orchestrator mode
99
99
  const dir = memoryDir(meta.node_id);
100
100
  mkdirSync(dir, { recursive: true });
101
- writeFileSync(join(dir, 'rung-none-fact.md'), '---\nkind: reference\n---\nbody that must not render at the none rung\n');
101
+ writeFileSync(join(dir, 'rung-none-fact.md'), '---\nkind: knowledge\n---\nbody that must not render at the none rung\n');
102
102
  const block = buildContextIntro(meta.node_id);
103
103
  // No per-store stanza headers, no (empty) markers, no MEMORY.md paths.
104
104
  assert.ok(!block.includes(`node-local · ${memoryDir(meta.node_id)}`), 'no node-local stanza header');
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
3
3
  import { mkdtempSync, rmSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
+ import { spawnSync } from 'node:child_process';
6
7
  import { createNode, getNode, subscribe } from '../canvas/canvas.js';
7
8
  import { closeDb } from '../canvas/db.js';
8
9
  import { readInboxSince } from '../feed/inbox.js';
@@ -27,6 +28,11 @@ function node(id, over = {}) {
27
28
  function spawnEdge(parent, child) {
28
29
  subscribe(parent, child, true);
29
30
  }
31
+ /** A pid that is guaranteed dead (models a crashed engine). */
32
+ function deadPid() {
33
+ const r = spawnSync('true', [], { stdio: 'ignore' });
34
+ return r.pid ?? 0x7ffffffe;
35
+ }
30
36
  before(() => {
31
37
  home = mkdtempSync(join(tmpdir(), 'crtr-daemon-boot-'));
32
38
  process.env['CRTR_HOME'] = home;
@@ -56,7 +62,13 @@ test('never-booted node is marked dead and surfaced to the parent as urgent', as
56
62
  intent: null,
57
63
  }));
58
64
  spawnEdge('P', 'C');
59
- await superviseTick();
65
+ // BROKER CUT: liveness is pid-only — the bogus window/session is irrelevant. A
66
+ // never-booted broker (pi_pid null AND pi_session_id null) is caught by the
67
+ // in-memory boot-grace clock, so the boot-failure fires on the SECOND tick,
68
+ // once REVIVE_GRACE_MS (~20s) of "still no pid, no session" has elapsed.
69
+ const t0 = Date.now();
70
+ await superviseTick(t0); // first observation: starts the boot-grace clock
71
+ await superviseTick(t0 + 60_000); // grace elapsed → crash + surfaceBootFailure
60
72
  assert.equal(getNode('C').status, 'dead', 'never-booted child is dead');
61
73
  const inbox = readInboxSince('P');
62
74
  assert.equal(inbox.length, 1, 'parent received exactly one pointer');
@@ -64,37 +76,32 @@ test('never-booted node is marked dead and surfaced to the parent as urgent', as
64
76
  assert.equal(inbox[0].from, 'C');
65
77
  assert.match(inbox[0].label, /Spawn failed/);
66
78
  });
67
- // A node that HAD booted (pi_session_id set) then died mid-generation is a
68
- // genuine crash, not a boot failure. It is marked dead AND wakes the inbox-
69
- // waiting parent but with a "child died" notice (surfaceChildDeath), NOT a
70
- // "Spawn failed" boot-failure pointer (surfaceBootFailure). The parent-wake
71
- // itself was added by main's b5abf6e (parent-wake on child death) and is locked
72
- // in by child-death-wake.test.ts; this test preserves the discriminating intent
73
- // a crash is never surfaced as a false spawn-failure alarm.
74
- test('a crash after boot wakes the parent with a child-death notice, not a boot-failure push', async () => {
79
+ // BROKER CUT: surfaceChildDeath was DELETED and liveness is pid-only. A crashed
80
+ // booted child (engine pid DEAD, pi_session_id set) is no longer marked `dead`
81
+ // and no longer fans a "child died" notice the daemon now grace-REVIVES it on
82
+ // its saved session, so the row stays active/revivable and the doctrine wake
83
+ // relocated (the revived child pushes when it truly finishes; a deliberate close
84
+ // is woken by close.ts). This test preserves the discriminating intent in the
85
+ // new model: a crash is never reaped to `dead` and never raises a false
86
+ // boot-failure alarm.
87
+ test('a crashed booted child is revivable, not reaped, and raises no false boot-failure alarm', async () => {
75
88
  createNode(node('P2', { kind: 'developer', lifecycle: 'resident' }));
76
89
  createNode(node('C2', {
77
90
  parent: 'P2',
78
91
  kind: 'explore',
79
- tmux_session: 'crtr-test-absent-session',
80
- window: '@999992',
92
+ pi_pid: deadPid(), // engine crashed — its recorded pid is dead
81
93
  pi_session_id: '019e8f00-booted-once', // it booted before dying
82
94
  intent: null,
83
95
  }));
84
96
  spawnEdge('P2', 'C2');
85
- // MID-GENERATION when the window vanished (busy marker present, agent_end
86
- // never cleared it) → a genuine mid-run crash. Without the marker a booted,
87
- // unsubscribed pane-gone node would FINALIZE to 'done' instead (it would read
88
- // as a finished node dismissed) — see the gone-pane routing in crtrd.ts.
89
- markBusy('C2');
97
+ markBusy('C2'); // died mid-generation
90
98
  await superviseTick();
91
- assert.equal(getNode('C2').status, 'dead', 'crashed child is dead');
92
- const inbox = readInboxSince('P2');
93
- assert.equal(inbox.length, 1, 'a genuine mid-run crash wakes the inbox-waiting parent (D-1 parent-wake)');
94
- assert.equal(inbox[0].tier, 'urgent', 'mid-run crash is delivered as urgent');
95
- assert.equal(inbox[0].from, 'C2');
96
- assert.match(inbox[0].label, /died/i, 'the wake is a child-death notice (surfaceChildDeath), not silence');
97
- assert.doesNotMatch(inbox[0].label, /Spawn failed/, 'a crash is NOT surfaced as a false boot-failure alarm');
99
+ // pid-only liveness: a dead engine pid is REVIVABLE (grace-revive RESUME on the
100
+ // saved session), not reaped to `dead`; the row stays active across the grace.
101
+ assert.equal(getNode('C2').status, 'active', 'crashed booted child stays revivable, NOT marked dead');
102
+ // surfaceChildDeath is gone and this is not a never-booted node, so the daemon
103
+ // fans NO push here — no false boot-failure / child-death alarm on a crash.
104
+ assert.equal(readInboxSince('P2').length, 0, 'no false boot-failure / child-death push on a crash (wake relocated to push/close)');
98
105
  clearBusy('C2');
99
106
  });
100
107
  // A still-booting node whose window is alive must be left untouched — boot is
@@ -35,7 +35,7 @@ function baseChrome(over = {}) {
35
35
  return { status: null, banner: null, busy: false, loaded: true, lastRefresh: Date.now(), tick: 0, subtitle: null, mode: null, ...over };
36
36
  }
37
37
  const MANIFEST = {
38
- id: 't', title: 'Test View', subtitle: '3 unread', description: 'd',
38
+ title: 'Test View', subtitle: '3 unread',
39
39
  keymap: [
40
40
  { keys: 'j/k', label: 'move' }, { keys: 'enter', label: 'open' },
41
41
  { keys: 'r', label: 'reply' }, { keys: 'g', label: 'refresh' }, { keys: 'q', label: 'quit' },
@@ -0,0 +1 @@
1
+ export {};