@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.
- package/dist/build-root.d.ts +15 -7
- package/dist/build-root.js +48 -34
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +5 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +22 -17
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +4 -25
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +8 -4
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/listing-completeness.test.js +2 -2
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.js +8 -7
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -335
- package/dist/core/runtime/placement.js +291 -858
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -52
- package/dist/core/runtime/tmux.js +35 -99
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +10 -3
- package/dist/core/substrate/schema.js +6 -3
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
- /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
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// closeNode against canvas rows fabricated DIRECTLY in an isolated home — NO
|
|
7
|
-
// tmux session, NO
|
|
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
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// (
|
|
23
|
-
//
|
|
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
|
|
77
|
-
//
|
|
78
|
-
//
|
|
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
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
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 —
|
|
98
|
-
*
|
|
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
|
-
//
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
assert.
|
|
143
|
-
|
|
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
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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 `<
|
|
3
|
-
// (substrate
|
|
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 + <
|
|
37
|
+
test('worker bearings: base framing + <knowledge> block, NO across-cycles framing', () => {
|
|
38
38
|
// Bug-regression: review finding M1 — buildContextBearings renders the
|
|
39
|
-
// <
|
|
40
|
-
//
|
|
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
|
|
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:
|
|
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
|
-
//
|
|
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, /<
|
|
57
|
-
assert.match(block, /\
|
|
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 <
|
|
66
|
-
// Bug-regression: review finding M1 — buildContextBearings renders <
|
|
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
|
|
77
|
-
writeFileSync(join(dir, 'flaky-build.md'), '---\nkind:
|
|
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, /<
|
|
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 <
|
|
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
|
|
95
|
-
// system-prompt-visibility: none and must still ride into <
|
|
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:
|
|
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
|
-
|
|
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
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
assert.equal(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
assert.
|
|
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
|
-
|
|
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 {};
|