@north-light/crouter 0.3.312 → 0.3.313
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +27 -30
- package/dist/api/client.js +80 -67
- package/dist/api/dto/broker-ops.d.ts +10 -20
- package/dist/api/dto/canvas.d.ts +1 -10
- package/dist/api/dto/health.d.ts +113 -0
- package/dist/api/dto/human-requests.d.ts +14 -3
- package/dist/api/dto/human.d.ts +2 -48
- package/dist/api/dto/human.js +3 -2
- package/dist/api/dto/inbox.d.ts +14 -0
- package/dist/api/dto/mail.d.ts +43 -0
- package/dist/api/dto/nodes.d.ts +11 -8
- package/dist/api/dto/recovery.d.ts +96 -0
- package/dist/api/dto/reviews.d.ts +2 -2
- package/dist/api/routes.d.ts +9 -6
- package/dist/api/routes.js +11 -11
- package/dist/build-root.d.ts +4 -0
- package/dist/build-root.js +11 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +35 -47
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +12 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +12 -5
- package/dist/cli.js +10 -1
- package/dist/clients/__tests__/helpers/human-daemon.d.ts +11 -0
- package/dist/clients/__tests__/helpers/human-daemon.js +60 -0
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/chrome/inbox-strip.d.ts +3 -3
- package/dist/clients/attach/chrome/inbox-strip.js +17 -9
- package/dist/clients/attach/chrome/ticket-panel.d.ts +2 -2
- package/dist/clients/attach/chrome/ticket-panel.js +77 -46
- package/dist/clients/attach/overlays/graph.js +6 -9
- package/dist/clients/attach/render/chat-view.d.ts +5 -4
- package/dist/clients/attach/render/chat-view.js +22 -13
- package/dist/clients/attach/render/page-block.d.ts +11 -7
- package/dist/clients/attach/render/page-block.js +64 -55
- package/dist/clients/attach/viewer.js +746 -735
- package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +56 -56
- package/dist/clients/inbox/__tests__/integration/stale-row-activation.test.js +45 -32
- package/dist/clients/inbox/controller.d.ts +42 -14
- package/dist/clients/inbox/controller.js +240 -124
- package/dist/clients/inbox/dto-adapt.d.ts +28 -0
- package/dist/clients/inbox/dto-adapt.js +93 -0
- package/dist/clients/inbox/page-adapter.d.ts +5 -4
- package/dist/clients/inbox/page-adapter.js +12 -12
- package/dist/clients/inbox/resolve.d.ts +10 -5
- package/dist/clients/inbox/resolve.js +13 -12
- package/dist/clients/inbox/review/review-client.d.ts +0 -1
- package/dist/clients/inbox/review/review-client.js +0 -4
- package/dist/clients/inbox/review-adapter.d.ts +2 -1
- package/dist/clients/inbox/review-adapter.js +3 -5
- package/dist/clients/inbox/surface.d.ts +2 -2
- package/dist/clients/inbox/surface.js +19 -8
- package/dist/clients/inbox/tui.d.ts +3 -3
- package/dist/commands/__tests__/human.test.js +31 -15
- package/dist/commands/__tests__/integration/dashboard-snapshot.test.js +36 -11
- package/dist/commands/{sys/__tests__ → __tests__/integration}/migrate.test.js +142 -36
- package/dist/commands/__tests__/revive-now-gate.test.js +2 -0
- package/dist/commands/api-client.d.ts +5 -4
- package/dist/commands/api-client.js +13 -7
- package/dist/commands/canvas-history/shared.js +1 -1
- package/dist/commands/canvas-history.js +1 -1
- package/dist/commands/canvas-roster.js +1 -1
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/human/feedback.js +20 -17
- package/dist/commands/human/prompts.js +45 -36
- package/dist/commands/human/queue.js +73 -123
- package/dist/commands/human/review.js +6 -16
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +2 -0
- package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +2 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +2 -0
- package/dist/commands/node-inspect-artifacts.js +1 -1
- package/dist/commands/node-lifecycle-revive.d.ts +2 -2
- package/dist/commands/node-lifecycle-revive.js +9 -19
- package/dist/commands/pkg/plugin-manage.js +0 -1
- package/dist/commands/profile/show.js +4 -12
- package/dist/commands/surface-tmux-spread.js +1 -5
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +23 -30
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +37 -11
- package/dist/commands/sys/branch.d.ts +2 -0
- package/dist/commands/sys/branch.js +19 -0
- package/dist/commands/sys/config.js +106 -33
- package/dist/commands/sys/daemon.d.ts +2 -0
- package/dist/commands/sys/daemon.js +130 -7
- package/dist/commands/sys/doctor.js +5 -15
- package/dist/commands/sys/migrate.js +21 -92
- package/dist/commands/sys/setup-settings-runner.js +4 -2
- package/dist/commands/sys.js +2 -15
- package/dist/core/__tests__/boot-fault-attempt.test.js +23 -1
- package/dist/core/__tests__/boot.test.js +32 -25
- package/dist/core/__tests__/broker-launch-candidates.test.js +49 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +149 -176
- package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +59 -10
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +76 -20
- package/dist/core/__tests__/canvas.test.js +18 -47
- package/dist/core/__tests__/child-death-wake.test.js +29 -7
- package/dist/core/__tests__/cli-help-contract.test.js +2 -0
- package/dist/core/__tests__/close.test.js +29 -10
- package/dist/core/__tests__/collectible.test.js +2 -1
- package/dist/core/__tests__/config-change-delta.test.js +2 -0
- package/dist/core/__tests__/context-intro.test.js +13 -1
- package/dist/core/__tests__/cron-armed-context-failure.test.js +17 -12
- package/dist/core/__tests__/cron-held-settlement.test.js +21 -3
- package/dist/core/__tests__/cron-node-sink-parked-root.test.js +29 -10
- package/dist/core/__tests__/cron-self-cancel-delivery.test.js +14 -2
- package/dist/core/__tests__/cron-timeout-descendants.test.js +13 -1
- package/dist/core/__tests__/daemon-boot.test.js +51 -14
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +8 -3
- package/dist/core/__tests__/focuses.test.js +10 -5
- package/dist/core/__tests__/helpers/harness.js +50 -18
- package/dist/core/__tests__/history-inbox.test.js +12 -16
- package/dist/core/__tests__/human-action-delivery.test.js +48 -28
- package/dist/core/__tests__/human-actions.test.js +7 -3
- package/dist/core/__tests__/inline-memory-refs.test.js +7 -2
- package/dist/core/__tests__/integration/broker-fork-seam.test.js +37 -10
- package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +42 -0
- package/dist/core/__tests__/integration/broker-snapshot-history.test.js +42 -29
- package/dist/core/__tests__/integration/broker-wedge-supervision.test.js +36 -35
- package/dist/core/__tests__/integration/command-plugins.test.js +15 -6
- package/dist/core/__tests__/integration/deferred-no-wake.test.js +113 -92
- package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
- package/dist/core/__tests__/integration/lifecycle-hooks.test.js +16 -3
- package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
- package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
- package/dist/core/__tests__/integration/revive.test.js +67 -21
- package/dist/core/__tests__/integration/spawn-root.test.js +51 -8
- package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
- package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
- package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
- package/dist/core/__tests__/kickoff.test.js +36 -39
- package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
- package/dist/core/__tests__/lifecycle.test.js +2 -1
- package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
- package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
- package/dist/core/__tests__/migration.test.js +145 -31
- package/dist/core/__tests__/model-pin-durability.test.js +4 -1
- package/dist/core/__tests__/model-routes-config.test.js +13 -5
- package/dist/core/__tests__/node-outcome.test.js +93 -26
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -1
- package/dist/core/__tests__/on-read-nested-store.test.js +2 -1
- package/dist/core/__tests__/passive-subscription.test.js +50 -157
- package/dist/core/__tests__/plugin-kinds.test.js +6 -4
- package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
- package/dist/core/__tests__/plugin-page-components.test.js +6 -4
- package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +4 -1
- package/dist/core/__tests__/prune.test.js +11 -5
- package/dist/core/__tests__/push-final-guard.test.js +14 -7
- package/dist/core/__tests__/relaunch-root.test.js +30 -2
- package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
- package/dist/core/__tests__/repository-root-identity.test.js +4 -0
- package/dist/core/__tests__/review-model-floor.test.js +6 -3
- package/dist/core/__tests__/revive-all.test.js +13 -3
- package/dist/core/__tests__/revive-capacity.test.js +26 -4
- package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
- package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
- package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
- package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
- package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
- package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
- package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
- package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +2 -1
- package/dist/core/__tests__/stop-guard.test.js +2 -1
- package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +18 -4
- package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
- package/dist/core/broker-client/transport-socket.d.ts +1 -0
- package/dist/core/broker-client/transport-socket.js +13 -5
- package/dist/core/canvas/__tests__/attention.test.js +29 -35
- package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
- package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
- package/dist/core/canvas/attention.d.ts +2 -5
- package/dist/core/canvas/attention.js +15 -64
- package/dist/core/canvas/boot.d.ts +6 -7
- package/dist/core/canvas/boot.js +5 -9
- package/dist/core/canvas/canvas.d.ts +13 -109
- package/dist/core/canvas/canvas.js +140 -401
- package/dist/core/canvas/crons.d.ts +2 -0
- package/dist/core/canvas/crons.js +16 -15
- package/dist/core/canvas/daemon-owner.js +10 -0
- package/dist/core/canvas/db.d.ts +2 -2
- package/dist/core/canvas/db.js +2 -18
- package/dist/core/canvas/deliveries.d.ts +104 -0
- package/dist/core/canvas/deliveries.js +354 -0
- package/dist/core/canvas/executions.d.ts +29 -0
- package/dist/core/canvas/executions.js +68 -0
- package/dist/core/canvas/history.js +35 -35
- package/dist/core/canvas/index.d.ts +3 -1
- package/dist/core/canvas/index.js +3 -1
- package/dist/core/canvas/migrations.js +20 -12
- package/dist/core/canvas/nav-model.js +1 -3
- package/dist/core/canvas/nav-render.d.ts +4 -4
- package/dist/core/canvas/nav-render.js +11 -21
- package/dist/core/canvas/node-sql-migration.d.ts +3 -0
- package/dist/core/canvas/node-sql-migration.js +261 -0
- package/dist/core/canvas/paths.d.ts +2 -16
- package/dist/core/canvas/paths.js +2 -31
- package/dist/core/canvas/recovery.d.ts +76 -0
- package/dist/core/canvas/recovery.js +588 -0
- package/dist/core/canvas/remote-canvas-source.js +4 -0
- package/dist/core/canvas/render-source.d.ts +16 -27
- package/dist/core/canvas/render-source.js +16 -45
- package/dist/core/canvas/sessions.d.ts +33 -0
- package/dist/core/canvas/sessions.js +78 -0
- package/dist/core/canvas/types.d.ts +33 -33
- package/dist/core/canvas/types.js +2 -5
- package/dist/core/canvas/waits.d.ts +18 -0
- package/dist/core/canvas/waits.js +70 -0
- package/dist/core/canvas/worktrees.d.ts +40 -0
- package/dist/core/canvas/worktrees.js +93 -0
- package/dist/core/config.d.ts +17 -12
- package/dist/core/config.js +106 -155
- package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
- package/dist/core/feed/feed.d.ts +0 -6
- package/dist/core/feed/feed.js +68 -56
- package/dist/core/feed/inbox.d.ts +7 -81
- package/dist/core/feed/inbox.js +46 -378
- package/dist/core/feed/messages.d.ts +58 -0
- package/dist/core/feed/messages.js +217 -0
- package/dist/core/fs-utils.d.ts +4 -0
- package/dist/core/fs-utils.js +21 -1
- package/dist/core/human/answer.d.ts +1 -1
- package/dist/core/human/answer.js +1 -1
- package/dist/core/human/claim.d.ts +1 -1
- package/dist/core/human/claim.js +2 -4
- package/dist/core/human/component-docs.js +1 -1
- package/dist/core/human/feedback-companion.js +3 -2
- package/dist/core/human/feedback.d.ts +5 -8
- package/dist/core/human/feedback.js +57 -85
- package/dist/core/human/page-authoring.d.ts +30 -0
- package/dist/core/human/page-authoring.js +19 -0
- package/dist/core/human/requests.d.ts +136 -0
- package/dist/core/human/requests.js +515 -0
- package/dist/core/human/root.d.ts +0 -7
- package/dist/core/human/root.js +0 -17
- package/dist/core/human/scan.d.ts +2 -7
- package/dist/core/human/scan.js +48 -132
- package/dist/core/human/summary.d.ts +1 -1
- package/dist/core/human/summary.js +1 -1
- package/dist/core/human/tickets.d.ts +23 -83
- package/dist/core/human/tickets.js +92 -243
- package/dist/core/human/types.d.ts +5 -1
- package/dist/core/installed-plugins.js +8 -3
- package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
- package/dist/core/locked-file.d.ts +6 -3
- package/dist/core/locked-file.js +36 -9
- package/dist/core/memory/extensions.js +2 -0
- package/dist/core/preview-registry.js +0 -1
- package/dist/core/profiles/__tests__/env-store.test.js +2 -0
- package/dist/core/profiles/default-binding.d.ts +0 -4
- package/dist/core/profiles/default-binding.js +12 -58
- package/dist/core/profiles/env-store.js +8 -3
- package/dist/core/profiles/manifest.d.ts +2 -37
- package/dist/core/profiles/manifest.js +35 -161
- package/dist/core/profiles/select.js +3 -0
- package/dist/core/review/__tests__/capture-origin.test.js +13 -2
- package/dist/core/review/__tests__/comments.test.js +28 -10
- package/dist/core/review/__tests__/remap.test.js +28 -10
- package/dist/core/review/__tests__/stage-identity.test.js +17 -7
- package/dist/core/review/birth.d.ts +1 -1
- package/dist/core/review/birth.js +34 -5
- package/dist/core/review/comments.js +1 -1
- package/dist/core/review/document.js +1 -1
- package/dist/core/review/project.d.ts +5 -1
- package/dist/core/review/project.js +14 -36
- package/dist/core/review/realize.d.ts +3 -6
- package/dist/core/review/realize.js +51 -168
- package/dist/core/review/remap.js +1 -1
- package/dist/core/review/stage.d.ts +3 -2
- package/dist/core/review/stage.js +2 -3
- package/dist/core/review/store.d.ts +3 -21
- package/dist/core/review/store.js +140 -186
- package/dist/core/review/types.d.ts +0 -1
- package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
- package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
- package/dist/core/runtime/broker/auth-reload.js +9 -10
- package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
- package/dist/core/runtime/broker/daemon-ops.js +42 -9
- package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
- package/dist/core/runtime/broker/extension-abort.js +11 -0
- package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
- package/dist/core/runtime/broker/fault-retry.js +142 -95
- package/dist/core/runtime/broker/inbox.d.ts +4 -32
- package/dist/core/runtime/broker/inbox.js +4 -92
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +21 -12
- package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
- package/dist/core/runtime/broker-persona-guidance.js +2 -13
- package/dist/core/runtime/broker.js +28 -22
- package/dist/core/runtime/close.d.ts +3 -9
- package/dist/core/runtime/close.js +16 -27
- package/dist/core/runtime/fault.d.ts +3 -93
- package/dist/core/runtime/fault.js +9 -419
- package/dist/core/runtime/kickoff.d.ts +3 -6
- package/dist/core/runtime/kickoff.js +12 -18
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/lifecycle.js +40 -7
- package/dist/core/runtime/placement.js +2 -2
- package/dist/core/runtime/recycle.js +8 -2
- package/dist/core/runtime/reset.js +22 -6
- package/dist/core/runtime/revive.js +226 -218
- package/dist/core/runtime/spawn.js +14 -3
- package/dist/core/scoped-state/db.d.ts +7 -0
- package/dist/core/scoped-state/db.js +92 -0
- package/dist/core/scoped-state/migrate.d.ts +11 -0
- package/dist/core/scoped-state/migrate.js +198 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +12 -0
- package/dist/core/scoped-state/profiles.js +62 -0
- package/dist/core/scoped-state/providers.d.ts +16 -0
- package/dist/core/scoped-state/providers.js +8 -0
- package/dist/core/scoped-state/schema.d.ts +3 -0
- package/dist/core/scoped-state/schema.js +26 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +82 -0
- package/dist/core/subscription-state.d.ts +29 -48
- package/dist/core/subscription-state.js +176 -712
- package/dist/core/worktree-close.js +4 -4
- package/dist/core/worktree-mutation-async.js +2 -2
- package/dist/core/worktree-sweep.js +11 -16
- package/dist/core/worktree.js +6 -12
- package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
- package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
- package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
- package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
- package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +39 -15
- package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
- package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
- package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
- package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
- package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
- package/dist/daemon/api/__tests__/model-config.test.js +8 -8
- package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
- package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
- package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -1
- package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
- package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
- package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
- package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
- package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +27 -41
- package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
- package/dist/daemon/api/bridge.js +1 -1
- package/dist/daemon/api/handlers/bash-jobs.js +6 -7
- package/dist/daemon/api/handlers/broker-ops.js +47 -84
- package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
- package/dist/daemon/api/handlers/broker-recovery.js +251 -0
- package/dist/daemon/api/handlers/canvas.js +2 -44
- package/dist/daemon/api/handlers/daemon.js +41 -0
- package/dist/daemon/api/handlers/feedback-comments.js +2 -2
- package/dist/daemon/api/handlers/health.js +10 -5
- package/dist/daemon/api/handlers/human-requests.js +168 -188
- package/dist/daemon/api/handlers/human.js +3 -151
- package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
- package/dist/daemon/api/handlers/inbox-page.js +89 -0
- package/dist/daemon/api/handlers/inbox.js +21 -120
- package/dist/daemon/api/handlers/messages.d.ts +1 -0
- package/dist/daemon/api/handlers/messages.js +73 -15
- package/dist/daemon/api/handlers/node-outcomes.js +58 -13
- package/dist/daemon/api/handlers/nodes.js +7 -13
- package/dist/daemon/api/handlers/reviews.js +4 -29
- package/dist/daemon/api/map.d.ts +25 -3
- package/dist/daemon/api/map.js +84 -14
- package/dist/daemon/api/server.js +68 -9
- package/dist/daemon/control.d.ts +18 -0
- package/dist/daemon/cron/disposition.d.ts +2 -0
- package/dist/daemon/cron/disposition.js +8 -5
- package/dist/daemon/cron/settle.js +37 -13
- package/dist/daemon/cron/sinks.d.ts +18 -1
- package/dist/daemon/cron/sinks.js +47 -93
- package/dist/daemon/crtrd-cli.js +4 -19
- package/dist/daemon/crtrd.d.ts +2 -0
- package/dist/daemon/crtrd.js +206 -81
- package/dist/daemon/fleet.js +5 -6
- package/dist/daemon/human/finish.d.ts +12 -24
- package/dist/daemon/human/finish.js +88 -285
- package/dist/daemon/human/settle.d.ts +40 -0
- package/dist/daemon/human/settle.js +89 -0
- package/dist/daemon/human/sweep.d.ts +20 -5
- package/dist/daemon/human/sweep.js +56 -60
- package/dist/daemon/manage.d.ts +21 -16
- package/dist/daemon/manage.js +85 -51
- package/dist/daemon/messaging/node-message.d.ts +1 -1
- package/dist/daemon/messaging/node-message.js +9 -12
- package/dist/daemon/park-activity.js +3 -3
- package/dist/daemon/reconcilers/bash-deadline.js +6 -7
- package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
- package/dist/daemon/reconcilers/broker-supervision.js +41 -19
- package/dist/daemon/reconcilers/controller-death.js +6 -18
- package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/delivery-lane.js +130 -0
- package/dist/daemon/reconcilers/live-obligation.js +3 -3
- package/dist/daemon/reconcilers/node-deadline.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
- package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
- package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
- package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
- package/dist/daemon/review/companion.js +5 -11
- package/dist/daemon/review/deliver.d.ts +1 -5
- package/dist/daemon/review/deliver.js +3 -52
- package/dist/daemon/review/finish.d.ts +2 -27
- package/dist/daemon/review/finish.js +79 -263
- package/dist/daemon/review/sweep.d.ts +3 -10
- package/dist/daemon/review/sweep.js +13 -131
- package/dist/daemon/startup-policy.d.ts +0 -5
- package/dist/daemon/startup-policy.js +0 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
- package/dist/migrations/__tests__/activation.test.js +9 -6
- package/dist/migrations/activation.d.ts +3 -2
- package/dist/migrations/activation.js +2 -4
- package/dist/migrations/corpus.d.ts +2 -5
- package/dist/migrations/corpus.js +9 -20
- package/dist/migrations/runner.d.ts +3 -1
- package/dist/migrations/runner.js +6 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
- package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
- package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
- package/dist/pi-extensions/canvas-passive-context.js +85 -37
- package/dist/pi-extensions/canvas-stophook.js +47 -31
- package/dist/shared/env.d.ts +1 -0
- package/dist/shared/env.js +3 -0
- package/package.json +1 -1
- package/runtime.lock.json +5 -5
- package/dist/clients/attach/__tests__/page-block.test.js +0 -54
- package/dist/clients/inbox/review-visibility.d.ts +0 -5
- package/dist/clients/inbox/review-visibility.js +0 -19
- package/dist/commands/canvas-rebuild-index.d.ts +0 -2
- package/dist/commands/canvas-rebuild-index.js +0 -46
- package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
- package/dist/core/__tests__/human-deliver.test.d.ts +0 -1
- package/dist/core/__tests__/human-deliver.test.js +0 -256
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
- package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
- package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
- package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
- package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
- package/dist/core/__tests__/phase4-review-store.test.js +0 -144
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
- package/dist/core/canvas/human-deliveries.d.ts +0 -39
- package/dist/core/canvas/human-deliveries.js +0 -75
- package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
- package/dist/core/canvas/node-outcome-deliveries.js +0 -159
- package/dist/core/feed/passive.d.ts +0 -3
- package/dist/core/feed/passive.js +0 -53
- package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
- package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
- package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
- package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
- package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
- package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
- package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
- package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
- package/dist/core/human/__tests__/page-tickets.test.js +0 -161
- package/dist/core/review/ticket-filter.d.ts +0 -17
- package/dist/core/review/ticket-filter.js +0 -21
- package/dist/core/runtime/broker/passive.d.ts +0 -17
- package/dist/core/runtime/broker/passive.js +0 -44
- package/dist/core/runtime/busy.d.ts +0 -46
- package/dist/core/runtime/busy.js +0 -150
- package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
- package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
- package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
- package/dist/daemon/human/deliver-action.d.ts +0 -3
- package/dist/daemon/human/deliver-action.js +0 -9
- package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
- package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
- package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
- package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
- package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
- package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
- package/dist/daemon/startup-block-marker.d.ts +0 -25
- package/dist/daemon/startup-block-marker.js +0 -99
- /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
- /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
- /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
|
@@ -10,9 +10,11 @@ import { tmpdir } from 'node:os';
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { createNode, getNode, subscribe } from '../canvas/canvas.js';
|
|
12
12
|
import { reportsDir } from '../canvas/paths.js';
|
|
13
|
-
import { closeDb } from '../canvas/db.js';
|
|
13
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
14
|
+
import { acknowledgeNodeMail, claimNodeMail } from '../canvas/deliveries.js';
|
|
15
|
+
import { beginExecution } from '../canvas/executions.js';
|
|
14
16
|
import { buildReviveKickoff, clearYieldMessage, drainBearings, readYieldMessage, writeYieldMessage, yieldMessagePath, } from '../runtime/kickoff.js';
|
|
15
|
-
import {
|
|
17
|
+
import { enqueueNodeMail } from '../feed/messages.js';
|
|
16
18
|
import { appendSituationalContext } from '../runtime/situational-context.js';
|
|
17
19
|
import { buildContextBearings as buildContextIntro } from '../runtime/bearings.js';
|
|
18
20
|
let home;
|
|
@@ -36,6 +38,7 @@ before(() => {
|
|
|
36
38
|
beforeEach(() => {
|
|
37
39
|
closeDb();
|
|
38
40
|
rmSync(home, { recursive: true, force: true });
|
|
41
|
+
migrate(openDb());
|
|
39
42
|
});
|
|
40
43
|
after(() => {
|
|
41
44
|
closeDb();
|
|
@@ -45,18 +48,23 @@ after(() => {
|
|
|
45
48
|
test('drainBearings preserves the yield note until explicit boot confirmation', () => {
|
|
46
49
|
const id = 'n1';
|
|
47
50
|
const meta = createNode(node(id));
|
|
51
|
+
const executionId = beginExecution(id, { daemonEpoch: 'test' });
|
|
48
52
|
writeYieldMessage(id, 'on wake, finish step 3');
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const b1 = drainBearings(meta);
|
|
53
|
+
enqueueNodeMail(id, { senderNodeId: 'w1', kind: 'update', label: 'first', bodyRef: '/tmp/a.md', urgency: 'normal', wake: 1 });
|
|
54
|
+
enqueueNodeMail(id, { senderNodeId: 'w1', kind: 'update', label: 'second', bodyRef: '/tmp/b.md', urgency: 'normal', wake: 1 });
|
|
55
|
+
const b1 = drainBearings(meta, executionId);
|
|
52
56
|
assert.equal(b1.yieldMsg?.trim(), 'on wake, finish step 3');
|
|
53
57
|
assert.ok(b1.unreadDigest !== null && b1.unreadDigest.length > 0);
|
|
54
58
|
assert.equal(existsSync(yieldMessagePath(id)), true, 'yield note remains pending before confirmation');
|
|
55
59
|
assert.equal(readYieldMessage(id), 'on wake, finish step 3', 'reader sees the same persisted note');
|
|
56
|
-
|
|
57
|
-
const
|
|
60
|
+
const claim = claimNodeMail(id, executionId, 'wake');
|
|
61
|
+
const receipt = acknowledgeNodeMail(id, executionId, claim.deliveries.map((delivery) => ({
|
|
62
|
+
deliveryId: delivery.deliveryId, attempts: delivery.attempts, disposition: 'acknowledged',
|
|
63
|
+
})));
|
|
64
|
+
assert.deepEqual(receipt.results.map((result) => result.outcome), ['acknowledged', 'acknowledged']);
|
|
65
|
+
const b2 = drainBearings(meta, executionId);
|
|
58
66
|
assert.equal(b2.yieldMsg, 'on wake, finish step 3', 'a failed launch can read the same note again');
|
|
59
|
-
assert.equal(b2.unreadDigest, null, '
|
|
67
|
+
assert.equal(b2.unreadDigest, null, 'settled deliveries do not return to the next launch');
|
|
60
68
|
clearYieldMessage(id);
|
|
61
69
|
clearYieldMessage(id);
|
|
62
70
|
assert.equal(readYieldMessage(id), null, 'confirmation clear is idempotent');
|
|
@@ -65,30 +73,19 @@ test('drainBearings preserves the yield note until explicit boot confirmation',
|
|
|
65
73
|
test('fresh kickoff keeps each inbox card directly before its own message', () => {
|
|
66
74
|
const id = 'ordered-cards';
|
|
67
75
|
const meta = createNode(node(id));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
kind: '
|
|
72
|
-
label: 'FIRST-BODY',
|
|
73
|
-
data: { body: 'FIRST-BODY', cards: ['<runtime kind="test-card">FIRST-CARD</runtime>'] },
|
|
76
|
+
const executionId = beginExecution(id, { daemonEpoch: 'test' });
|
|
77
|
+
enqueueNodeMail(id, {
|
|
78
|
+
senderNodeId: 'sender', kind: 'message', label: 'FIRST-BODY',
|
|
79
|
+
bodyJson: { body: 'FIRST-BODY', cards: ['<runtime kind="test-card">FIRST-CARD</runtime>'] }, urgency: 'normal', wake: 1,
|
|
74
80
|
});
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
kind: 'message',
|
|
79
|
-
label: 'SECOND-BODY',
|
|
80
|
-
data: { body: 'SECOND-BODY', cards: ['<runtime kind="test-card">SECOND-CARD</runtime>'] },
|
|
81
|
+
enqueueNodeMail(id, {
|
|
82
|
+
senderNodeId: 'sender', kind: 'message', label: 'SECOND-BODY',
|
|
83
|
+
bodyJson: { body: 'SECOND-BODY', cards: ['<runtime kind="test-card">SECOND-CARD</runtime>'] }, urgency: 'normal', wake: 1,
|
|
81
84
|
});
|
|
82
85
|
for (const body of ['GROUPED-BODY-1', 'GROUPED-BODY-2', 'GROUPED-BODY-3']) {
|
|
83
|
-
|
|
84
|
-
from: 'card-free-sender',
|
|
85
|
-
tier: 'normal',
|
|
86
|
-
kind: 'message',
|
|
87
|
-
label: body,
|
|
88
|
-
data: { body },
|
|
89
|
-
});
|
|
86
|
+
enqueueNodeMail(id, { senderNodeId: 'card-free-sender', kind: 'message', label: body, bodyJson: { body }, urgency: 'normal', wake: 1 });
|
|
90
87
|
}
|
|
91
|
-
const kickoff = buildReviveKickoff(meta, drainBearings(meta));
|
|
88
|
+
const kickoff = buildReviveKickoff(meta, drainBearings(meta, executionId));
|
|
92
89
|
const firstCard = kickoff.indexOf('FIRST-CARD');
|
|
93
90
|
const firstBody = kickoff.indexOf('FIRST-BODY');
|
|
94
91
|
const secondCard = kickoff.indexOf('SECOND-CARD');
|
|
@@ -109,7 +106,7 @@ test('drainBearings leaves persona drift pending until the replacement context e
|
|
|
109
106
|
mode: 'orchestrator',
|
|
110
107
|
persona_ack: oldPersona,
|
|
111
108
|
}));
|
|
112
|
-
const bearings = drainBearings(meta);
|
|
109
|
+
const bearings = drainBearings(meta, beginExecution(meta.node_id, { daemonEpoch: 'test' }));
|
|
113
110
|
assert.match(bearings.driftGuidance ?? '', /ORCHESTRATOR/);
|
|
114
111
|
assert.deepEqual(getNode(meta.node_id)?.persona_ack, oldPersona, 'pre-session_start launch work cannot acknowledge the drift');
|
|
115
112
|
});
|
|
@@ -117,7 +114,7 @@ test('the feed block frames awaiting workers as alive + auto-waking, so a fresh
|
|
|
117
114
|
const parent = createNode(node('p1'));
|
|
118
115
|
const child = createNode(node('c1')); // status 'active' by default
|
|
119
116
|
subscribe(parent.node_id, child.node_id); // parent awaits child
|
|
120
|
-
const msg = buildReviveKickoff(parent, drainBearings(parent));
|
|
117
|
+
const msg = buildReviveKickoff(parent, drainBearings(parent, beginExecution(parent.node_id, { daemonEpoch: 'test' })));
|
|
121
118
|
// The roster names the live child...
|
|
122
119
|
assert.ok(msg.includes(child.node_id), 'awaiting roster lists the live child');
|
|
123
120
|
// ...and asserts aliveness + the automatic wake at the source, so the node
|
|
@@ -143,7 +140,7 @@ test('a fresh revive is pointed at its subscriptions\' on-disk report history (c
|
|
|
143
140
|
const newer = join(dir, '20260608T130000-final.md');
|
|
144
141
|
writeFileSync(older, '---\n---\nearly progress', 'utf8');
|
|
145
142
|
writeFileSync(newer, '---\n---\nthe result', 'utf8');
|
|
146
|
-
const msg = buildReviveKickoff(parent, drainBearings(parent));
|
|
143
|
+
const msg = buildReviveKickoff(parent, drainBearings(parent, beginExecution(parent.node_id, { daemonEpoch: 'test' })));
|
|
147
144
|
// The feed block stays condensed: it reports that history exists and surfaces
|
|
148
145
|
// the newest report ref for immediate dereference, while older refs stay one
|
|
149
146
|
// command away instead of being preloaded into the revive prompt.
|
|
@@ -170,7 +167,7 @@ test('a fresh revive kickoff never carries situational context — it rides the
|
|
|
170
167
|
const id = 'n3';
|
|
171
168
|
const meta = createNode(node(id));
|
|
172
169
|
appendSituationalContext(id, 'user is mid-checkout on the store applet');
|
|
173
|
-
const kickoff = buildReviveKickoff(meta, drainBearings(meta));
|
|
170
|
+
const kickoff = buildReviveKickoff(meta, drainBearings(meta, beginExecution(meta.node_id, { daemonEpoch: 'test' })));
|
|
174
171
|
assert.doesNotMatch(kickoff, /kind="situational"/, 'visible kickoff never carries the ambient card');
|
|
175
172
|
assert.doesNotMatch(kickoff, /mid-checkout on the store applet/, 'visible kickoff never carries the ambient text');
|
|
176
173
|
const hidden = buildContextIntro(id);
|
|
@@ -180,20 +177,20 @@ test('a fresh revive kickoff never carries situational context — it rides the
|
|
|
180
177
|
test('buildReviveKickoff is pure — building twice eats nothing', () => {
|
|
181
178
|
const id = 'n2';
|
|
182
179
|
const meta = createNode(node(id));
|
|
180
|
+
const executionId = beginExecution(id, { daemonEpoch: 'test' });
|
|
183
181
|
writeYieldMessage(id, 'remember the invariant');
|
|
184
|
-
|
|
185
|
-
//
|
|
186
|
-
const bearings = drainBearings(meta);
|
|
187
|
-
const cursorAfterDrain = readCursor(id);
|
|
188
|
-
assert.equal(cursorAfterDrain, last.entry_id);
|
|
182
|
+
enqueueNodeMail(id, { senderNodeId: 'w', kind: 'update', label: 'r', bodyRef: '/tmp/r.md', urgency: 'normal', wake: 1 });
|
|
183
|
+
// Claim once, then build twice from the same immutable bearings.
|
|
184
|
+
const bearings = drainBearings(meta, executionId);
|
|
189
185
|
const s1 = buildReviveKickoff(meta, bearings);
|
|
190
186
|
const s2 = buildReviveKickoff(meta, bearings);
|
|
191
|
-
// Identical output, and the build
|
|
187
|
+
// Identical output, and the build mutates neither delivery receipts nor note.
|
|
192
188
|
assert.equal(s1, s2);
|
|
193
189
|
assert.match(s1, /<yield-message>\nremember the invariant\n<\/yield-message>/, 'yield note is delivered inline');
|
|
194
190
|
const contextInventory = s1.match(/<context-dir path="[^"]+">[\s\S]*?<\/context-dir>/)?.[0];
|
|
195
191
|
assert.ok(contextInventory, 'context inventory is present');
|
|
196
192
|
assert.doesNotMatch(contextInventory, /yield-message\.md/, 'ephemeral yield note is not addressable from inventory');
|
|
197
|
-
|
|
193
|
+
const claim = claimNodeMail(id, executionId, 'wake');
|
|
194
|
+
assert.equal(claim.deliveries.length, 1, 'build does not settle its claimed delivery');
|
|
198
195
|
assert.equal(readYieldMessage(id), 'remember the invariant', 'build does not mutate the persisted note');
|
|
199
196
|
});
|
|
@@ -4,7 +4,7 @@ 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 } from '../canvas/canvas.js';
|
|
7
|
-
import { closeDb } from '../canvas/db.js';
|
|
7
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
8
8
|
import { transition } from '../runtime/lifecycle.js';
|
|
9
9
|
let home;
|
|
10
10
|
function seed(nodeId) {
|
|
@@ -30,6 +30,7 @@ before(() => {
|
|
|
30
30
|
beforeEach(() => {
|
|
31
31
|
closeDb();
|
|
32
32
|
rmSync(home, { recursive: true, force: true });
|
|
33
|
+
migrate(openDb());
|
|
33
34
|
});
|
|
34
35
|
after(() => {
|
|
35
36
|
closeDb();
|
|
@@ -11,7 +11,7 @@ import { mkdtempSync, rmSync } from 'node:fs';
|
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
13
|
import { createNode, getNode, setStatus, setIntent } from '../canvas/canvas.js';
|
|
14
|
-
import { closeDb } from '../canvas/db.js';
|
|
14
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
15
15
|
import { isParked, transition } from '../runtime/lifecycle.js';
|
|
16
16
|
let home;
|
|
17
17
|
function node(id) {
|
|
@@ -39,6 +39,7 @@ before(() => {
|
|
|
39
39
|
beforeEach(() => {
|
|
40
40
|
closeDb();
|
|
41
41
|
rmSync(home, { recursive: true, force: true });
|
|
42
|
+
migrate(openDb());
|
|
42
43
|
});
|
|
43
44
|
after(() => {
|
|
44
45
|
closeDb();
|
|
@@ -6,10 +6,13 @@ import { mkdtempSync, rmSync } from 'node:fs';
|
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { createNode, updateNode } from '../canvas/canvas.js';
|
|
9
|
-
import { closeDb } from '../canvas/db.js';
|
|
9
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
10
|
+
import { ensureScopeInitialized } from '../config.js';
|
|
11
|
+
import { closeScopedStores } from '../scoped-state/db.js';
|
|
10
12
|
import { renderPreferencesSection } from '../substrate/render-node.js';
|
|
11
13
|
import { clearSessionCache } from '../substrate/session-cache.js';
|
|
12
14
|
let home;
|
|
15
|
+
let priorUserHome;
|
|
13
16
|
function node(id) {
|
|
14
17
|
return {
|
|
15
18
|
node_id: id,
|
|
@@ -24,18 +27,27 @@ function node(id) {
|
|
|
24
27
|
}
|
|
25
28
|
before(() => {
|
|
26
29
|
home = mkdtempSync(join(tmpdir(), 'crtr-live-lifecycle-prompt-'));
|
|
30
|
+
priorUserHome = process.env['HOME'];
|
|
31
|
+
process.env['HOME'] = home;
|
|
27
32
|
process.env['CRTR_HOME'] = home;
|
|
28
33
|
});
|
|
29
34
|
beforeEach(() => {
|
|
30
35
|
closeDb();
|
|
31
36
|
clearSessionCache();
|
|
32
37
|
rmSync(home, { recursive: true, force: true });
|
|
38
|
+
ensureScopeInitialized('user', join(home, '.crouter'));
|
|
39
|
+
migrate(openDb());
|
|
33
40
|
});
|
|
34
41
|
after(() => {
|
|
35
42
|
closeDb();
|
|
36
43
|
clearSessionCache();
|
|
44
|
+
closeScopedStores();
|
|
37
45
|
rmSync(home, { recursive: true, force: true });
|
|
38
46
|
delete process.env['CRTR_HOME'];
|
|
47
|
+
if (priorUserHome === undefined)
|
|
48
|
+
delete process.env['HOME'];
|
|
49
|
+
else
|
|
50
|
+
process.env['HOME'] = priorUserHome;
|
|
39
51
|
});
|
|
40
52
|
test('a cached resident prompt becomes terminal after a live lifecycle change', () => {
|
|
41
53
|
createNode(node('live'));
|
|
@@ -23,6 +23,7 @@ import { tmpdir } from 'node:os';
|
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { gitSync } from '../git.js';
|
|
25
25
|
import { createProfile } from '../profiles/manifest.js';
|
|
26
|
+
import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
|
|
26
27
|
import { resetScopeCache } from '../scope.js';
|
|
27
28
|
import { loadMemoryStoreView, openProjectMemoryStore, resolveMemoryCandidates, resolveMemoryDoc, resolveMemoryDocInStore, resolveMemoryDocs, } from '../memory-resolver.js';
|
|
28
29
|
let home;
|
|
@@ -54,6 +55,7 @@ before(() => {
|
|
|
54
55
|
prevHome = process.env.HOME;
|
|
55
56
|
prevProfile = process.env.CRTR_PROFILE_ID;
|
|
56
57
|
process.env.HOME = home;
|
|
58
|
+
initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
|
|
57
59
|
// A wider user-scope doc whose name a project doc used to shadow.
|
|
58
60
|
mkdirSync(join(home, '.crouter', 'memory'), { recursive: true });
|
|
59
61
|
writeFileSync(join(home, '.crouter', 'memory', 'testing-stance.md'), '---\nkind: preference\n---\nUSER PREFERENCE BODY\n', 'utf8');
|
|
@@ -10,9 +10,10 @@ import { canvasDbPath, ensureHome } from '../canvas/paths.js';
|
|
|
10
10
|
import { customExtensionPaths } from '../canvas/extensions.js';
|
|
11
11
|
import { assertMandatoryCanvasExtensions, CANVAS_EXTENSIONS, mergeCanvasExtensions } from '../runtime/canvas-extensions.js';
|
|
12
12
|
import { updateNode } from '../canvas/canvas.js';
|
|
13
|
+
import { parsePage, parsePageDelivery } from '../human/page.js';
|
|
13
14
|
import { spawn, spawnSync } from 'node:child_process';
|
|
14
15
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const RUNTIME_COLUMNS = ['intent', '
|
|
16
|
+
const RUNTIME_COLUMNS = ['intent', 'window', 'tmux_session'];
|
|
16
17
|
const RACE_FIXTURE = join(HERE, 'fixtures', 'metadata-migration-race.ts');
|
|
17
18
|
const PRE_TOOL_GUIDE_CANVAS_EXTENSION_NAMES = [
|
|
18
19
|
'canvas-inbox-watcher', 'canvas-stophook', 'canvas-recap',
|
|
@@ -53,6 +54,11 @@ function userVersion(db) {
|
|
|
53
54
|
return db.prepare('PRAGMA user_version').get()
|
|
54
55
|
.user_version;
|
|
55
56
|
}
|
|
57
|
+
function openMigratedDb() {
|
|
58
|
+
const db = openDb();
|
|
59
|
+
migrate(db);
|
|
60
|
+
return db;
|
|
61
|
+
}
|
|
56
62
|
function nodeColumns(db) {
|
|
57
63
|
return db.prepare('PRAGMA table_info(nodes)').all().map((r) => r.name);
|
|
58
64
|
}
|
|
@@ -77,8 +83,11 @@ after(() => {
|
|
|
77
83
|
rmSync(home, { recursive: true, force: true });
|
|
78
84
|
delete process.env['CRTR_HOME'];
|
|
79
85
|
});
|
|
80
|
-
test('opening a fresh home
|
|
86
|
+
test('opening a fresh home leaves schema initialization explicit', () => {
|
|
81
87
|
const db = openDb();
|
|
88
|
+
assert.equal(userVersion(db), 0);
|
|
89
|
+
assert.equal(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'nodes'").get(), undefined);
|
|
90
|
+
migrate(db);
|
|
82
91
|
assert.equal(userVersion(db), MIGRATIONS.length);
|
|
83
92
|
});
|
|
84
93
|
test('a persisted review-boundary canvas prefix is not treated as custom extensions', () => {
|
|
@@ -120,7 +129,7 @@ test('a v14 canvas normalizes historical canvas paths while retaining custom lau
|
|
|
120
129
|
const saved = JSON.parse(readFileSync(metaPath, 'utf8'));
|
|
121
130
|
assert.deepEqual(saved.launch.extensions, ['/plugins/canvas-inbox-watcher.js']);
|
|
122
131
|
});
|
|
123
|
-
test('
|
|
132
|
+
test('an explicit v14 migration preserves legacy metadata while node updates use SQL state', async () => {
|
|
124
133
|
const metaPath = join(home, 'nodes', 'saved', 'meta.json');
|
|
125
134
|
mkdirSync(join(home, 'nodes', 'saved'), { recursive: true });
|
|
126
135
|
writeFileSync(metaPath, JSON.stringify({
|
|
@@ -143,16 +152,17 @@ test('a v14 migration is protected against concurrent writeback by normal update
|
|
|
143
152
|
rmSync(marker, { force: true });
|
|
144
153
|
const { done } = await spawnMigrationRace(metaPath, marker);
|
|
145
154
|
await waitForPath(marker);
|
|
155
|
+
migrate(raw);
|
|
156
|
+
await done;
|
|
157
|
+
raw.close();
|
|
146
158
|
const update = updateNode('saved', { name: 'saved-raced' });
|
|
147
159
|
assert.equal(update.name, 'saved-raced');
|
|
148
|
-
await done;
|
|
149
160
|
const settled = JSON.parse(readFileSync(metaPath, 'utf8'));
|
|
150
161
|
assert.equal(customExtensionPaths(settled.launch.extensions).length, settled.launch.extensions.length);
|
|
151
|
-
assert.deepEqual(settled.launch.extensions, ['/plugins/canvas-inbox-watcher.js'], '
|
|
152
|
-
assert.equal(settled.name, 'saved
|
|
153
|
-
raw.close();
|
|
162
|
+
assert.deepEqual(settled.launch.extensions, ['/plugins/canvas-inbox-watcher.js'], 'migration writeback cannot restore retired hook prefixes');
|
|
163
|
+
assert.equal(settled.name, 'saved', 'the legacy conversion input remains unchanged after a SQL node update');
|
|
154
164
|
});
|
|
155
|
-
test('a
|
|
165
|
+
test('a v0 db retains its runtime fields while process state moves to executions', () => {
|
|
156
166
|
// Build a v0 db by hand: the baseline tables only, user_version 0, no runtime
|
|
157
167
|
// columns — exactly the shape of the live fleet before this phase.
|
|
158
168
|
ensureHome();
|
|
@@ -189,14 +199,14 @@ test('a fresh db has the four additive runtime columns', () => {
|
|
|
189
199
|
assert.equal(userVersion(raw), 0);
|
|
190
200
|
assert.ok(!nodeColumns(raw).includes('intent'));
|
|
191
201
|
raw.close();
|
|
192
|
-
|
|
193
|
-
const db = openDb();
|
|
202
|
+
const db = openMigratedDb();
|
|
194
203
|
assert.equal(userVersion(db), MIGRATIONS.length);
|
|
195
|
-
//
|
|
204
|
+
// Node runtime columns now exist; process state is projected from executions.
|
|
196
205
|
const cols = nodeColumns(db);
|
|
197
206
|
for (const c of RUNTIME_COLUMNS) {
|
|
198
207
|
assert.ok(cols.includes(c), `expected nodes.${c} after migration`);
|
|
199
208
|
}
|
|
209
|
+
assert.ok(!cols.includes('pi_pid'), 'nodes no longer stores process state');
|
|
200
210
|
// Pre-existing data is intact — no loss, no mutation of existing columns.
|
|
201
211
|
const row = db
|
|
202
212
|
.prepare('SELECT node_id, status, cwd FROM nodes WHERE node_id = ?')
|
|
@@ -208,10 +218,9 @@ test('a fresh db has the four additive runtime columns', () => {
|
|
|
208
218
|
assert.equal(edgeCount, 1);
|
|
209
219
|
// The new columns are seeded NULL for the migrated row — nothing observed.
|
|
210
220
|
const rt = db
|
|
211
|
-
.prepare('SELECT intent,
|
|
221
|
+
.prepare('SELECT intent, "window", tmux_session FROM nodes WHERE node_id = ?')
|
|
212
222
|
.get('n1');
|
|
213
223
|
assert.equal(rt['intent'], null);
|
|
214
|
-
assert.equal(rt['pi_pid'], null);
|
|
215
224
|
assert.equal(rt['window'], null);
|
|
216
225
|
assert.equal(rt['tmux_session'], null);
|
|
217
226
|
});
|
|
@@ -240,7 +249,7 @@ test('session-file ownership migration reconciles authoritative metas before cle
|
|
|
240
249
|
insert.run('later-claimant', 'later-claimant', 'developer', 'base', 'terminal', 'active', '/tmp', '2026-01-02T00:00:00.000Z', aliasedSessionFile);
|
|
241
250
|
insert.run('recovered-claimant', 'recovered-claimant', 'developer', 'base', 'terminal', 'active', '/tmp', '2026-01-03T00:00:00.000Z', sessionFile);
|
|
242
251
|
assert.doesNotThrow(() => migrate(raw), 'a real canvas with pre-existing duplicates must migrate');
|
|
243
|
-
const rows = raw.prepare('SELECT node_id, pi_session_file FROM
|
|
252
|
+
const rows = raw.prepare('SELECT node_id, pi_session_file FROM node_rows ORDER BY created, node_id').all();
|
|
244
253
|
assert.deepEqual(rows.map((row) => [row.node_id, row.pi_session_file]), [
|
|
245
254
|
['first-owner', sessionFile],
|
|
246
255
|
['later-claimant', null],
|
|
@@ -252,7 +261,7 @@ test('session-file ownership migration reconciles authoritative metas before cle
|
|
|
252
261
|
const recovered = JSON.parse(readFileSync(join(home, 'nodes', 'recovered-claimant', 'meta.json'), 'utf8'));
|
|
253
262
|
assert.equal(recovered.pi_session_id, 'recovered-claimant-session', 'a stale row cannot clear the authoritative session id');
|
|
254
263
|
assert.equal(recovered.pi_session_file, recoveredSessionFile, 'a stale row cannot clear the authoritative session file');
|
|
255
|
-
assert.throws(() => raw.prepare(
|
|
264
|
+
assert.throws(() => raw.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES ('later-duplicate', 'later-duplicate-session', 'later-claimant', ?, NULL, NULL, '2026-01-02T00:00:00.000Z', '2026-01-02T00:00:00.000Z', NULL)").run(sessionFile), /UNIQUE/, 'the SQL session table enforces the repaired invariant');
|
|
256
265
|
raw.close();
|
|
257
266
|
});
|
|
258
267
|
test('forward session-file repair reprojects a surviving v36 meta coordinate before restoring its UNIQUE index', () => {
|
|
@@ -280,7 +289,7 @@ test('forward session-file repair reprojects a surviving v36 meta coordinate bef
|
|
|
280
289
|
raw.exec('DROP INDEX IF EXISTS idx_nodes_pi_session_file; CREATE UNIQUE INDEX idx_nodes_pi_session_file ON nodes(pi_session_file); PRAGMA user_version = 36;');
|
|
281
290
|
assert.doesNotThrow(() => migrate(raw));
|
|
282
291
|
assert.equal(userVersion(raw), MIGRATIONS.length);
|
|
283
|
-
const rows = raw.prepare('SELECT node_id, pi_session_file FROM
|
|
292
|
+
const rows = raw.prepare('SELECT node_id, pi_session_file FROM node_rows ORDER BY created, node_id').all();
|
|
284
293
|
assert.deepEqual(rows.map((row) => [row.node_id, row.pi_session_file]), [
|
|
285
294
|
['surviving-owner', recoveredSessionFile],
|
|
286
295
|
['stale-row', null],
|
|
@@ -288,7 +297,7 @@ test('forward session-file repair reprojects a surviving v36 meta coordinate bef
|
|
|
288
297
|
const survivor = JSON.parse(readFileSync(join(home, 'nodes', 'surviving-owner', 'meta.json'), 'utf8'));
|
|
289
298
|
assert.equal(survivor.pi_session_id, 'surviving-session');
|
|
290
299
|
assert.equal(survivor.pi_session_file, recoveredSessionFile);
|
|
291
|
-
assert.throws(() => raw.prepare(
|
|
300
|
+
assert.throws(() => raw.prepare("INSERT INTO sessions (binding_id, session_id, node_id, transcript_path, source_session_id, fork_entry_id, created_at, bound_at, unbound_at) VALUES ('stale-duplicate', NULL, 'stale-row', ?, NULL, NULL, '2026-01-02T00:00:00.000Z', '2026-01-02T00:00:00.000Z', NULL)").run(recoveredSessionFile), /UNIQUE/);
|
|
292
301
|
raw.close();
|
|
293
302
|
});
|
|
294
303
|
test('node lifecycle substrate migration adds six columns and backfills terminal reasons', () => {
|
|
@@ -314,7 +323,7 @@ test('node lifecycle substrate migration adds six columns and backfills terminal
|
|
|
314
323
|
insert.run('crashed', 'crashed', 'general', 'dead', null, null, '/tmp', created);
|
|
315
324
|
insert.run('active', 'active', 'general', 'active', null, null, '/tmp', created);
|
|
316
325
|
raw.close();
|
|
317
|
-
const db =
|
|
326
|
+
const db = openMigratedDb();
|
|
318
327
|
assert.equal(userVersion(db), MIGRATIONS.length);
|
|
319
328
|
for (const column of lifecycleColumns)
|
|
320
329
|
assert.ok(nodeColumns(db).includes(column));
|
|
@@ -331,8 +340,8 @@ test('node lifecycle substrate migration adds six columns and backfills terminal
|
|
|
331
340
|
// v4: edges FK ON DELETE CASCADE (the one irreversible rebuild)
|
|
332
341
|
/** Build a hand-rolled db at exactly v3 (baseline tables + the four runtime
|
|
333
342
|
* columns, edges with NO foreign key, `user_version = 3`) — the shape the live
|
|
334
|
-
* fleet reaches just before v4. Caller seeds it,
|
|
335
|
-
*
|
|
343
|
+
* fleet reaches just before v4. Caller seeds it, then explicitly runs v4 and
|
|
344
|
+
* later steps. */
|
|
336
345
|
function buildV3Db() {
|
|
337
346
|
ensureHome();
|
|
338
347
|
const raw = new DatabaseSync(canvasDbPath());
|
|
@@ -382,8 +391,8 @@ test('v4 migrates a db with a pre-existing orphan edge without throwing', () =>
|
|
|
382
391
|
assert.equal(userVersion(raw), 3);
|
|
383
392
|
assert.equal(edgeCount(raw), 2);
|
|
384
393
|
raw.close();
|
|
385
|
-
//
|
|
386
|
-
const db =
|
|
394
|
+
// Explicit migration must not throw on the orphan, and must drop it.
|
|
395
|
+
const db = openMigratedDb();
|
|
387
396
|
assert.equal(userVersion(db), MIGRATIONS.length); // migrated forward to the head
|
|
388
397
|
// The orphan edge is filtered; the non-orphan edge + its count are preserved.
|
|
389
398
|
assert.equal(edgeCount(db), 1);
|
|
@@ -435,7 +444,7 @@ test('bug #99: a stale v4 db with later columns already present migrates cleanly
|
|
|
435
444
|
assert.equal(userVersion(raw), 4);
|
|
436
445
|
assert.equal(nodeColumns(raw).includes('pane'), true, 'precondition: pane already exists even though user_version is stale');
|
|
437
446
|
raw.close();
|
|
438
|
-
const db =
|
|
447
|
+
const db = openMigratedDb();
|
|
439
448
|
assert.equal(userVersion(db), MIGRATIONS.length);
|
|
440
449
|
const cols = nodeColumns(db);
|
|
441
450
|
assert.ok(cols.includes('pane'), 'pane survives the migration');
|
|
@@ -536,7 +545,7 @@ test('v22 converts existing schedules into self-contained crons and removes the
|
|
|
536
545
|
payload: { kind: 'general', mode: 'base', cwd: '/tmp/work', prompt: 'go', parent: 'owner' },
|
|
537
546
|
});
|
|
538
547
|
raw.close();
|
|
539
|
-
const db =
|
|
548
|
+
const db = openMigratedDb();
|
|
540
549
|
assert.equal(userVersion(db), MIGRATIONS.length);
|
|
541
550
|
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => row.name);
|
|
542
551
|
assert.ok(!tables.includes('triggers'));
|
|
@@ -571,8 +580,7 @@ test('v22 converts existing schedules into self-contained crons and removes the
|
|
|
571
580
|
assert.match(birth.command, /crtr node new --kind 'general'/);
|
|
572
581
|
assert.match(birth.command, /go/);
|
|
573
582
|
});
|
|
574
|
-
// The v22 migration
|
|
575
|
-
// a throw would make every crtr process unable to open the canvas forever. These
|
|
583
|
+
// The explicit v22 migration must handle every declared-legal data shape. These
|
|
576
584
|
// cases are all shapes the retired schema declared legal.
|
|
577
585
|
function insertNodeAt(db, id, cwd, created, parent) {
|
|
578
586
|
db.prepare('INSERT INTO nodes (node_id, name, kind, status, cwd, parent, created) VALUES (?,?,?,?,?,?,?)').run(id, id, 'general', 'active', cwd, parent, created);
|
|
@@ -671,7 +679,7 @@ test('v22 converts the legal edge rows and records the ones it cannot express',
|
|
|
671
679
|
payload: { body: 'nope' },
|
|
672
680
|
});
|
|
673
681
|
raw.close();
|
|
674
|
-
const db =
|
|
682
|
+
const db = openMigratedDb();
|
|
675
683
|
assert.equal(userVersion(db), MIGRATIONS.length, 'the schema advances despite the unconvertible row');
|
|
676
684
|
const cron = (id) => db.prepare('SELECT * FROM crons WHERE cron_id = ?').get(id);
|
|
677
685
|
const pruned = cron('pruned-owner');
|
|
@@ -706,7 +714,7 @@ test('v22 re-run over already-converted crons converges instead of colliding on
|
|
|
706
714
|
VALUES ('scheduled-message', 'stale', 'owner', 'echo stale', '2026-01-01T00:00:00.000Z', '/tmp/work', '{}', '2020-01-01T00:00:00Z', '2020-01-01T00:00:00Z')
|
|
707
715
|
`).run();
|
|
708
716
|
raw.close();
|
|
709
|
-
const db =
|
|
717
|
+
const db = openMigratedDb();
|
|
710
718
|
assert.equal(userVersion(db), MIGRATIONS.length);
|
|
711
719
|
const row = db.prepare('SELECT command FROM crons WHERE cron_id = ?').get('scheduled-message');
|
|
712
720
|
assert.match(row.command, /crtr node message send --to 'target'/, 'the re-run replaces the half-written row');
|
|
@@ -754,8 +762,8 @@ test('v22 recovers a db wedged by an interrupted pre-atomicity run', () => {
|
|
|
754
762
|
`).run();
|
|
755
763
|
raw.exec('DROP TABLE triggers');
|
|
756
764
|
raw.close();
|
|
757
|
-
const db =
|
|
758
|
-
assert.equal(userVersion(db), MIGRATIONS.length, 'the schema advances rather than failing every
|
|
765
|
+
const db = openMigratedDb();
|
|
766
|
+
assert.equal(userVersion(db), MIGRATIONS.length, 'the schema advances rather than failing every explicit migration');
|
|
759
767
|
assert.notEqual(db.prepare('SELECT 1 FROM crons WHERE cron_id = ?').get('half-written'), undefined);
|
|
760
768
|
});
|
|
761
769
|
// The generated cron scripts are the migration's real product, so the M4
|
|
@@ -784,6 +792,112 @@ function runMigratedScript(script, opts) {
|
|
|
784
792
|
rmSync(dir, { recursive: true, force: true });
|
|
785
793
|
return { code: result.status ?? -1, log: written };
|
|
786
794
|
}
|
|
795
|
+
function buildV39Db() {
|
|
796
|
+
ensureHome();
|
|
797
|
+
const raw = new DatabaseSync(canvasDbPath());
|
|
798
|
+
migrateToVersion(raw, 39);
|
|
799
|
+
return raw;
|
|
800
|
+
}
|
|
801
|
+
function resetV39Home() {
|
|
802
|
+
closeDb();
|
|
803
|
+
rmSync(home, { recursive: true, force: true });
|
|
804
|
+
return buildV39Db();
|
|
805
|
+
}
|
|
806
|
+
function writeLegacyPage(id, source, dialect = 'jsx') {
|
|
807
|
+
const dir = join(home, 'nodes', id);
|
|
808
|
+
mkdirSync(dir, { recursive: true });
|
|
809
|
+
const document = dialect === 'jsx'
|
|
810
|
+
? 'export default function LegacyPage() { return <Page title="The gate is gone" subtitle="This page was sent with page_surface off — the refusal that used to block it no longer exists. Nothing to answer; it is here to prove delivery." placement="panel" />; }\n'
|
|
811
|
+
: '---\ntitle: The gate is gone\n---\nThis is the stored Markdown page.\n';
|
|
812
|
+
writeFileSync(join(dir, dialect === 'jsx' ? 'page.tsx' : 'page.md'), document);
|
|
813
|
+
writeFileSync(join(dir, 'page.json'), JSON.stringify({
|
|
814
|
+
schema: 'crtr.page/v1', dialect, title: 'The gate is gone',
|
|
815
|
+
subtitle: 'This page was sent with page_surface off — the refusal that used to block it no longer exists. Nothing to answer; it is here to prove delivery.',
|
|
816
|
+
placement: 'panel', document: dialect === 'jsx' ? 'page.tsx' : 'page.md', steps: 1, slots: [], source,
|
|
817
|
+
}));
|
|
818
|
+
return dir;
|
|
819
|
+
}
|
|
820
|
+
test('v40 imports pre-envelope v1 pages and still rejects invalid v2 delivery envelopes', () => {
|
|
821
|
+
const raw = buildV39Db();
|
|
822
|
+
const blocked = writeLegacyPage('page-bba481b2', {
|
|
823
|
+
sessionName: 'general', blockedSince: '2026-08-11T17:32:21.979Z', profileName: 'crouter', nodeId: '3zl47w7d-msowsany-ee8e2bba',
|
|
824
|
+
});
|
|
825
|
+
const emitted = writeLegacyPage('nfy-3b21bc8a', {
|
|
826
|
+
sessionName: 'notify', nodeId: '3zl47w7d-msmeye7h-cb3ff1da',
|
|
827
|
+
}, 'md');
|
|
828
|
+
const legacyBytes = readFileSync(join(blocked, 'page.json'));
|
|
829
|
+
migrate(raw);
|
|
830
|
+
for (const [dir, id, delivery] of [[blocked, 'page-bba481b2', { placement: 'panel', inbox: true, reply: true }], [emitted, 'nfy-3b21bc8a', { placement: 'panel', inbox: true, reply: false }]]) {
|
|
831
|
+
const stored = raw.prepare('SELECT presentation_dir, specification_json FROM human_requests WHERE request_id = ?').get(id);
|
|
832
|
+
const manifest = parsePage(stored.presentation_dir);
|
|
833
|
+
assert.deepEqual(manifest.delivery, delivery);
|
|
834
|
+
assert.deepEqual(parsePageDelivery(stored.presentation_dir), delivery);
|
|
835
|
+
assert.deepEqual(JSON.parse(stored.specification_json), manifest);
|
|
836
|
+
}
|
|
837
|
+
assert.deepEqual(readFileSync(join(blocked, 'page.json')), legacyBytes, 'the source manifest remains untouched');
|
|
838
|
+
raw.close();
|
|
839
|
+
const invalid = resetV39Home();
|
|
840
|
+
const valid = writeLegacyPage('valid-v1-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
|
|
841
|
+
const validBytes = readFileSync(join(valid, 'page.json'));
|
|
842
|
+
const dir = writeLegacyPage('broken-v2-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
|
|
843
|
+
writeFileSync(join(dir, 'page.json'), JSON.stringify({ schema: 'crtr.page/v2', title: 'Broken', delivery: { placement: 'panel', inbox: true, reply: false, extra: true } }));
|
|
844
|
+
assert.throws(() => migrate(invalid), (error) => error instanceof Error && error.message.includes(join(dir, 'page.json')));
|
|
845
|
+
assert.equal(userVersion(invalid), 39, 'the failed migration rolls back');
|
|
846
|
+
assert.deepEqual(readFileSync(join(valid, 'page.json')), validBytes, 'a failed migration does not alter an earlier v1 input');
|
|
847
|
+
invalid.close();
|
|
848
|
+
const malformedV2 = resetV39Home();
|
|
849
|
+
const malformedV2Dir = writeLegacyPage('malformed-v2-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
|
|
850
|
+
writeFileSync(join(malformedV2Dir, 'page.json'), JSON.stringify({ schema: 'crtr.page/v2', title: 'Broken', delivery: { placement: 'panel', inbox: true, reply: false } }));
|
|
851
|
+
assert.throws(() => migrate(malformedV2), (error) => error instanceof Error && error.message.includes(join(malformedV2Dir, 'page.json')));
|
|
852
|
+
malformedV2.close();
|
|
853
|
+
const malformedV1 = resetV39Home();
|
|
854
|
+
const malformedV1Dir = writeLegacyPage('malformed-v1-page', { blockedSince: '2026-08-11T17:32:21.979Z' });
|
|
855
|
+
writeFileSync(join(malformedV1Dir, 'page.json'), JSON.stringify({
|
|
856
|
+
schema: 'crtr.page/v1', dialect: 'jsx', title: 'Broken', placement: 'panel', document: 'page.tsx', steps: 1,
|
|
857
|
+
slots: [{ id: 'out-of-range', kind: 'options', step: 1, config: {} }], source: { blockedSince: '2026-08-11T17:32:21.979Z' },
|
|
858
|
+
}));
|
|
859
|
+
assert.throws(() => migrate(malformedV1), (error) => error instanceof Error && error.message.includes(join(malformedV1Dir, 'page.json')));
|
|
860
|
+
malformedV1.close();
|
|
861
|
+
const malformedMarkdown = resetV39Home();
|
|
862
|
+
const malformedMarkdownDir = writeLegacyPage('malformed-markdown-page', {}, 'md');
|
|
863
|
+
const malformedMarkdownManifest = JSON.parse(readFileSync(join(malformedMarkdownDir, 'page.json'), 'utf8'));
|
|
864
|
+
malformedMarkdownManifest.steps = 2;
|
|
865
|
+
writeFileSync(join(malformedMarkdownDir, 'page.json'), JSON.stringify(malformedMarkdownManifest));
|
|
866
|
+
assert.throws(() => migrate(malformedMarkdown), (error) => error instanceof Error && error.message.includes(join(malformedMarkdownDir, 'page.json')));
|
|
867
|
+
malformedMarkdown.close();
|
|
868
|
+
});
|
|
869
|
+
test('v40 skips recovery witnesses without identity while retaining failures for indexed nodes', () => {
|
|
870
|
+
const raw = buildV39Db();
|
|
871
|
+
const faultDir = join(home, 'nodes', 'orphan-fault', 'job');
|
|
872
|
+
const retryDir = join(home, 'nodes', 'orphan-retry', 'job');
|
|
873
|
+
mkdirSync(faultDir, { recursive: true });
|
|
874
|
+
mkdirSync(retryDir, { recursive: true });
|
|
875
|
+
writeFileSync(join(faultDir, 'fault'), JSON.stringify({
|
|
876
|
+
link: 'viewer↔broker', op: 'connect', kind: 'connection', message: 'ENOENT', since: '2026-09-10T00:00:00.000Z', operation_id: '11111111111111111111111111111111', retry: { disposition: 'fatal' },
|
|
877
|
+
}));
|
|
878
|
+
writeFileSync(join(retryDir, 'provider-retry'), JSON.stringify({
|
|
879
|
+
state: 'pending', sessionFile: '/tmp/orphan-retry.jsonl', anchorEntryId: null,
|
|
880
|
+
fault: { link: 'pi→provider', op: 'request', kind: 'connection', message: 'ECONNRESET', since: '2026-09-10T00:00:00.000Z', operation_id: '22222222222222222222222222222222', retry: { disposition: 'auto', by: 'daemon' } },
|
|
881
|
+
}));
|
|
882
|
+
const resident = join(home, 'nodes', 'metadata-node');
|
|
883
|
+
mkdirSync(join(resident, 'job'), { recursive: true });
|
|
884
|
+
writeFileSync(join(resident, 'meta.json'), JSON.stringify({
|
|
885
|
+
node_id: 'metadata-node', name: 'metadata-node', created: '2026-09-10T00:00:00.000Z', cwd: '/tmp', kind: 'developer', mode: 'base', lifecycle: 'terminal',
|
|
886
|
+
}));
|
|
887
|
+
writeFileSync(join(resident, 'job', 'busy'), '');
|
|
888
|
+
migrate(raw);
|
|
889
|
+
assert.notEqual(raw.prepare('SELECT 1 FROM nodes WHERE node_id = ?').get('metadata-node'), undefined);
|
|
890
|
+
assert.equal(raw.prepare('SELECT COUNT(*) AS count FROM executions WHERE node_id = ?').get('metadata-node').count, 1);
|
|
891
|
+
assert.equal(raw.prepare("SELECT 1 FROM nodes WHERE node_id IN ('orphan-fault', 'orphan-retry')").get(), undefined);
|
|
892
|
+
raw.close();
|
|
893
|
+
const invalid = resetV39Home();
|
|
894
|
+
insertNode(invalid, 'indexed-node');
|
|
895
|
+
const invalidPath = join(home, 'nodes', 'indexed-node', 'job', 'fault');
|
|
896
|
+
mkdirSync(dirname(invalidPath), { recursive: true });
|
|
897
|
+
writeFileSync(invalidPath, 'not json');
|
|
898
|
+
assert.throws(() => migrate(invalid), (error) => error instanceof Error && error.message.includes(invalidPath));
|
|
899
|
+
invalid.close();
|
|
900
|
+
});
|
|
787
901
|
test('a migrated predicate script delivers its exact body and self-cancels only when the action succeeds', () => {
|
|
788
902
|
const raw = buildV21Db();
|
|
789
903
|
insertLegacySchedule(raw, {
|
|
@@ -811,7 +925,7 @@ test('a migrated predicate script delivers its exact body and self-cancels only
|
|
|
811
925
|
timeoutAt: '2026-01-04T00:00:00.000Z',
|
|
812
926
|
});
|
|
813
927
|
raw.close();
|
|
814
|
-
const db =
|
|
928
|
+
const db = openMigratedDb();
|
|
815
929
|
const script = (id) => db.prepare('SELECT command FROM crons WHERE cron_id = ?').get(id).command;
|
|
816
930
|
const stored = script('conditional-message');
|
|
817
931
|
const satisfied = runMigratedScript(stored, { gateReady: true });
|
|
@@ -20,9 +20,10 @@ import { join } from 'node:path';
|
|
|
20
20
|
import { buildLaunchSpec, isProviderPinnedModelToken, normalizeModel } from '../runtime/launch.js';
|
|
21
21
|
import { defaultModelLaddersConfig } from '../../types.js';
|
|
22
22
|
import { persistDormantModel } from '../runtime/model-swap.js';
|
|
23
|
+
import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
|
|
23
24
|
import { promote, reshapeNode } from '../runtime/promote.js';
|
|
24
25
|
import { createNode, getNode } from '../canvas/canvas.js';
|
|
25
|
-
import { closeDb } from '../canvas/db.js';
|
|
26
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
26
27
|
const ladders = defaultModelLaddersConfig();
|
|
27
28
|
// The concrete cell the `ultra` tier normalizes to — the byte-equal collision at
|
|
28
29
|
// the heart of #328. A raw `ultra` token is UNpinned; this concrete cell is
|
|
@@ -36,9 +37,11 @@ before(() => {
|
|
|
36
37
|
home = mkdtempSync(join(tmpdir(), 'crtr-model-pin-durability-'));
|
|
37
38
|
process.env['HOME'] = home; // buildLaunchSpec reads merged launch config here
|
|
38
39
|
process.env['CRTR_HOME'] = home; // the canvas db lives here
|
|
40
|
+
initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
|
|
39
41
|
});
|
|
40
42
|
beforeEach(() => {
|
|
41
43
|
closeDb();
|
|
44
|
+
migrate(openDb());
|
|
42
45
|
});
|
|
43
46
|
after(() => {
|
|
44
47
|
closeDb();
|