@north-light/crouter 0.3.311 → 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 +17 -5
- 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 +312 -1
- 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 +62 -19
- 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 +4 -3
- package/dist/core/__tests__/on-read-dedup-resume.test.js +8 -7
- package/dist/core/__tests__/on-read-nested-store.test.js +8 -7
- 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 +35 -32
- 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 +5 -4
- 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 +19 -5
- 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-hooks/artifact.d.ts +6 -0
- package/dist/core/command-hooks/artifact.js +22 -6
- package/dist/core/command-hooks/discovery.d.ts +2 -0
- package/dist/core/command-hooks/discovery.js +2 -0
- package/dist/core/command-hooks/lifecycle-catalog.d.ts +20 -2
- package/dist/core/command-hooks/lifecycle-catalog.js +7 -2
- package/dist/core/command-hooks/lifecycle-create.d.ts +4 -0
- package/dist/core/command-hooks/lifecycle-create.js +51 -0
- package/dist/core/command-hooks/schema.d.ts +2 -2
- package/dist/core/command-hooks/schema.js +5 -4
- package/dist/core/command-hooks/transport/exec-invoke.js +3 -1
- package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +18 -8
- package/dist/core/command-hooks/transport/exec-lifecycle.js +89 -30
- package/dist/core/config.d.ts +17 -12
- package/dist/core/config.js +106 -155
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +4 -0
- 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 +4 -3
- 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/companion.d.ts +1 -1
- package/dist/core/review/companion.js +2 -2
- 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/nodes.d.ts +5 -1
- package/dist/core/runtime/nodes.js +26 -11
- package/dist/core/runtime/placement.js +2 -2
- package/dist/core/runtime/recycle.js +24 -14
- package/dist/core/runtime/reset.js +26 -7
- package/dist/core/runtime/revive.js +226 -217
- package/dist/core/runtime/spawn.d.ts +4 -1
- package/dist/core/runtime/spawn.js +43 -5
- package/dist/core/runtime/warm-pool.d.ts +2 -2
- package/dist/core/runtime/warm-pool.js +23 -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 +41 -17
- 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 +6 -3
- 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 +29 -43
- 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 -148
- 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.d.ts +1 -0
- package/dist/daemon/api/handlers/nodes.js +41 -42
- 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/hook-authoring.d.ts +28 -2
- package/dist/hook-authoring.js +53 -1
- package/dist/index.d.ts +3 -2
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { getNode,
|
|
3
|
+
import { getNode, recordDisposalGrant, recordLandIntent } from './canvas/index.js';
|
|
4
4
|
import { withExclusiveDirectoryLockAsync } from './exclusive-lock.js';
|
|
5
5
|
import { gitAsync } from './git.js';
|
|
6
6
|
import { proveBranchContained } from './worktree-containment.js';
|
|
@@ -66,8 +66,7 @@ function recordBlockedLandIntent(nodeId, blocked) {
|
|
|
66
66
|
const current = getNode(nodeId)?.managed_worktree;
|
|
67
67
|
if (current === undefined || current === null || current.state !== 'open')
|
|
68
68
|
return;
|
|
69
|
-
|
|
70
|
-
updateNode(nodeId, { managed_worktree: { ...retained, land_intent: { at: new Date().toISOString(), blocked } } });
|
|
69
|
+
recordLandIntent(nodeId, { at: new Date().toISOString(), blocked });
|
|
71
70
|
}
|
|
72
71
|
async function dropStashIfStillTip(cwd, stashSha) {
|
|
73
72
|
const tip = await gitAsync(['rev-parse', '--verify', '--quiet', 'refs/stash'], cwd);
|
|
@@ -144,7 +143,8 @@ async function closeLocked(nodeId) {
|
|
|
144
143
|
if (!(await registeredWorktree(wt.repo_root, wt.path, branch)) || (await runGit(wt.path, ['rev-parse', '--abbrev-ref', 'HEAD'], 'branch_check_failed', 'Inspect the worktree manually, then retry.')) !== branch || (await runGit(wt.path, ['rev-parse', 'HEAD'], 'branch_check_failed', 'Inspect the worktree manually, then retry.')) !== head) {
|
|
145
144
|
throw new WorktreeError('wrong_branch', `managed worktree at ${wt.path} changed while closing`, `Inspect ${wt.path}, then rerun \`crtr node worktree close\`.`);
|
|
146
145
|
}
|
|
147
|
-
|
|
146
|
+
recordDisposalGrant(nodeId, { observed_branch: branch, observed_sha: head, common_dir: commonDir, authorized: new Date().toISOString() });
|
|
147
|
+
persistClosedWorktreeState(nodeId, wt, 'pending');
|
|
148
148
|
return { node_id: nodeId, branch, worktree_path: wt.path, landed_sha: head, ...deferredCleanupResult(wt), branch_delete_error: `${branch} is not a crtr-managed branch and will never be deleted automatically; it is preserved once the checkout above is removed.` };
|
|
149
149
|
}
|
|
150
150
|
// Decide whether landing remains before resolving a destination. A diverged
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { abandonWorktreeAndRepoint, getNode } from './canvas/index.js';
|
|
4
4
|
import { isPidAlive } from './canvas/pid.js';
|
|
5
5
|
import { CONFIG_FILE, CRTR_DIR_NAME, STATE_FILE } from '../types.js';
|
|
6
6
|
import { withExclusiveDirectoryLockAsync } from './exclusive-lock.js';
|
|
@@ -211,7 +211,7 @@ async function rebaseInProgress(cwd) {
|
|
|
211
211
|
return false;
|
|
212
212
|
}
|
|
213
213
|
function persistAbandoned(nodeId, wt, tip, at, by, branch = wt.branch) {
|
|
214
|
-
|
|
214
|
+
abandonWorktreeAndRepoint(nodeId, { cwd: wt.repo_root, abandoned: { branch, tip_sha: tip, at, by } });
|
|
215
215
|
}
|
|
216
216
|
export async function abandonManagedWorktreeAsync(nodeId, by) {
|
|
217
217
|
const wt = getNode(nodeId)?.managed_worktree;
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
import { createHash } from 'node:crypto';
|
|
19
19
|
import { existsSync, realpathSync } from 'node:fs';
|
|
20
20
|
import { resolve } from 'node:path';
|
|
21
|
-
import { getNode,
|
|
21
|
+
import { completeCleanup, getNode, recordSweepAttempt } from './canvas/index.js';
|
|
22
22
|
import { withExclusiveDirectoryLockAsync } from './exclusive-lock.js';
|
|
23
23
|
import { gitAsync } from './git.js';
|
|
24
24
|
import { proveBranchContained, upstreamTip } from './worktree-containment.js';
|
|
25
25
|
import { parseStatusPorcelain, planLandIntoCheckout, refDeleteTransactionStdin } from './worktree-landing.js';
|
|
26
26
|
import { shellQuote } from '../shared/shell-quote.js';
|
|
27
|
-
import { WORKTREE_LOCK_WAIT_MS, checkoutPathForBranchInList,
|
|
27
|
+
import { WORKTREE_LOCK_WAIT_MS, checkoutPathForBranchInList, samePath, worktreeLockPathForCommonDir } from './worktree.js';
|
|
28
28
|
/** Escalating recheck interval, in minutes, indexed by consecutive refusal. The
|
|
29
29
|
* last tier is the cap AND the quarantine threshold: at that point the record
|
|
30
30
|
* needs a human, so it is listed by `crtr sys worktrees` and costs one cheap
|
|
@@ -147,19 +147,14 @@ function recordUnfinished(nodeId, wt, outcome, observed, now, guard) {
|
|
|
147
147
|
const current = getNode(nodeId)?.managed_worktree;
|
|
148
148
|
if (current == null || !isReconcilable(current))
|
|
149
149
|
return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
last_attempt: new Date(now).toISOString(),
|
|
159
|
-
next_attempt: new Date(now + minutes * 60_000).toISOString(),
|
|
160
|
-
...(attempts >= QUARANTINE_ATTEMPTS ? { quarantined: true } : {}),
|
|
161
|
-
},
|
|
162
|
-
},
|
|
150
|
+
recordSweepAttempt(nodeId, {
|
|
151
|
+
reason: outcome.reason,
|
|
152
|
+
...(outcome.detail === undefined ? {} : { detail: outcome.detail }),
|
|
153
|
+
observed,
|
|
154
|
+
attempts,
|
|
155
|
+
lastAttemptAt: new Date(now).toISOString(),
|
|
156
|
+
nextAttemptAt: new Date(now + minutes * 60_000).toISOString(),
|
|
157
|
+
quarantined: attempts >= QUARANTINE_ATTEMPTS,
|
|
163
158
|
});
|
|
164
159
|
}
|
|
165
160
|
// Landing a blocked close the sweep is allowed to finish
|
|
@@ -327,7 +322,7 @@ function persistComplete(nodeId, wt, guard) {
|
|
|
327
322
|
if (current == null || current.path !== wt.path || current.branch !== wt.branch) {
|
|
328
323
|
return { status: 'skipped', reason: 'record_changed' };
|
|
329
324
|
}
|
|
330
|
-
|
|
325
|
+
completeCleanup(nodeId);
|
|
331
326
|
return { status: 'complete' };
|
|
332
327
|
}
|
|
333
328
|
/** Resume a record whose checkout is already gone — the crash-after-removal
|
package/dist/core/worktree.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { gitAsync, gitSync } from './git.js';
|
|
5
5
|
import { crtrHome } from './canvas/paths.js';
|
|
6
|
-
import { getNode,
|
|
6
|
+
import { closeWorktreeAndRepoint, getNode, recordLandIntent } from './canvas/index.js';
|
|
7
7
|
import { CONFIG_FILE, CRTR_DIR_NAME, STATE_FILE } from '../types.js';
|
|
8
8
|
import { withExclusiveDirectoryLock } from './exclusive-lock.js';
|
|
9
9
|
import { parseStatusPorcelain, planLandIntoCheckout, refDeleteTransactionStdin } from './worktree-landing.js';
|
|
@@ -344,16 +344,11 @@ export function isRebaseInProgress(cwd) {
|
|
|
344
344
|
* sync. */
|
|
345
345
|
export function persistClosedWorktreeState(nodeId, wt, cleanup = 'pending') {
|
|
346
346
|
const node = getNode(nodeId);
|
|
347
|
-
|
|
348
|
-
// Every caller reaches this only after CONCLUDING the land — it succeeded, or
|
|
349
|
-
// the record was proven to have nothing to land. So a recorded land intent is
|
|
350
|
-
// discharged here, and a recorded refusal describes a state that no longer
|
|
351
|
-
// holds; dropping both leaves the sweep re-examining this record fresh.
|
|
352
|
-
const { land_intent: _landIntent, sweep: _sweep, ...retained } = wt;
|
|
353
|
-
updateNode(nodeId, {
|
|
347
|
+
closeWorktreeAndRepoint(nodeId, {
|
|
354
348
|
cwd: wt.repo_root,
|
|
355
|
-
|
|
356
|
-
|
|
349
|
+
cleanup,
|
|
350
|
+
closedAt: new Date().toISOString(),
|
|
351
|
+
unbindSession: node?.pi_session_id != null || node?.pi_session_file != null,
|
|
357
352
|
});
|
|
358
353
|
}
|
|
359
354
|
/** Record that this node ASKED to land and was blocked, so the daemon sweep may
|
|
@@ -366,8 +361,7 @@ function recordBlockedLandIntent(nodeId, blocked) {
|
|
|
366
361
|
const current = getNode(nodeId)?.managed_worktree;
|
|
367
362
|
if (current == null || current.state !== 'open')
|
|
368
363
|
return;
|
|
369
|
-
|
|
370
|
-
updateNode(nodeId, { managed_worktree: { ...retained, land_intent: { at: new Date().toISOString(), blocked } } });
|
|
364
|
+
recordLandIntent(nodeId, { at: new Date().toISOString(), blocked });
|
|
371
365
|
}
|
|
372
366
|
/** Fast-forward a base branch that is CHECKED OUT somewhere, landing underneath
|
|
373
367
|
* whatever local changes that checkout holds.
|
|
@@ -3,9 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
import { DaemonStartupBlockedError, daemonAutostartSuppression, ensureDaemon, resetDaemonAutostart, verifyDaemonStartup, } from '../manage.js';
|
|
8
|
-
import { DAEMON_EXIT_STARTUP_BLOCKED } from '../startup-policy.js';
|
|
6
|
+
import { ensureDaemon, resetDaemonAutostart, } from '../manage.js';
|
|
9
7
|
import { withExclusiveDirectoryLockAsync } from '../../core/exclusive-lock.js';
|
|
10
8
|
// REGRESSION (daemon respawn storm): a daemon whose startup hit a STANDING
|
|
11
9
|
// condition — a blocked on-disk migration — exited, its attach clients saw a
|
|
@@ -31,42 +29,6 @@ after(() => {
|
|
|
31
29
|
process.env['CRTR_NO_DAEMON_AUTOSTART'] = autostartEnv;
|
|
32
30
|
resetDaemonAutostart();
|
|
33
31
|
});
|
|
34
|
-
test('the blocked-startup exit code surfaces as a typed terminal error, not a generic exit', async () => {
|
|
35
|
-
let exited = null;
|
|
36
|
-
const child = spawn(process.execPath, ['-e', `process.exit(${DAEMON_EXIT_STARTUP_BLOCKED})`], { stdio: 'ignore' });
|
|
37
|
-
child.once('exit', (code, signal) => {
|
|
38
|
-
exited = { code, signal };
|
|
39
|
-
});
|
|
40
|
-
await assert.rejects(async () => verifyDaemonStartup(77, 2_000, {
|
|
41
|
-
readPidfile: () => null,
|
|
42
|
-
isPidAlive: () => false,
|
|
43
|
-
isDaemonServing: async () => false,
|
|
44
|
-
childExited: () => exited,
|
|
45
|
-
}), (error) => {
|
|
46
|
-
assert.ok(error instanceof DaemonStartupBlockedError, 'must be typed, so the spawner can stop retrying');
|
|
47
|
-
assert.match(error.message, /crtr sys migrate/);
|
|
48
|
-
return true;
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
test('a blocked start suppresses every later autostart instead of respawning into the same wall', async () => {
|
|
52
|
-
let spawns = 0;
|
|
53
|
-
const deps = {
|
|
54
|
-
isDaemonRunning: () => false,
|
|
55
|
-
spawnDaemon: async () => {
|
|
56
|
-
spawns += 1;
|
|
57
|
-
throw new DaemonStartupBlockedError(4242);
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
ensureDaemon(deps);
|
|
61
|
-
await settle();
|
|
62
|
-
assert.equal(spawns, 1);
|
|
63
|
-
assert.match(daemonAutostartSuppression() ?? '', /crtr sys migrate/);
|
|
64
|
-
// The storm: every redial of every attach client lands here. None may spawn.
|
|
65
|
-
for (let i = 0; i < 50; i++)
|
|
66
|
-
ensureDaemon(deps);
|
|
67
|
-
await settle();
|
|
68
|
-
assert.equal(spawns, 1, 'a standing failure must never be retried');
|
|
69
|
-
});
|
|
70
32
|
test('an ordinary spawn failure backs off, then retries once the window passes', async () => {
|
|
71
33
|
let spawns = 0;
|
|
72
34
|
let now = 1_000_000;
|
|
@@ -22,8 +22,8 @@ export interface SourceDaemonProcess {
|
|
|
22
22
|
signal: NodeJS.Signals | null;
|
|
23
23
|
}>;
|
|
24
24
|
}
|
|
25
|
-
export declare function createSourceDaemonFixture(): Promise<SourceDaemonFixture>;
|
|
26
|
-
export declare function startSourceDaemon(fixture: SourceDaemonFixture): SourceDaemonProcess;
|
|
25
|
+
export declare function createSourceDaemonFixture(opts?: Partial<Pick<SourceDaemonFixture, 'root' | 'home' | 'cwd' | 'canvasHome'>>): Promise<SourceDaemonFixture>;
|
|
26
|
+
export declare function startSourceDaemon(fixture: SourceDaemonFixture, envOverrides?: NodeJS.ProcessEnv): SourceDaemonProcess;
|
|
27
27
|
export declare function healthRequest(port: number, timeoutMs?: number): Promise<boolean>;
|
|
28
28
|
export declare function waitForHealth(process: SourceDaemonProcess, port: number, timeoutMs?: number): Promise<void>;
|
|
29
29
|
export declare function healthWasReadyBeforeExit(process: SourceDaemonProcess, port: number): Promise<boolean>;
|
|
@@ -20,11 +20,11 @@ async function reserveTcpPort() {
|
|
|
20
20
|
await new Promise((resolve, reject) => server.close((error) => error === undefined ? resolve() : reject(error)));
|
|
21
21
|
return address.port;
|
|
22
22
|
}
|
|
23
|
-
export async function createSourceDaemonFixture() {
|
|
24
|
-
const root = mkdtempSync('/tmp/crtr-migration-daemon-');
|
|
25
|
-
const home = join(root, 'home');
|
|
26
|
-
const cwd = join(home, 'work');
|
|
27
|
-
const canvasHome = join(root, 'canvas');
|
|
23
|
+
export async function createSourceDaemonFixture(opts = {}) {
|
|
24
|
+
const root = opts.root ?? mkdtempSync('/tmp/crtr-migration-daemon-');
|
|
25
|
+
const home = opts.home ?? join(root, 'home');
|
|
26
|
+
const cwd = opts.cwd ?? join(home, 'work');
|
|
27
|
+
const canvasHome = opts.canvasHome ?? join(root, 'canvas');
|
|
28
28
|
const pidfile = join(canvasHome, 'ready.pid');
|
|
29
29
|
const socket = join(canvasHome, 'crtrd.sock');
|
|
30
30
|
mkdirSync(cwd, { recursive: true });
|
|
@@ -41,7 +41,7 @@ export async function createSourceDaemonFixture() {
|
|
|
41
41
|
dispose: () => rmSync(root, { recursive: true, force: true }),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
export function startSourceDaemon(fixture) {
|
|
44
|
+
export function startSourceDaemon(fixture, envOverrides = {}) {
|
|
45
45
|
const require = createRequire(import.meta.url);
|
|
46
46
|
const tsxLoader = pathToFileURL(require.resolve('tsx/esm')).href;
|
|
47
47
|
const entry = fileURLToPath(new URL('../../crtrd-cli.ts', import.meta.url));
|
|
@@ -72,6 +72,7 @@ export function startSourceDaemon(fixture) {
|
|
|
72
72
|
'NODE_OPTIONS',
|
|
73
73
|
])
|
|
74
74
|
delete env[key];
|
|
75
|
+
Object.assign(env, envOverrides);
|
|
75
76
|
const child = spawn(process.execPath, [
|
|
76
77
|
'--conditions=crtr-src',
|
|
77
78
|
'--import', tsxLoader,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, statSync,
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import test from 'node:test';
|
|
5
5
|
import { onDiskMigrationIdentity } from '../../../migrations/registry.js';
|
|
6
|
-
import { brokerPidsForCanvas, createSourceDaemonFixture,
|
|
6
|
+
import { brokerPidsForCanvas, createSourceDaemonFixture, startSourceDaemon, waitForHealth, } from '../helpers/source-daemon.js';
|
|
7
7
|
const PRE_MIGRATION_DOC = `---
|
|
8
8
|
kind: knowledge
|
|
9
9
|
name: daemon-ordering
|
|
@@ -24,7 +24,11 @@ function writeUserMemory(home, name, source) {
|
|
|
24
24
|
writeFileSync(path, source, 'utf8');
|
|
25
25
|
return path;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
async function requestJson(port, path, init = {}) {
|
|
28
|
+
const response = await fetch(`http://127.0.0.1:${port}${path}`, init);
|
|
29
|
+
return { status: response.status, body: await response.json() };
|
|
30
|
+
}
|
|
31
|
+
test('source daemon migrates the pre-migration corpus before full readiness', { timeout: 30_000 }, async () => {
|
|
28
32
|
const fixture = await createSourceDaemonFixture();
|
|
29
33
|
const docPath = writeUserMemory(fixture.home, 'daemon-ordering.md', PRE_MIGRATION_DOC);
|
|
30
34
|
const markerPath = join(fixture.canvasHome, 'on-disk-migrations.json');
|
|
@@ -41,8 +45,8 @@ test('source daemon migrates the pre-migration corpus and writes its marker befo
|
|
|
41
45
|
assert.equal(existsSync(markerPath), true, `marker was not written; stderr:\n${daemon.stderr()}`);
|
|
42
46
|
const migratedAtMarker = readFileSync(docPath, 'utf8');
|
|
43
47
|
assert.doesNotMatch(migratedAtMarker, /system-prompt-visibility:|file-read-visibility:|applies-to:|read-when:/);
|
|
44
|
-
assert.equal(existsSync(fixture.pidfile),
|
|
45
|
-
assert.equal(existsSync(fixture.socket),
|
|
48
|
+
assert.equal(existsSync(fixture.pidfile), true, 'the live owner mirror is published at the restricted repair boundary');
|
|
49
|
+
assert.equal(existsSync(fixture.socket), true, 'the repair listener is bound before corpus activation completes');
|
|
46
50
|
await waitForHealth(daemon, fixture.tcpPort);
|
|
47
51
|
const migrated = readFileSync(docPath, 'utf8');
|
|
48
52
|
assert.doesNotMatch(migrated, /system-prompt-visibility:|file-read-visibility:|applies-to:|read-when:/);
|
|
@@ -53,8 +57,8 @@ test('source daemon migrates the pre-migration corpus and writes its marker befo
|
|
|
53
57
|
assert.equal(existsSync(fixture.pidfile), true, 'health readiness follows the pidfile readiness mirror');
|
|
54
58
|
assert.equal(existsSync(fixture.socket), true, 'the unique unix API listener is bound at readiness');
|
|
55
59
|
assert.equal(existsSync(dbPath), true, 'daemon ownership has opened canvas.db by readiness');
|
|
56
|
-
assert.ok(statSync(
|
|
57
|
-
assert.ok(statSync(
|
|
60
|
+
assert.ok(statSync(fixture.pidfile).mtimeMs <= statSync(markerPath).mtimeMs, 'the pidfile is written before the completion marker');
|
|
61
|
+
assert.ok(statSync(fixture.socket).mtimeMs <= statSync(markerPath).mtimeMs, 'the repair listener is bound before the completion marker');
|
|
58
62
|
}
|
|
59
63
|
finally {
|
|
60
64
|
const exit = await daemon.stop();
|
|
@@ -62,33 +66,62 @@ test('source daemon migrates the pre-migration corpus and writes its marker befo
|
|
|
62
66
|
assert.deepEqual(exit, { code: 0, signal: null }, `daemon must stop cleanly; stderr:\n${daemon.stderr()}`);
|
|
63
67
|
}
|
|
64
68
|
});
|
|
65
|
-
test('a real migration blocker
|
|
69
|
+
test('a real migration blocker keeps a restricted daemon alive for repair', { timeout: 30_000 }, async () => {
|
|
66
70
|
const fixture = await createSourceDaemonFixture();
|
|
67
71
|
const blockerPath = writeUserMemory(fixture.home, 'blocked.md', `---\nkind: knowledge\nbroken: [\n---\n# Blocked\n`);
|
|
68
|
-
const effectNames = new Set();
|
|
69
|
-
const effects = watch(fixture.canvasHome, (_event, filename) => {
|
|
70
|
-
if (filename !== null)
|
|
71
|
-
effectNames.add(filename.toString());
|
|
72
|
-
});
|
|
73
72
|
const daemon = startSourceDaemon(fixture);
|
|
74
|
-
const readiness = healthWasReadyBeforeExit(daemon, fixture.tcpPort);
|
|
75
|
-
const exit = await daemon.exited;
|
|
76
|
-
const wasReady = await readiness;
|
|
77
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
78
|
-
effects.close();
|
|
79
73
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
const deadline = Date.now() + 20_000;
|
|
75
|
+
let health;
|
|
76
|
+
while (Date.now() < deadline) {
|
|
77
|
+
try {
|
|
78
|
+
const response = await requestJson(fixture.tcpPort, '/healthz');
|
|
79
|
+
if (response.status === 503) {
|
|
80
|
+
health = response;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// The repair listener has not bound yet.
|
|
86
|
+
}
|
|
87
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
88
|
+
}
|
|
89
|
+
assert.ok(health, `source daemon did not reach restricted startup:\n${daemon.stderr()}`);
|
|
90
|
+
assert.equal(daemon.child.exitCode, null, `blocker must keep daemon alive; stderr:\n${daemon.stderr()}`);
|
|
91
|
+
assert.deepEqual(health.body, {
|
|
92
|
+
ok: false,
|
|
93
|
+
daemon_up: true,
|
|
94
|
+
store_accessible: true,
|
|
95
|
+
brokers_reconciled: false,
|
|
96
|
+
startup_phase: 'blocked',
|
|
97
|
+
startup_blocked: health.body.startup_blocked,
|
|
98
|
+
});
|
|
99
|
+
assert.match(health.body.startup_blocked, new RegExp(blockerPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
|
100
|
+
assert.match(health.body.startup_blocked, /Repair: crtr sys migrate/);
|
|
101
|
+
const status = await requestJson(fixture.tcpPort, '/v1/status');
|
|
102
|
+
assert.equal(status.status, 200, 'restricted startup keeps daemon status diagnostic available');
|
|
103
|
+
const refused = await requestJson(fixture.tcpPort, '/v1/nodes', { method: 'POST', body: '{}' });
|
|
104
|
+
assert.equal(refused.status, 503, 'normal node creation is refused during restricted startup');
|
|
105
|
+
assert.deepEqual(refused.body, {
|
|
106
|
+
error: { code: 'startup_blocked', message: health.body.startup_blocked },
|
|
107
|
+
});
|
|
108
|
+
const repair = await requestJson(fixture.tcpPort, '/v1/daemon/migrate', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'content-type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ dry_run: true }),
|
|
112
|
+
});
|
|
113
|
+
assert.equal(repair.status, 200, 'the restricted daemon serves a repair preview');
|
|
114
|
+
assert.equal(repair.body.blocked, true);
|
|
115
|
+
assert.equal(repair.body.dryRun, true);
|
|
116
|
+
assert.equal(repair.body.resumed, false);
|
|
117
|
+
assert.equal(existsSync(fixture.pidfile), true, 'the live owner mirror remains while startup is blocked');
|
|
118
|
+
assert.equal(existsSync(fixture.socket), true, 'the repair socket remains while startup is blocked');
|
|
87
119
|
assert.equal(existsSync(join(fixture.canvasHome, 'on-disk-migrations.json')), false, 'blocker never gets a completion marker');
|
|
88
120
|
assert.deepEqual(brokerPidsForCanvas(fixture.canvasHome), [], 'no broker process declares the isolated canvas');
|
|
89
|
-
assert.deepEqual([...effectNames].filter((name) => name === 'ready.pid' || name === 'crtrd.sock'), [], `filesystem watcher saw no transient readiness or broker effects (all events: ${[...effectNames].join(', ')})`);
|
|
90
121
|
}
|
|
91
122
|
finally {
|
|
123
|
+
const exit = await daemon.stop();
|
|
92
124
|
fixture.dispose();
|
|
125
|
+
assert.deepEqual(exit, { code: 0, signal: null }, `daemon must stop cleanly; stderr:\n${daemon.stderr()}`);
|
|
93
126
|
}
|
|
94
127
|
});
|
|
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { closeDb } from '../../core/canvas/db.js';
|
|
6
|
+
import { closeDb, migrate, openDb } from '../../core/canvas/db.js';
|
|
7
7
|
import { createNode, dueNodeDeadlines, getRow } from '../../core/canvas/canvas.js';
|
|
8
8
|
import { pushFinal } from '../../core/feed/feed.js';
|
|
9
9
|
import { transition } from '../../core/runtime/lifecycle.js';
|
|
@@ -30,6 +30,7 @@ beforeEach(() => {
|
|
|
30
30
|
closeDb();
|
|
31
31
|
home = mkdtempSync(join(tmpdir(), 'crtr-node-deadline-'));
|
|
32
32
|
process.env['CRTR_HOME'] = home;
|
|
33
|
+
migrate(openDb());
|
|
33
34
|
});
|
|
34
35
|
afterEach(() => {
|
|
35
36
|
closeDb();
|
|
@@ -3,15 +3,17 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { closeDb } from '../../core/canvas/db.js';
|
|
6
|
+
import { closeDb, migrate, openDb, withCanvasWrite } from '../../core/canvas/db.js';
|
|
7
|
+
import { initializeFreshScopedStore } from '../../core/scoped-state/migrate.js';
|
|
7
8
|
import { createNode, getRow, transitionStatusIntent } from '../../core/canvas/canvas.js';
|
|
8
|
-
import {
|
|
9
|
+
import { armExternalActionDelivery, messageBodyForDelivery, readExternalActionDelivery } from '../../core/canvas/deliveries.js';
|
|
9
10
|
import { pushFinal } from '../../core/feed/feed.js';
|
|
10
11
|
import { transition } from '../../core/runtime/lifecycle.js';
|
|
11
12
|
import { spawnNode } from '../../core/runtime/nodes.js';
|
|
12
13
|
import { spawnChildPrepared } from '../../core/runtime/spawn.js';
|
|
13
14
|
let home;
|
|
14
15
|
const priorHome = process.env.CRTR_HOME;
|
|
16
|
+
const priorUserHome = process.env.HOME;
|
|
15
17
|
function node(nodeId, opts = {}) {
|
|
16
18
|
return {
|
|
17
19
|
node_id: nodeId,
|
|
@@ -32,10 +34,27 @@ const binding = {
|
|
|
32
34
|
configOrigin: 'test',
|
|
33
35
|
payload: { callback: 'one' },
|
|
34
36
|
};
|
|
37
|
+
function outcomeDelivery(nodeId) {
|
|
38
|
+
return readExternalActionDelivery(`node-outcome:${nodeId}`);
|
|
39
|
+
}
|
|
40
|
+
function outcomePayload(nodeId) {
|
|
41
|
+
const adapter = outcomeDelivery(nodeId)?.adapter;
|
|
42
|
+
return adapter?.payload ?? null;
|
|
43
|
+
}
|
|
44
|
+
function armOutcomeAction(nodeId, input = binding) {
|
|
45
|
+
withCanvasWrite((db) => armExternalActionDelivery(db, {
|
|
46
|
+
destinationId: input.actionName,
|
|
47
|
+
adapter: { adapter: 'subprocess', argv: input.argv, cwd: input.cwd, config_origin: input.configOrigin, payload: input.payload },
|
|
48
|
+
idempotencyKey: `node-outcome:${nodeId}`,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
35
51
|
beforeEach(() => {
|
|
36
52
|
closeDb();
|
|
37
53
|
home = mkdtempSync(join(tmpdir(), 'crtr-outcome-birth-'));
|
|
38
54
|
process.env.CRTR_HOME = home;
|
|
55
|
+
process.env.HOME = home;
|
|
56
|
+
initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
|
|
57
|
+
migrate(openDb());
|
|
39
58
|
});
|
|
40
59
|
afterEach(() => {
|
|
41
60
|
closeDb();
|
|
@@ -46,10 +65,14 @@ after(() => {
|
|
|
46
65
|
delete process.env.CRTR_HOME;
|
|
47
66
|
else
|
|
48
67
|
process.env.CRTR_HOME = priorHome;
|
|
68
|
+
if (priorUserHome === undefined)
|
|
69
|
+
delete process.env.HOME;
|
|
70
|
+
else
|
|
71
|
+
process.env.HOME = priorUserHome;
|
|
49
72
|
});
|
|
50
|
-
test('birth commits the requested deadline and outcome registration before spawn orchestration can launch a broker', () => {
|
|
73
|
+
test('birth commits the requested deadline and outcome registration before spawn orchestration can launch a broker', async () => {
|
|
51
74
|
const deadlineAt = '2026-01-01T00:00:00.000Z';
|
|
52
|
-
const born = spawnNode({
|
|
75
|
+
const born = await spawnNode({
|
|
53
76
|
kind: 'general',
|
|
54
77
|
cwd: home,
|
|
55
78
|
lifecycle: 'terminal',
|
|
@@ -57,10 +80,10 @@ test('birth commits the requested deadline and outcome registration before spawn
|
|
|
57
80
|
outcomeDelivery: binding,
|
|
58
81
|
});
|
|
59
82
|
assert.equal(getRow(born.node_id)?.deadline_at, deadlineAt);
|
|
60
|
-
assert.deepEqual(
|
|
61
|
-
state:
|
|
62
|
-
action:
|
|
63
|
-
payload:
|
|
83
|
+
assert.deepEqual(outcomeDelivery(born.node_id) && {
|
|
84
|
+
state: outcomeDelivery(born.node_id).state,
|
|
85
|
+
action: outcomeDelivery(born.node_id).destinationId,
|
|
86
|
+
payload: outcomePayload(born.node_id),
|
|
64
87
|
}, {
|
|
65
88
|
state: 'armed',
|
|
66
89
|
action: 'test-action',
|
|
@@ -71,7 +94,7 @@ test('a pre-launch spawn failure leaves its caller-requested outcome delivery pe
|
|
|
71
94
|
let nodeId;
|
|
72
95
|
await assert.rejects(() => spawnChildPrepared({
|
|
73
96
|
kind: 'general',
|
|
74
|
-
cwd:
|
|
97
|
+
cwd: home,
|
|
75
98
|
root: true,
|
|
76
99
|
profile: null,
|
|
77
100
|
prompt: '',
|
|
@@ -83,12 +106,12 @@ test('a pre-launch spawn failure leaves its caller-requested outcome delivery pe
|
|
|
83
106
|
}), /simulated pre-launch failure/);
|
|
84
107
|
assert.ok(nodeId);
|
|
85
108
|
assert.equal(getRow(nodeId)?.outcome_reason, 'launch_failed');
|
|
86
|
-
assert.equal(
|
|
87
|
-
assert.ok(
|
|
109
|
+
assert.equal(outcomeDelivery(nodeId)?.state, 'pending');
|
|
110
|
+
assert.ok(messageBodyForDelivery(outcomeDelivery(nodeId).deliveryId));
|
|
88
111
|
});
|
|
89
112
|
test('an armed delivery cannot become pending without its outcome document', () => {
|
|
90
113
|
createNode(node('guarded'));
|
|
91
|
-
|
|
114
|
+
armOutcomeAction('guarded');
|
|
92
115
|
transitionStatusIntent('guarded', { status: 'dead', terminal_reason: 'crashed' }, {
|
|
93
116
|
outcome: {
|
|
94
117
|
kind: 'failure',
|
|
@@ -98,16 +121,17 @@ test('an armed delivery cannot become pending without its outcome document', ()
|
|
|
98
121
|
documentJson: null,
|
|
99
122
|
},
|
|
100
123
|
});
|
|
101
|
-
const delivery =
|
|
124
|
+
const delivery = outcomeDelivery('guarded');
|
|
102
125
|
assert.equal(delivery?.state, 'armed');
|
|
103
|
-
assert.equal(delivery
|
|
126
|
+
assert.equal(messageBodyForDelivery(delivery.deliveryId), null);
|
|
104
127
|
});
|
|
105
|
-
test('
|
|
128
|
+
test('terminal transition composes the latched terminal view from hydrated node identity', () => {
|
|
106
129
|
createNode(node('late', { spawned_by: 'origin' }));
|
|
130
|
+
armOutcomeAction('late');
|
|
107
131
|
transition('late', 'crash');
|
|
108
132
|
transition('late', 'revive');
|
|
109
|
-
const delivery =
|
|
110
|
-
const document = delivery
|
|
133
|
+
const delivery = outcomeDelivery('late');
|
|
134
|
+
const document = messageBodyForDelivery(delivery.deliveryId);
|
|
111
135
|
assert.equal(document.node.status, 'dead');
|
|
112
136
|
assert.equal(document.node.terminalReason, 'crashed');
|
|
113
137
|
assert.equal(document.node.spawnedBy, 'origin');
|
|
@@ -3,27 +3,48 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { closeDb } from '../../core/canvas/db.js';
|
|
6
|
+
import { closeDb, migrate, openDb, withCanvasWrite } from '../../core/canvas/db.js';
|
|
7
|
+
import { initializeFreshScopedStore } from '../../core/scoped-state/migrate.js';
|
|
8
|
+
import { closeScopedStores } from '../../core/scoped-state/db.js';
|
|
7
9
|
import { createNode } from '../../core/canvas/canvas.js';
|
|
8
10
|
import { isPidAlive, killProcessGroup } from '../../core/canvas/pid.js';
|
|
9
|
-
import {
|
|
11
|
+
import { armExternalActionDelivery, claimExternalActionDelivery, messageBodyForDelivery, readExternalActionDelivery, settleDelivery } from '../../core/canvas/deliveries.js';
|
|
10
12
|
import { transition } from '../../core/runtime/lifecycle.js';
|
|
11
13
|
import { DELIVERY_TIMEOUT_MS } from '../delivery/run-delivery.js';
|
|
12
|
-
import {
|
|
14
|
+
import { runDelivery } from '../delivery/run-delivery.js';
|
|
13
15
|
let home;
|
|
14
16
|
let processGroupFiles;
|
|
15
17
|
const priorHome = process.env.CRTR_HOME;
|
|
18
|
+
const priorUserHome = process.env.HOME;
|
|
16
19
|
function node(nodeId) {
|
|
17
20
|
return { node_id: nodeId, name: nodeId, created: new Date().toISOString(), cwd: home, kind: 'general', mode: 'base', lifecycle: 'terminal', status: 'active' };
|
|
18
21
|
}
|
|
22
|
+
function outcomeDelivery(nodeId) {
|
|
23
|
+
return readExternalActionDelivery(`node-outcome:${nodeId}`);
|
|
24
|
+
}
|
|
25
|
+
async function deliverOutcome(delivery, claimOwner) {
|
|
26
|
+
const adapter = delivery.adapter;
|
|
27
|
+
if (adapter?.adapter !== 'subprocess' || !Array.isArray(adapter.argv) || !adapter.argv.every((entry) => typeof entry === 'string') || typeof adapter.cwd !== 'string')
|
|
28
|
+
throw new Error(`outcome delivery ${delivery.deliveryId} has an invalid subprocess adapter`);
|
|
29
|
+
const document = messageBodyForDelivery(delivery.deliveryId);
|
|
30
|
+
if (document === null)
|
|
31
|
+
throw new Error(`outcome delivery ${delivery.deliveryId} has no document`);
|
|
32
|
+
await runDelivery({ argv: adapter.argv, cwd: adapter.cwd, document, attempt: delivery.attempts }, (settlement) => settleDelivery(delivery.deliveryId, delivery.attempts, claimOwner, settlement, Date.now()));
|
|
33
|
+
}
|
|
19
34
|
function claimScript(id, script) {
|
|
20
35
|
const path = join(home, `${id}.sh`);
|
|
21
36
|
writeFileSync(path, script);
|
|
22
37
|
chmodSync(path, 0o755);
|
|
23
38
|
createNode(node(id));
|
|
24
|
-
|
|
39
|
+
withCanvasWrite((db) => armExternalActionDelivery(db, {
|
|
40
|
+
destinationId: 'test',
|
|
41
|
+
adapter: { adapter: 'subprocess', argv: [path], cwd: home, config_origin: 'test' },
|
|
42
|
+
idempotencyKey: `node-outcome:${id}`,
|
|
43
|
+
}));
|
|
25
44
|
transition(id, 'crash');
|
|
26
|
-
const
|
|
45
|
+
const action = outcomeDelivery(id);
|
|
46
|
+
assert.ok(action);
|
|
47
|
+
const delivery = claimExternalActionDelivery(action.deliveryId, Date.now(), 'test-daemon');
|
|
27
48
|
assert.ok(delivery);
|
|
28
49
|
return delivery;
|
|
29
50
|
}
|
|
@@ -31,6 +52,9 @@ beforeEach(() => {
|
|
|
31
52
|
closeDb();
|
|
32
53
|
home = mkdtempSync(join(tmpdir(), 'crtr-outcome-delivery-'));
|
|
33
54
|
process.env.CRTR_HOME = home;
|
|
55
|
+
process.env.HOME = home;
|
|
56
|
+
initializeFreshScopedStore({ kind: 'user', root: join(home, '.crouter') });
|
|
57
|
+
migrate(openDb());
|
|
34
58
|
processGroupFiles = [];
|
|
35
59
|
});
|
|
36
60
|
afterEach(() => {
|
|
@@ -42,6 +66,7 @@ afterEach(() => {
|
|
|
42
66
|
killProcessGroup(pid, 'SIGKILL');
|
|
43
67
|
}
|
|
44
68
|
closeDb();
|
|
69
|
+
closeScopedStores();
|
|
45
70
|
rmSync(home, { recursive: true, force: true });
|
|
46
71
|
});
|
|
47
72
|
after(() => {
|
|
@@ -49,19 +74,23 @@ after(() => {
|
|
|
49
74
|
delete process.env.CRTR_HOME;
|
|
50
75
|
else
|
|
51
76
|
process.env.CRTR_HOME = priorHome;
|
|
77
|
+
if (priorUserHome === undefined)
|
|
78
|
+
delete process.env.HOME;
|
|
79
|
+
else
|
|
80
|
+
process.env.HOME = priorUserHome;
|
|
52
81
|
});
|
|
53
82
|
test('exit acknowledgments settle outcome delivery durably', async () => {
|
|
54
83
|
const accepted = claimScript('accepted', '#!/bin/sh\necho ignored\necho diagnostic >&2\nexit 0\n');
|
|
55
84
|
await deliverOutcome(accepted, 'test-daemon');
|
|
56
|
-
assert.equal(
|
|
85
|
+
assert.equal(outcomeDelivery('accepted')?.state, 'acknowledged');
|
|
57
86
|
const permanent = claimScript('permanent', '#!/bin/sh\nexit 78\n');
|
|
58
87
|
await deliverOutcome(permanent, 'test-daemon');
|
|
59
|
-
assert.equal(
|
|
88
|
+
assert.equal(outcomeDelivery('permanent')?.state, 'permanent_failed');
|
|
60
89
|
const retry = claimScript('retry', '#!/bin/sh\necho retry-stderr >&2\nexit 1\n');
|
|
61
90
|
await deliverOutcome(retry, 'test-daemon');
|
|
62
|
-
const row =
|
|
91
|
+
const row = outcomeDelivery('retry');
|
|
63
92
|
assert.equal(row?.state, 'pending');
|
|
64
|
-
assert.ok(row.nextAttemptAt > Date.now());
|
|
93
|
+
assert.ok(row?.nextAttemptAt !== null && row?.nextAttemptAt !== undefined && row.nextAttemptAt > Date.now());
|
|
65
94
|
assert.equal(row?.lastFailure?.kind, 'exit');
|
|
66
95
|
assert.equal(row?.lastFailure?.stderr, 'retry-stderr\n');
|
|
67
96
|
});
|
|
@@ -78,7 +107,7 @@ test('timeout SIGKILL escalation reaps a SIGTERM-ignoring descendant after its l
|
|
|
78
107
|
finally {
|
|
79
108
|
global.setTimeout = originalSetTimeout;
|
|
80
109
|
}
|
|
81
|
-
const row =
|
|
110
|
+
const row = outcomeDelivery('timeout');
|
|
82
111
|
assert.equal(row?.state, 'pending');
|
|
83
112
|
assert.equal(row?.lastFailure?.kind, 'timeout');
|
|
84
113
|
const pid = Number(readFileSync(childPid, 'utf8').trim());
|