@north-light/crouter 0.3.312 → 0.3.314
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 +757 -746
- 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/fresh-home-commands.test.js +49 -0
- 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/command.js +11 -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 +18 -0
- package/dist/core/scoped-state/db.js +135 -0
- package/dist/core/scoped-state/migrate.d.ts +8 -0
- package/dist/core/scoped-state/migrate.js +181 -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.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
- /package/dist/core/__tests__/{human-deliver.test.d.ts → integration/fresh-home-commands.test.d.ts} +0 -0
|
@@ -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();
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { after, afterEach, before, beforeEach, test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync,
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { persistModelRoute, readConfig, readMergedLaunchConfig } from '../config.js';
|
|
6
|
+
import { persistModelRoute, readConfig, readMergedLaunchConfig, readRawConfigAtRoot, writeRawConfig } from '../config.js';
|
|
7
|
+
import { initializeFreshScopedStore } from '../scoped-state/migrate.js';
|
|
7
8
|
import { expandModelCandidates } from '../model-routes.js';
|
|
9
|
+
import { piAgentDir } from '../runtime/package-health.js';
|
|
8
10
|
import { resetScopeCache } from '../scope.js';
|
|
9
11
|
let home;
|
|
10
12
|
let work;
|
|
@@ -23,6 +25,10 @@ function route(providerId = 'provider-x', family = 'family-x', credentialSource
|
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
function writeConfig(root, config) {
|
|
28
|
+
if (root === join(home, '.crouter')) {
|
|
29
|
+
writeRawConfig('user', config);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
26
32
|
mkdirSync(root, { recursive: true });
|
|
27
33
|
writeFileSync(join(root, 'config.json'), JSON.stringify(config));
|
|
28
34
|
}
|
|
@@ -34,6 +40,8 @@ beforeEach(() => {
|
|
|
34
40
|
work = mkdtempSync(join(tmpdir(), 'crtr-model-routes-work-'));
|
|
35
41
|
process.env.HOME = home;
|
|
36
42
|
resetScopeCache();
|
|
43
|
+
initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
|
|
44
|
+
initializeFreshScopedStore({ kind: 'provider', root: piAgentDir() });
|
|
37
45
|
});
|
|
38
46
|
afterEach(() => {
|
|
39
47
|
resetScopeCache();
|
|
@@ -87,7 +95,7 @@ test('route records retain __proto__ ids when read and persisted', () => {
|
|
|
87
95
|
writeConfig(root, JSON.parse(JSON.stringify({ modelRoutes: { ['__proto__']: route() } })));
|
|
88
96
|
assert.deepEqual(readMergedLaunchConfig(work, null).modelRoutes?.['__proto__'], route());
|
|
89
97
|
persistModelRoute(root, '__proto__', route('persisted-provider'));
|
|
90
|
-
const raw =
|
|
98
|
+
const raw = readRawConfigAtRoot(root);
|
|
91
99
|
assert.deepEqual(raw.modelRoutes['__proto__'], route('persisted-provider'));
|
|
92
100
|
});
|
|
93
101
|
test('route candidate identity distinguishes delimiter-containing route and provider ids', () => {
|
|
@@ -131,12 +139,12 @@ test('persistModelRoute touches only its raw route and deletes by restoring inhe
|
|
|
131
139
|
const other = route('other-provider');
|
|
132
140
|
writeConfig(root, { modelRoutes: { other }, max_panes_per_window: 7 });
|
|
133
141
|
persistModelRoute(root, 'edited', route('edited-provider'));
|
|
134
|
-
let raw =
|
|
142
|
+
let raw = readRawConfigAtRoot(root);
|
|
135
143
|
assert.deepEqual(raw.modelRoutes.other, other);
|
|
136
144
|
assert.deepEqual(raw.modelRoutes.edited, route('edited-provider'));
|
|
137
145
|
assert.equal(raw.modelLadders, undefined);
|
|
138
146
|
assert.equal(raw.max_panes_per_window, 7);
|
|
139
147
|
persistModelRoute(root, 'edited', undefined);
|
|
140
|
-
raw =
|
|
148
|
+
raw = readRawConfigAtRoot(root);
|
|
141
149
|
assert.deepEqual(raw.modelRoutes, { other });
|
|
142
150
|
});
|
|
@@ -4,15 +4,80 @@ import { DatabaseSync } from 'node:sqlite';
|
|
|
4
4
|
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
|
-
import { closeDb, MIGRATIONS } from '../canvas/db.js';
|
|
8
|
-
import { createNode, getRow } from '../canvas/canvas.js';
|
|
9
|
-
import {
|
|
7
|
+
import { closeDb, MIGRATIONS, migrate, openDb, withCanvasWrite } from '../canvas/db.js';
|
|
8
|
+
import { createNode, getNode, getRow } from '../canvas/canvas.js';
|
|
9
|
+
import { activateArmedExternalActionDelivery, armExternalActionDelivery, claimExternalActionDelivery, messageBodyForDelivery, readExternalActionDelivery, resetHandedOffDeliveries, settleDelivery, } from '../canvas/deliveries.js';
|
|
10
10
|
import { transition } from '../runtime/lifecycle.js';
|
|
11
11
|
import { composeNodeOutcomeDocument } from '../runtime/outcome-document.js';
|
|
12
12
|
import { pushFinal } from '../feed/feed.js';
|
|
13
13
|
import { OUTCOME_PAYLOAD_MAX_BYTES } from '../canvas/types.js';
|
|
14
14
|
let home;
|
|
15
15
|
const priorHome = process.env['CRTR_HOME'];
|
|
16
|
+
function outcomeKey(nodeId) {
|
|
17
|
+
return `node-outcome:${nodeId}`;
|
|
18
|
+
}
|
|
19
|
+
function outcomeDelivery(nodeId) {
|
|
20
|
+
return readExternalActionDelivery(outcomeKey(nodeId));
|
|
21
|
+
}
|
|
22
|
+
function nodeAtOutcome(node) {
|
|
23
|
+
const reason = node.outcome_reason;
|
|
24
|
+
let status;
|
|
25
|
+
let intent;
|
|
26
|
+
if (node.outcome_kind === 'result' || reason === 'finished' || reason === 'declined') {
|
|
27
|
+
status = 'done';
|
|
28
|
+
intent = 'done';
|
|
29
|
+
}
|
|
30
|
+
else if (reason === 'parked') {
|
|
31
|
+
status = 'done';
|
|
32
|
+
intent = 'parked';
|
|
33
|
+
}
|
|
34
|
+
else if (reason === 'closed' || reason === 'retired' || reason === 'stranded' || reason === 'deadline_exceeded') {
|
|
35
|
+
status = 'canceled';
|
|
36
|
+
intent = null;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
status = 'dead';
|
|
40
|
+
intent = null;
|
|
41
|
+
}
|
|
42
|
+
return { ...node, status, intent, terminal_reason: reason };
|
|
43
|
+
}
|
|
44
|
+
function armOutcomeAction(input) {
|
|
45
|
+
const payload = input.payload ?? null;
|
|
46
|
+
const json = JSON.stringify(payload);
|
|
47
|
+
if (json === undefined)
|
|
48
|
+
throw new TypeError('outcome delivery payload must be JSON-serializable');
|
|
49
|
+
if (Buffer.byteLength(json, 'utf8') > OUTCOME_PAYLOAD_MAX_BYTES)
|
|
50
|
+
throw new RangeError(`outcome delivery payload exceeds ${OUTCOME_PAYLOAD_MAX_BYTES} bytes`);
|
|
51
|
+
return withCanvasWrite((db) => {
|
|
52
|
+
const existing = outcomeDelivery(input.nodeId);
|
|
53
|
+
if (existing !== null && existing.state !== 'armed')
|
|
54
|
+
return null;
|
|
55
|
+
armExternalActionDelivery(db, {
|
|
56
|
+
destinationId: input.actionName,
|
|
57
|
+
adapter: { adapter: 'subprocess', argv: input.argv, cwd: input.cwd, config_origin: input.configOrigin, payload },
|
|
58
|
+
idempotencyKey: outcomeKey(input.nodeId),
|
|
59
|
+
}, input.now);
|
|
60
|
+
const node = getNode(input.nodeId);
|
|
61
|
+
if (node === null)
|
|
62
|
+
throw new Error(`unknown node ${input.nodeId}`);
|
|
63
|
+
if (node.outcome_at !== null) {
|
|
64
|
+
if (node.outcome_kind === null || node.outcome_reason === null)
|
|
65
|
+
throw new Error(`outcome latch is incomplete for node ${input.nodeId}`);
|
|
66
|
+
activateArmedExternalActionDelivery(db, {
|
|
67
|
+
idempotencyKey: outcomeKey(input.nodeId),
|
|
68
|
+
label: input.nodeId,
|
|
69
|
+
bodyJson: JSON.stringify(composeNodeOutcomeDocument({
|
|
70
|
+
node: nodeAtOutcome(node),
|
|
71
|
+
outcome: { kind: node.outcome_kind, reason: node.outcome_reason, at: node.outcome_at, revision: node.outcome_revision, detailJson: node.outcome_detail_json ?? null },
|
|
72
|
+
action: { name: input.actionName, payload },
|
|
73
|
+
finalReport: node.final_report,
|
|
74
|
+
})),
|
|
75
|
+
supersede: false,
|
|
76
|
+
}, input.now);
|
|
77
|
+
}
|
|
78
|
+
return outcomeDelivery(input.nodeId);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
16
81
|
function node(nodeId) {
|
|
17
82
|
return {
|
|
18
83
|
node_id: nodeId,
|
|
@@ -29,6 +94,7 @@ beforeEach(() => {
|
|
|
29
94
|
closeDb();
|
|
30
95
|
home = mkdtempSync(join(tmpdir(), 'crtr-node-outcome-'));
|
|
31
96
|
process.env['CRTR_HOME'] = home;
|
|
97
|
+
migrate(openDb());
|
|
32
98
|
});
|
|
33
99
|
afterEach(() => {
|
|
34
100
|
closeDb();
|
|
@@ -105,23 +171,24 @@ test('a canonical final supersedes a prior failure outcome and increments revisi
|
|
|
105
171
|
});
|
|
106
172
|
test('outcome delivery rejects an oversized payload before a node is armed', () => {
|
|
107
173
|
createNode(node('oversized-payload'));
|
|
108
|
-
assert.throws(() =>
|
|
174
|
+
assert.throws(() => armOutcomeAction({
|
|
109
175
|
nodeId: 'oversized-payload', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { value: 'x'.repeat(OUTCOME_PAYLOAD_MAX_BYTES) },
|
|
110
176
|
}), /exceeds/);
|
|
111
|
-
assert.equal(
|
|
177
|
+
assert.equal(outcomeDelivery('oversized-payload'), null);
|
|
112
178
|
});
|
|
113
179
|
test('outcome delivery armed after settlement is born pending with its document', () => {
|
|
114
180
|
createNode(node('settled-delivery'));
|
|
115
181
|
transition('settled-delivery', 'crash');
|
|
116
|
-
const delivery =
|
|
182
|
+
const delivery = armOutcomeAction({
|
|
117
183
|
nodeId: 'settled-delivery', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { callback: 'one' }, now: 123,
|
|
118
184
|
});
|
|
119
185
|
assert.equal(delivery?.state, 'pending');
|
|
120
186
|
assert.equal(delivery?.nextAttemptAt, 123);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
187
|
+
const document = delivery === null ? null : messageBodyForDelivery(delivery.deliveryId);
|
|
188
|
+
assert.deepEqual(document && {
|
|
189
|
+
schema: document.schema,
|
|
190
|
+
outcomeId: document.outcomeId,
|
|
191
|
+
action: document.action,
|
|
125
192
|
}, {
|
|
126
193
|
schema: 'crtr.node-outcome/v1',
|
|
127
194
|
outcomeId: 'settled-delivery:1',
|
|
@@ -130,28 +197,28 @@ test('outcome delivery armed after settlement is born pending with its document'
|
|
|
130
197
|
});
|
|
131
198
|
test('outcome delivery state is fenced, recoverable, and follows the durable backoff', () => {
|
|
132
199
|
createNode(node('delivery'));
|
|
133
|
-
assert.ok(
|
|
200
|
+
assert.ok(armOutcomeAction({
|
|
134
201
|
nodeId: 'delivery', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', payload: { callback: 'one' }, now: 100,
|
|
135
202
|
}));
|
|
136
203
|
transition('delivery', 'crash');
|
|
137
|
-
assert.equal(
|
|
138
|
-
assert.ok(
|
|
139
|
-
const first =
|
|
140
|
-
assert.equal(first.state, '
|
|
141
|
-
assert.equal(
|
|
142
|
-
assert.equal(
|
|
143
|
-
assert.equal(
|
|
204
|
+
assert.equal(outcomeDelivery('delivery')?.state, 'pending');
|
|
205
|
+
assert.ok(messageBodyForDelivery(outcomeDelivery('delivery').deliveryId), 'the armed row is stamped with its outcome document');
|
|
206
|
+
const first = claimExternalActionDelivery(outcomeDelivery('delivery').deliveryId, Date.now(), 'daemon-a');
|
|
207
|
+
assert.equal(first.state, 'handed_off');
|
|
208
|
+
assert.equal(settleDelivery(first.deliveryId, first.attempts, 'daemon-b', { state: 'accepted' }, Date.now()), false, 'a stale claimant cannot settle');
|
|
209
|
+
assert.equal(settleDelivery(first.deliveryId, first.attempts, 'daemon-a', { state: 'accepted' }, Date.now()), true);
|
|
210
|
+
assert.equal(outcomeDelivery('delivery')?.state, 'acknowledged');
|
|
144
211
|
createNode(node('recover'));
|
|
145
|
-
|
|
212
|
+
armOutcomeAction({ nodeId: 'recover', actionName: 'test-action', argv: ['/bin/true'], cwd: home, configOrigin: 'test', now: 100 });
|
|
146
213
|
transition('recover', 'crash');
|
|
147
|
-
const running =
|
|
148
|
-
assert.equal(running.state, '
|
|
214
|
+
const running = claimExternalActionDelivery(outcomeDelivery('recover').deliveryId, Date.now(), 'daemon-a');
|
|
215
|
+
assert.equal(running.state, 'handed_off');
|
|
149
216
|
const recoveredAt = Date.now();
|
|
150
|
-
assert.equal(
|
|
151
|
-
assert.deepEqual(
|
|
152
|
-
state:
|
|
153
|
-
nextAttemptAt:
|
|
154
|
-
claimOwner:
|
|
217
|
+
assert.equal(resetHandedOffDeliveries(recoveredAt), 1);
|
|
218
|
+
assert.deepEqual(outcomeDelivery('recover') && {
|
|
219
|
+
state: outcomeDelivery('recover').state,
|
|
220
|
+
nextAttemptAt: outcomeDelivery('recover').nextAttemptAt,
|
|
221
|
+
claimOwner: outcomeDelivery('recover').claimOwner,
|
|
155
222
|
}, { state: 'pending', nextAttemptAt: recoveredAt, claimOwner: null });
|
|
156
223
|
});
|
|
157
224
|
test('v39 migrates a v38 database and backfills finalized and terminal rows', () => {
|
|
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
|
|
|
10
10
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
-
import { closeDb } from '../canvas/db.js';
|
|
13
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
14
14
|
import { resetScopeCache } from '../scope.js';
|
|
15
15
|
import { spawnNode } from '../runtime/nodes.js';
|
|
16
16
|
import { renderOnReadDocs } from '../substrate/on-read-node.js';
|
|
@@ -32,6 +32,7 @@ beforeEach(() => {
|
|
|
32
32
|
// touch the fake user home; the read path we test is constructed by hand.
|
|
33
33
|
canvasHome = mkdtempSync(join(tmpdir(), 'crtr-fence-canvas-'));
|
|
34
34
|
process.env['CRTR_HOME'] = canvasHome;
|
|
35
|
+
migrate(openDb());
|
|
35
36
|
});
|
|
36
37
|
after(() => {
|
|
37
38
|
closeDb();
|
|
@@ -10,7 +10,7 @@ import assert from 'node:assert/strict';
|
|
|
10
10
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
-
import { closeDb } from '../canvas/db.js';
|
|
13
|
+
import { closeDb, migrate, openDb } from '../canvas/db.js';
|
|
14
14
|
import { resetScopeCache } from '../scope.js';
|
|
15
15
|
import { spawnNode } from '../runtime/nodes.js';
|
|
16
16
|
import { renderOnReadDocs } from '../substrate/on-read-node.js';
|
|
@@ -44,6 +44,7 @@ beforeEach(() => {
|
|
|
44
44
|
else
|
|
45
45
|
process.env['HOME'] = prevHomeEnv;
|
|
46
46
|
work = mkdtempSync(join(tmpdir(), 'crtr-onread-work-'));
|
|
47
|
+
migrate(openDb());
|
|
47
48
|
});
|
|
48
49
|
after(() => {
|
|
49
50
|
closeDb();
|