@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
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// wake-worthy work" (no revive, relaunch-attempt state cleared); a
|
|
21
21
|
// non-deferred unseen entry still wakes exactly as before, and delivers
|
|
22
22
|
// the previously-held deferred entry alongside it with no loss (neither
|
|
23
|
-
//
|
|
23
|
+
// delivery is ever dropped or acknowledged before settlement).
|
|
24
24
|
//
|
|
25
25
|
// (1) and (2) are exercised through the REAL `node message send` command
|
|
26
26
|
// boundary (mirrors push-final-guard.test.ts's #339/#340 pattern) with
|
|
@@ -47,7 +47,11 @@ import { DatabaseSync } from 'node:sqlite';
|
|
|
47
47
|
import { createNode, deleteNode, getNode, setFrozen, setStatus, subscribe, withFreshTerminalGuard } from '../../canvas/canvas.js';
|
|
48
48
|
import { hasNoNaturalCycle } from '../../runtime/revive-all.js';
|
|
49
49
|
import { canvasDbPath, nodeDir, apiSocketPath } from '../../canvas/paths.js';
|
|
50
|
-
import { closeDb } from '../../canvas/db.js';
|
|
50
|
+
import { closeDb, migrate, openDb } from '../../canvas/db.js';
|
|
51
|
+
import { recordExecutionPid } from '../../canvas/executions.js';
|
|
52
|
+
import { closeScopedStores } from '../../scoped-state/db.js';
|
|
53
|
+
import { initializeFreshScopedStore } from '../../scoped-state/migrate.js';
|
|
54
|
+
import { userScopeRoot } from '../../scope.js';
|
|
51
55
|
import { createApiServer } from '../../../daemon/api/server.js';
|
|
52
56
|
import { CrtrClient } from '../../../api/index.js';
|
|
53
57
|
import { BROKER_IDLE_TURN_START } from '../../runtime/broker/held-deferred-inbox.js';
|
|
@@ -55,7 +59,9 @@ import { registerNode } from '../../../commands/node.js';
|
|
|
55
59
|
import { pushFinal } from '../../feed/feed.js';
|
|
56
60
|
import { InputError } from '../../io.js';
|
|
57
61
|
import { headlessBrokerHost } from '../../runtime/host.js';
|
|
58
|
-
import {
|
|
62
|
+
import { enqueueNodeMail } from '../../feed/messages.js';
|
|
63
|
+
import { acknowledgedNodeMailCount } from '../../canvas/deliveries.js';
|
|
64
|
+
import { beginExecution } from '../../canvas/executions.js';
|
|
59
65
|
import { superviseTick } from '../../../daemon/crtrd.js';
|
|
60
66
|
import registerCanvasInboxWatcher from '../../../pi-extensions/canvas-inbox-watcher.js';
|
|
61
67
|
import { bindTestFleet } from '../helpers/fleet.js';
|
|
@@ -63,6 +69,7 @@ import { resetFleetForTests } from '../../runtime/fleet.js';
|
|
|
63
69
|
let home;
|
|
64
70
|
let server;
|
|
65
71
|
const prevNodeId = process.env['CRTR_NODE_ID'];
|
|
72
|
+
const prevHome = process.env['HOME'];
|
|
66
73
|
// createApiServer binds its socket asynchronously (file created in the listen
|
|
67
74
|
// callback), and the socket FILE existing does not yet prove the server answers
|
|
68
75
|
// a full HTTP round-trip — under serial recycle the first request to a
|
|
@@ -86,6 +93,25 @@ async function waitForSocket(path) {
|
|
|
86
93
|
}
|
|
87
94
|
throw new Error(`api socket never answered /healthz at ${path}`);
|
|
88
95
|
}
|
|
96
|
+
function inboxRows(nodeId) {
|
|
97
|
+
return openDb().prepare(`SELECT m.message_id, d.urgency, d.state, d.acknowledged_at FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
98
|
+
WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.canceled_at IS NULL ORDER BY m.seq`).all(nodeId).map((row) => ({
|
|
99
|
+
messageId: row.message_id,
|
|
100
|
+
tier: row.urgency,
|
|
101
|
+
state: row.state,
|
|
102
|
+
acknowledgedAt: row.acknowledged_at,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
function sendMail(nodeId, tier, body, senderNodeId = 'human') {
|
|
106
|
+
return enqueueNodeMail(nodeId, {
|
|
107
|
+
...(senderNodeId === null ? {} : { senderNodeId }),
|
|
108
|
+
kind: 'message',
|
|
109
|
+
label: body,
|
|
110
|
+
bodyJson: { body },
|
|
111
|
+
urgency: tier,
|
|
112
|
+
wake: 1,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
89
115
|
function node(id, over = {}) {
|
|
90
116
|
return {
|
|
91
117
|
node_id: id,
|
|
@@ -99,6 +125,14 @@ function node(id, over = {}) {
|
|
|
99
125
|
...over,
|
|
100
126
|
};
|
|
101
127
|
}
|
|
128
|
+
function createFixtureNode(meta) {
|
|
129
|
+
createNode(meta);
|
|
130
|
+
if (meta.pi_pid == null)
|
|
131
|
+
return null;
|
|
132
|
+
const executionId = beginExecution(meta.node_id, { daemonEpoch: 'test-epoch' });
|
|
133
|
+
recordExecutionPid(executionId, meta.node_id, meta.pi_pid);
|
|
134
|
+
return executionId;
|
|
135
|
+
}
|
|
102
136
|
/** A pid guaranteed dead by the time spawnSync returns. */
|
|
103
137
|
function deadPid() {
|
|
104
138
|
const r = spawnSync('true', [], { stdio: 'ignore' });
|
|
@@ -113,6 +147,7 @@ function messageSendLeaf() {
|
|
|
113
147
|
}
|
|
114
148
|
before(() => {
|
|
115
149
|
home = mkdtempSync(join(tmpdir(), 'crtr-deferred-nowake-'));
|
|
150
|
+
process.env['HOME'] = home;
|
|
116
151
|
process.env['CRTR_HOME'] = home;
|
|
117
152
|
// superviseTick and the in-process API server's launch verbs resolve
|
|
118
153
|
// boundFleet() (D-2). Empty is the point: no fabricated row here has a live
|
|
@@ -121,8 +156,11 @@ before(() => {
|
|
|
121
156
|
});
|
|
122
157
|
beforeEach(async () => {
|
|
123
158
|
closeDb();
|
|
159
|
+
closeScopedStores();
|
|
124
160
|
rmSync(home, { recursive: true, force: true });
|
|
125
161
|
mkdirSync(home, { recursive: true });
|
|
162
|
+
migrate(openDb());
|
|
163
|
+
initializeFreshScopedStore({ kind: 'user', root: userScopeRoot() });
|
|
126
164
|
// POST-P1 the `node message send` leaf forwards its wake/revive canvas ops
|
|
127
165
|
// through cliClient() over crtrd's socket. This home neutralizes the daemon (no REAL
|
|
128
166
|
// crtrd) but binds no server, so stand one up in-process on the isolated
|
|
@@ -135,11 +173,17 @@ afterEach(async () => {
|
|
|
135
173
|
await server?.close();
|
|
136
174
|
server = undefined;
|
|
137
175
|
closeDb();
|
|
176
|
+
closeScopedStores();
|
|
138
177
|
});
|
|
139
178
|
after(() => {
|
|
140
179
|
closeDb();
|
|
180
|
+
closeScopedStores();
|
|
141
181
|
rmSync(home, { recursive: true, force: true });
|
|
142
182
|
delete process.env['CRTR_HOME'];
|
|
183
|
+
if (prevHome === undefined)
|
|
184
|
+
delete process.env['HOME'];
|
|
185
|
+
else
|
|
186
|
+
process.env['HOME'] = prevHome;
|
|
143
187
|
if (prevNodeId === undefined)
|
|
144
188
|
delete process.env['CRTR_NODE_ID'];
|
|
145
189
|
else
|
|
@@ -153,7 +197,7 @@ test('node message send --tier deferred to an ordinary dormant (idle-released) t
|
|
|
153
197
|
// terminal target takes no such cycle, so its mail is raised to the steering
|
|
154
198
|
// tier instead (locked by the next test).
|
|
155
199
|
createNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release' }));
|
|
156
|
-
const inboxBefore =
|
|
200
|
+
const inboxBefore = inboxRows(id).length;
|
|
157
201
|
const originalLaunch = headlessBrokerHost.launch;
|
|
158
202
|
let launchCalls = 0;
|
|
159
203
|
headlessBrokerHost.launch = ((...args) => {
|
|
@@ -169,14 +213,14 @@ test('node message send --tier deferred to an ordinary dormant (idle-released) t
|
|
|
169
213
|
headlessBrokerHost.launch = originalLaunch;
|
|
170
214
|
}
|
|
171
215
|
assert.equal(launchCalls, 0, 'a deferred delivery to a dormant target must never attempt a revive');
|
|
172
|
-
assert.equal(
|
|
216
|
+
assert.equal(inboxRows(id).length, inboxBefore + 1, 'the SQL delivery is durable despite the hold');
|
|
173
217
|
const after = getNode(id);
|
|
174
218
|
assert.equal(after.status, 'idle', 'target left exactly where it was');
|
|
175
219
|
assert.equal(after.intent, 'idle-release', 'target left exactly where it was');
|
|
176
220
|
});
|
|
177
221
|
test('node message send --tier deferred to a TERMINAL-lifecycle target is raised to the steering tier and wakes it', { timeout: 30_000 }, async () => {
|
|
178
222
|
const id = 'terminalLifecycleTarget';
|
|
179
|
-
|
|
223
|
+
createFixtureNode(node(id, { status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
|
|
180
224
|
// Fully stubbed (never delegates): the point is that the raised entry DOES
|
|
181
225
|
// attempt the wake, so a real detached broker must not be spawned to prove it.
|
|
182
226
|
const originalLaunch = headlessBrokerHost.launch;
|
|
@@ -192,9 +236,9 @@ test('node message send --tier deferred to a TERMINAL-lifecycle target is raised
|
|
|
192
236
|
finally {
|
|
193
237
|
headlessBrokerHost.launch = originalLaunch;
|
|
194
238
|
}
|
|
195
|
-
const entries =
|
|
196
|
-
assert.equal(entries.length, 1, 'the
|
|
197
|
-
assert.equal(entries[0].tier, 'urgent', 'a terminal target takes no later cycle, so the
|
|
239
|
+
const entries = inboxRows(id);
|
|
240
|
+
assert.equal(entries.length, 1, 'the SQL delivery is durable');
|
|
241
|
+
assert.equal(entries[0].tier, 'urgent', 'a terminal target takes no later cycle, so the delivery is raised to the steering tier');
|
|
198
242
|
assert.equal(launchCalls, 1, 'the raised entry wakes the dormant terminal target instead of waiting for a cycle it never takes');
|
|
199
243
|
assert.equal(res['revived'], true, 'the send reports the wake it actually performed');
|
|
200
244
|
});
|
|
@@ -202,7 +246,7 @@ test('node message send --tier deferred rejects ordinary terminal targets but ac
|
|
|
202
246
|
for (const status of ['done', 'canceled']) {
|
|
203
247
|
const id = `terminal-${status}`;
|
|
204
248
|
createNode(node(id, { status, intent: null }));
|
|
205
|
-
const inboxBefore =
|
|
249
|
+
const inboxBefore = inboxRows(id).length;
|
|
206
250
|
const originalLaunch = headlessBrokerHost.launch;
|
|
207
251
|
let launchCalls = 0;
|
|
208
252
|
headlessBrokerHost.launch = ((...args) => {
|
|
@@ -216,7 +260,7 @@ test('node message send --tier deferred rejects ordinary terminal targets but ac
|
|
|
216
260
|
headlessBrokerHost.launch = originalLaunch;
|
|
217
261
|
}
|
|
218
262
|
assert.equal(launchCalls, 0, `${status}: a rejected deferred must never attempt a revive`);
|
|
219
|
-
assert.equal(
|
|
263
|
+
assert.equal(inboxRows(id).length, inboxBefore, `${status}: a rejected deferred must not create a SQL delivery`);
|
|
220
264
|
}
|
|
221
265
|
const frozenId = 'terminal-frozen';
|
|
222
266
|
createNode(node(frozenId, {
|
|
@@ -225,25 +269,20 @@ test('node message send --tier deferred rejects ordinary terminal targets but ac
|
|
|
225
269
|
intent: 'parked',
|
|
226
270
|
}));
|
|
227
271
|
setFrozen(frozenId, new Date().toISOString());
|
|
228
|
-
const inboxBefore =
|
|
272
|
+
const inboxBefore = inboxRows(frozenId).length;
|
|
229
273
|
const result = await messageSendLeaf().run({ to: frozenId, tier: 'deferred', body: 'held for thaw' });
|
|
230
274
|
assert.equal(result['guidance'], "Delivered — held for the target's next natural cycle; not revived.", 'a frozen terminal row still has a thaw cycle');
|
|
231
275
|
assert.equal(result['revived'], false, 'deferred mail waits for the thaw without launching');
|
|
232
|
-
assert.equal(
|
|
276
|
+
assert.equal(inboxRows(frozenId).length, inboxBefore + 1, 'the deferred SQL delivery is durable');
|
|
233
277
|
});
|
|
234
278
|
// #343 third-fix-cycle review: the IMMEDIATE `--tier deferred` path had its
|
|
235
279
|
// own, separate TOCTOU — a stale-snapshot check (`rejectDeferredWithoutNaturalCycle`)
|
|
236
|
-
// ran
|
|
237
|
-
//
|
|
238
|
-
// commitReopen) in between.
|
|
239
|
-
// one
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
// that exact composed shape (the guard wrapping a body that appends an inbox
|
|
243
|
-
// entry identical to what `deliverImmediate` builds) against a REAL rival
|
|
244
|
-
// writer, proving the immediate path linearizes the same way the shared
|
|
245
|
-
// primitive is proven to below.
|
|
246
|
-
test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node message send --tier deferred` immediate delivery too — a rival terminalization cannot land between the fresh check and the appendInbox it guards', { timeout: 30_000 }, () => {
|
|
280
|
+
// ran before the SQL delivery write, with several fallible side effects
|
|
281
|
+
// (assertFinalizedForReopen/writeOutputSchema/appendSituationalContext/
|
|
282
|
+
// commitReopen) in between. The guarded closure keeps the fresh check and
|
|
283
|
+
// delivery write in one canvas transaction. This drives that shape against a
|
|
284
|
+
// real rival writer.
|
|
285
|
+
test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node message send --tier deferred` — a rival terminalization cannot land between the fresh check and the SQL delivery it guards', { timeout: 30_000 }, () => {
|
|
247
286
|
const targetId = 'raceTargetImmediate';
|
|
248
287
|
createNode(node(targetId, { status: 'active', intent: null }));
|
|
249
288
|
const rival = new DatabaseSync(canvasDbPath());
|
|
@@ -252,13 +291,7 @@ test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node
|
|
|
252
291
|
const outcome = withFreshTerminalGuard(targetId, hasNoNaturalCycle, () => {
|
|
253
292
|
// Same entry shape `deliverImmediate` builds for a body message — the
|
|
254
293
|
// guarded write this test's race must prove is atomic with the fresh read.
|
|
255
|
-
const entry =
|
|
256
|
-
from: 'human',
|
|
257
|
-
tier: 'deferred',
|
|
258
|
-
kind: 'message',
|
|
259
|
-
label: 'raced note',
|
|
260
|
-
data: { body: 'raced note' },
|
|
261
|
-
});
|
|
294
|
+
const entry = sendMail(targetId, 'deferred', 'raced note');
|
|
262
295
|
// Attempted WHILE the guard holds its BEGIN IMMEDIATE write lock — a real
|
|
263
296
|
// concurrent finish/cancel/finalization racing the immediate delivery.
|
|
264
297
|
try {
|
|
@@ -274,7 +307,7 @@ test('#343 (immediate path): withFreshTerminalGuard closes the TOCTOU for `node
|
|
|
274
307
|
try {
|
|
275
308
|
assert.equal(rivalBlocked, true, 'the rival terminalization could not acquire the lock mid-guard — the immediate delivery genuinely serializes against it');
|
|
276
309
|
assert.equal(outcome.kind, 'ran', 'the guard read the pre-terminal state and completed the delivery, uncontested by the rival');
|
|
277
|
-
assert.equal(
|
|
310
|
+
assert.equal(inboxRows(targetId).length, 1, 'exactly the one guarded SQL delivery landed');
|
|
278
311
|
}
|
|
279
312
|
finally {
|
|
280
313
|
try {
|
|
@@ -307,7 +340,7 @@ test('#343 (immediate path): `node message send --tier deferred --reopen` agains
|
|
|
307
340
|
else
|
|
308
341
|
process.env['CRTR_NODE_ID'] = priorNodeId;
|
|
309
342
|
}
|
|
310
|
-
const inboxBefore =
|
|
343
|
+
const inboxBefore = inboxRows(id).length;
|
|
311
344
|
const originalLaunch = headlessBrokerHost.launch;
|
|
312
345
|
let launchCalls = 0;
|
|
313
346
|
headlessBrokerHost.launch = ((...args) => {
|
|
@@ -321,7 +354,7 @@ test('#343 (immediate path): `node message send --tier deferred --reopen` agains
|
|
|
321
354
|
headlessBrokerHost.launch = originalLaunch;
|
|
322
355
|
}
|
|
323
356
|
assert.equal(launchCalls, 0, 'a rejected reopen+deferred retask must never attempt a revive');
|
|
324
|
-
assert.equal(
|
|
357
|
+
assert.equal(inboxRows(id).length, inboxBefore, 'a rejected reopen+deferred retask must not create a SQL delivery');
|
|
325
358
|
assert.equal(getNode(id)?.final_report, firstFinal, 'the canonical final must survive untouched — assertFinalizedForReopen/commitReopen never ran on the rejected path');
|
|
326
359
|
});
|
|
327
360
|
// The shared `withFreshTerminalGuard` primitive itself (Major, gh #341/#343
|
|
@@ -396,11 +429,11 @@ test("withFreshTerminalGuard reports 'missing' — never runs the guarded body
|
|
|
396
429
|
let bodyRan = false;
|
|
397
430
|
const outcome = withFreshTerminalGuard(targetId, hasNoNaturalCycle, () => {
|
|
398
431
|
bodyRan = true;
|
|
399
|
-
return
|
|
432
|
+
return sendMail(targetId, 'deferred', 'orphan');
|
|
400
433
|
});
|
|
401
434
|
assert.equal(bodyRan, false, "a missing target must NEVER run the guarded body — 'missing' must not be treated as safe/empty-and-runnable");
|
|
402
435
|
assert.deepEqual(outcome, { kind: 'missing' }, "the guard reports a distinct 'missing' outcome, not 'terminal' (nothing to report) and not a fabricated 'ran'");
|
|
403
|
-
assert.equal(existsSync(nodeDir(targetId)), false, 'the guarded
|
|
436
|
+
assert.equal(existsSync(nodeDir(targetId)), false, 'the guarded SQL delivery never ran, so no orphan node dir was recreated for the deleted target');
|
|
404
437
|
});
|
|
405
438
|
// Daemon second pass — crtrd.ts's idle-release inbox loop
|
|
406
439
|
// Deterministic: `headlessBrokerHost.launch` is fully STUBBED (never delegates
|
|
@@ -409,18 +442,18 @@ test("withFreshTerminalGuard reports 'missing' — never runs the guarded body
|
|
|
409
442
|
// `exited` promise never settles and its pid is a fixed sentinel, so the only
|
|
410
443
|
// thing under test is the daemon's revive-or-hold DECISION (cycles/status/
|
|
411
444
|
// intent), never a real engine's boot. Genuine post-revive DELIVERY (both
|
|
412
|
-
// entries actually reaching the target, no duplicate/loss,
|
|
445
|
+
// entries actually reaching the target, no duplicate/loss, SQL acknowledgement) is
|
|
413
446
|
// proven separately below through the real inbox watcher.
|
|
414
447
|
test('daemon second pass: a deferred-only unseen set never wakes an idle-released node; a later non-deferred entry wakes it (decision only — real delivery is proven separately below)', { timeout: 30_000 }, async () => {
|
|
415
448
|
const id = 'idleReleaseDeferredDaemon';
|
|
416
|
-
|
|
449
|
+
createFixtureNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
|
|
417
450
|
// A live child is a standing obligation, which is what keeps this row
|
|
418
451
|
// idle-released across the tick. Without one it would settle into its park
|
|
419
452
|
// instead (the resident carve-out, locked in the next test) and the wake half
|
|
420
453
|
// below would be measuring a parked row.
|
|
421
454
|
createNode(node(`${id}Child`, { status: 'active' }));
|
|
422
455
|
subscribe(id, `${id}Child`);
|
|
423
|
-
const
|
|
456
|
+
const baselineAcknowledged = acknowledgedNodeMailCount(id);
|
|
424
457
|
const originalLaunch = headlessBrokerHost.launch;
|
|
425
458
|
let launchCalls = 0;
|
|
426
459
|
headlessBrokerHost.launch = (() => {
|
|
@@ -430,13 +463,7 @@ test('daemon second pass: a deferred-only unseen set never wakes an idle-release
|
|
|
430
463
|
let deferredEntry;
|
|
431
464
|
let normalEntry;
|
|
432
465
|
try {
|
|
433
|
-
deferredEntry =
|
|
434
|
-
from: 'human',
|
|
435
|
-
tier: 'deferred',
|
|
436
|
-
kind: 'message',
|
|
437
|
-
label: 'deferred note',
|
|
438
|
-
data: { body: 'deferred note' },
|
|
439
|
-
});
|
|
466
|
+
deferredEntry = sendMail(id, 'deferred', 'deferred note');
|
|
440
467
|
await superviseTick();
|
|
441
468
|
{
|
|
442
469
|
const after = getNode(id);
|
|
@@ -445,14 +472,8 @@ test('daemon second pass: a deferred-only unseen set never wakes an idle-release
|
|
|
445
472
|
assert.equal(after.intent, 'idle-release', 'target left idle-release');
|
|
446
473
|
assert.equal(launchCalls, 0, 'no launch attempt for a deferred-only unseen set');
|
|
447
474
|
}
|
|
448
|
-
assert.equal(
|
|
449
|
-
normalEntry =
|
|
450
|
-
from: 'human',
|
|
451
|
-
tier: 'normal',
|
|
452
|
-
kind: 'message',
|
|
453
|
-
label: 'go now',
|
|
454
|
-
data: { body: 'go now' },
|
|
455
|
-
});
|
|
475
|
+
assert.equal(acknowledgedNodeMailCount(id), baselineAcknowledged, 'the held delivery remains unacknowledged');
|
|
476
|
+
normalEntry = sendMail(id, 'normal', 'go now');
|
|
456
477
|
await superviseTick();
|
|
457
478
|
{
|
|
458
479
|
const after = getNode(id);
|
|
@@ -465,15 +486,12 @@ test('daemon second pass: a deferred-only unseen set never wakes an idle-release
|
|
|
465
486
|
finally {
|
|
466
487
|
headlessBrokerHost.launch = originalLaunch;
|
|
467
488
|
}
|
|
468
|
-
// No loss:
|
|
469
|
-
|
|
470
|
-
// normal flush to deliver together on this same revived cycle.
|
|
471
|
-
const unseen = readInboxSince(id, baseline).map((e) => e.entry_id);
|
|
472
|
-
assert.deepEqual(unseen, [deferredEntry.entry_id, normalEntry.entry_id], 'neither entry was dropped or skipped by the daemon');
|
|
489
|
+
// No loss: both independent SQL obligations remain pending for the revived execution.
|
|
490
|
+
assert.deepEqual(inboxRows(id).filter((row) => row.state === 'pending').map((row) => row.messageId), [deferredEntry.messageId, normalEntry.messageId], 'neither delivery was dropped or acknowledged by the daemon');
|
|
473
491
|
});
|
|
474
492
|
test('the lifecycle tick parks a resident holding deferred-only mail — mail that never wakes it must not hold it idle forever either', { timeout: 30_000 }, async () => {
|
|
475
493
|
const id = 'residentDeferredOnly';
|
|
476
|
-
|
|
494
|
+
createFixtureNode(node(id, { lifecycle: 'resident', status: 'idle', intent: 'idle-release', pi_pid: deadPid(), pi_session_id: null, cwd: home }));
|
|
477
495
|
const originalLaunch = headlessBrokerHost.launch;
|
|
478
496
|
let launchCalls = 0;
|
|
479
497
|
headlessBrokerHost.launch = (() => {
|
|
@@ -481,19 +499,19 @@ test('the lifecycle tick parks a resident holding deferred-only mail — mail th
|
|
|
481
499
|
return { pid: 0x7ffffffe, exited: new Promise(() => { }) };
|
|
482
500
|
});
|
|
483
501
|
try {
|
|
484
|
-
const held =
|
|
485
|
-
from: null,
|
|
486
|
-
tier: 'deferred',
|
|
502
|
+
const held = enqueueNodeMail(id, {
|
|
487
503
|
kind: 'message',
|
|
488
504
|
label: 'Child done — auto-done notice',
|
|
489
|
-
|
|
505
|
+
bodyJson: { reason: 'child-auto-done' },
|
|
506
|
+
urgency: 'deferred',
|
|
507
|
+
wake: 1,
|
|
490
508
|
});
|
|
491
509
|
await superviseTick();
|
|
492
510
|
const after = getNode(id);
|
|
493
511
|
assert.equal(after.status, 'done', 'the resident settles into its park instead of sitting idle behind mail that cannot wake it');
|
|
494
512
|
assert.equal(after.intent, 'parked', 'parked, not released');
|
|
495
513
|
assert.equal(launchCalls, 0, 'settling quietly never launches an engine');
|
|
496
|
-
assert.deepEqual(
|
|
514
|
+
assert.deepEqual(inboxRows(id).filter((row) => row.state === 'pending').map((row) => row.messageId), [held.messageId], 'the held delivery survives the park, ready for the cycle that reopens the conversation');
|
|
497
515
|
}
|
|
498
516
|
finally {
|
|
499
517
|
headlessBrokerHost.launch = originalLaunch;
|
|
@@ -512,21 +530,32 @@ function makeFakePi() {
|
|
|
512
530
|
return pi;
|
|
513
531
|
}
|
|
514
532
|
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
515
|
-
|
|
533
|
+
async function waitUntil(check, timeoutMs) {
|
|
534
|
+
const deadline = Date.now() + timeoutMs;
|
|
535
|
+
while (!check()) {
|
|
536
|
+
if (Date.now() >= deadline)
|
|
537
|
+
throw new Error('timed out waiting for watcher deliveries');
|
|
538
|
+
await wait(5);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
test('the real inbox watcher holds a deferred delivery alone, then delivers it TOGETHER with a later normal delivery with no duplicate/loss, and acknowledges both at settlement (gh #341 review: genuine delivery, not just the daemon decision)', { timeout: 30_000 }, async () => {
|
|
516
542
|
const id = 'watcherDeferredThenNormal';
|
|
517
|
-
// The watcher
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
createNode(node(id, { lifecycle: 'resident', status: 'idle', intent: null
|
|
543
|
+
// The watcher claims mail only for its current execution. This fake Pi runs
|
|
544
|
+
// in this test process, so its fabricated node must carry that execution just
|
|
545
|
+
// like a real launched node does.
|
|
546
|
+
createNode(node(id, { description: 'deferred inbox watcher fixture', lifecycle: 'resident', status: 'idle', intent: null }));
|
|
547
|
+
const executionId = beginExecution(id, { daemonEpoch: 'test' });
|
|
521
548
|
const TICK_MS = 20;
|
|
522
549
|
const DEBOUNCE_MS = 25;
|
|
523
550
|
const SETTLE_MS = TICK_MS * 2 + DEBOUNCE_MS + 30;
|
|
524
551
|
const origTick = process.env['CRTR_WATCHER_TICK_MS'];
|
|
525
552
|
const origDebounce = process.env['CRTR_WATCHER_DEBOUNCE_MS'];
|
|
526
553
|
const origNodeId = process.env['CRTR_NODE_ID'];
|
|
554
|
+
const origExecutionId = process.env['CRTR_EXECUTION_ID'];
|
|
527
555
|
process.env['CRTR_WATCHER_TICK_MS'] = String(TICK_MS);
|
|
528
556
|
process.env['CRTR_WATCHER_DEBOUNCE_MS'] = String(DEBOUNCE_MS);
|
|
529
557
|
process.env['CRTR_NODE_ID'] = id;
|
|
558
|
+
process.env['CRTR_EXECUTION_ID'] = executionId;
|
|
530
559
|
const pi = makeFakePi();
|
|
531
560
|
let dispose;
|
|
532
561
|
try {
|
|
@@ -538,24 +567,12 @@ test('the real inbox watcher holds a deferred entry alone, then delivers it TOGE
|
|
|
538
567
|
// No agent_start fired — the watcher treats the node as idle, same as the
|
|
539
568
|
// idle-released daemon scenario above.
|
|
540
569
|
await wait(TICK_MS + 60);
|
|
541
|
-
const deferredEntry =
|
|
542
|
-
from: 'human',
|
|
543
|
-
tier: 'deferred',
|
|
544
|
-
kind: 'message',
|
|
545
|
-
label: 'deferred note',
|
|
546
|
-
data: { body: 'deferred note' },
|
|
547
|
-
});
|
|
570
|
+
const deferredEntry = sendMail(id, 'deferred', 'deferred note');
|
|
548
571
|
await wait(SETTLE_MS);
|
|
549
572
|
assert.equal(pi.injected.length, 0, 'a deferred-only idle buffer is held by the real watcher, not delivered alone');
|
|
550
|
-
assert.equal(
|
|
551
|
-
const normalEntry =
|
|
552
|
-
|
|
553
|
-
tier: 'normal',
|
|
554
|
-
kind: 'message',
|
|
555
|
-
label: 'go now',
|
|
556
|
-
data: { body: 'go now' },
|
|
557
|
-
});
|
|
558
|
-
await wait(SETTLE_MS + TICK_MS * 2);
|
|
573
|
+
assert.equal(acknowledgedNodeMailCount(id), 0, 'nothing is acknowledged while held');
|
|
574
|
+
const normalEntry = sendMail(id, 'normal', 'go now');
|
|
575
|
+
await waitUntil(() => pi.injected.length === 2, SETTLE_MS + TICK_MS * 12);
|
|
559
576
|
// Two direct human entries are two verbatim deliveries — one message per
|
|
560
577
|
// entry, physical order preserved, one visible send per watcher cycle.
|
|
561
578
|
assert.equal(pi.injected.length, 2, 'both entries are delivered once a non-deferred entry joins the batch — each human entry as its own verbatim message');
|
|
@@ -563,13 +580,13 @@ test('the real inbox watcher holds a deferred entry alone, then delivers it TOGE
|
|
|
563
580
|
assert.doesNotMatch(pi.injected[0].content, /go now/, 'the deliveries are separate messages, not one concatenation');
|
|
564
581
|
assert.match(pi.injected[1].content, /go now/, 'the new normal entry follows in its own message — not duplicated');
|
|
565
582
|
assert.equal(pi.injected[0].deliverAs, undefined, 'idle delivery triggers a fresh turn, no deliverAs');
|
|
566
|
-
// Idle-path delivery hands off on the void send but only
|
|
567
|
-
//
|
|
568
|
-
// itself) — settle the turn and prove
|
|
569
|
-
// entries, not just the earlier (previously-held) one.
|
|
583
|
+
// Idle-path delivery hands off on the void send but only acknowledges the
|
|
584
|
+
// SQL deliveries at agent_settled (never on the fire-and-forget send
|
|
585
|
+
// itself) — settle the turn and prove both delivered obligations settle.
|
|
570
586
|
await pi.fire('agent_settled', { type: 'agent_settled' }, { isIdle: () => true, hasPendingMessages: () => false });
|
|
571
|
-
assert.equal(
|
|
572
|
-
assert.
|
|
587
|
+
assert.equal(acknowledgedNodeMailCount(id), 2, 'agent_settled acknowledges both delivered SQL obligations');
|
|
588
|
+
assert.ok(inboxRows(id).every((row) => row.acknowledgedAt !== null), 'neither delivery is left in a partial handoff');
|
|
589
|
+
assert.notEqual(normalEntry.messageId, deferredEntry.messageId, 'the batch retains distinct message identities');
|
|
573
590
|
}
|
|
574
591
|
finally {
|
|
575
592
|
dispose?.();
|
|
@@ -585,5 +602,9 @@ test('the real inbox watcher holds a deferred entry alone, then delivers it TOGE
|
|
|
585
602
|
delete process.env['CRTR_NODE_ID'];
|
|
586
603
|
else
|
|
587
604
|
process.env['CRTR_NODE_ID'] = origNodeId;
|
|
605
|
+
if (origExecutionId === undefined)
|
|
606
|
+
delete process.env['CRTR_EXECUTION_ID'];
|
|
607
|
+
else
|
|
608
|
+
process.env['CRTR_EXECUTION_ID'] = origExecutionId;
|
|
588
609
|
}
|
|
589
610
|
});
|
|
@@ -67,8 +67,8 @@ import { tmpdir } from 'node:os';
|
|
|
67
67
|
import { join } from 'node:path';
|
|
68
68
|
import { createHarness } from '../helpers/harness.js';
|
|
69
69
|
import { isPidAlive, capturePidIdentities, killProcessTreePids, captureTeardownSnapshot } from '../../canvas/pid.js';
|
|
70
|
-
import { recordPid } from '../../canvas/canvas.js';
|
|
71
70
|
import { openDb } from '../../canvas/db.js';
|
|
71
|
+
import { beginExecution, recordExecutionPid } from '../../canvas/executions.js';
|
|
72
72
|
import { headlessBrokerHost } from '../../runtime/host.js';
|
|
73
73
|
let h;
|
|
74
74
|
let root;
|
|
@@ -85,6 +85,10 @@ function trackPid(pid) {
|
|
|
85
85
|
if (pid != null)
|
|
86
86
|
spawnedPids.add(pid);
|
|
87
87
|
}
|
|
88
|
+
function recordExecution(nodeId, pid) {
|
|
89
|
+
const executionId = beginExecution(nodeId, { daemonEpoch: h.fleet.epoch() });
|
|
90
|
+
recordExecutionPid(executionId, nodeId, pid);
|
|
91
|
+
}
|
|
88
92
|
before(async () => {
|
|
89
93
|
h = await createHarness({ headless: true, sessionPrefix: 'crtr-hostpg' });
|
|
90
94
|
root = h.spawnRoot('host-teardown-process-group suite root');
|
|
@@ -362,7 +366,7 @@ test('CONNECTED broker accepts the shutdown frame and exits cleanly, but leaves
|
|
|
362
366
|
const id = h.fabricateBrokerNode({ parent: root, pi_pid: null, status: 'active' });
|
|
363
367
|
const sockPath = h.brokerSock(id);
|
|
364
368
|
const { leaderPid, descendantPid } = await spawnBrokerWithDetachedDescendantAndSocket(sockPath, ['sleep', '300']);
|
|
365
|
-
|
|
369
|
+
recordExecution(id, leaderPid);
|
|
366
370
|
assert.equal(isPidAlive(leaderPid), true, 'fake connected broker leader alive before teardown');
|
|
367
371
|
assert.equal(isPidAlive(descendantPid), true, 'detached descendant alive before teardown');
|
|
368
372
|
// teardown() connects, writes the shutdown frame, and the fake broker
|
|
@@ -455,14 +459,14 @@ test('end-to-end: headlessBrokerHost.teardown() honors a launch-time identity mi
|
|
|
455
459
|
'stand-in "reused pid" tree at all, even after both the SIGTERM and SIGKILL escalation grace windows elapse', async () => {
|
|
456
460
|
const { leaderPid, descendantPid } = await spawnBrokerWithSharedGroupDescendant('teardown-mismatch');
|
|
457
461
|
const id = h.fabricateBrokerNode({ parent: root, pi_pid: leaderPid, status: 'active' });
|
|
458
|
-
//
|
|
462
|
+
// recordExecution captures the REAL current identity for leaderPid (genuinely
|
|
459
463
|
// alive right now, so this succeeds) — then this corrupts JUST the stored
|
|
460
464
|
// identity column to simulate "the recorded launch-time baseline no
|
|
461
465
|
// longer matches this pid's CURRENT owner", standing in for an actual OS
|
|
462
466
|
// pid-recycle between this node's launch and this teardown() call.
|
|
463
|
-
|
|
467
|
+
recordExecution(id, leaderPid);
|
|
464
468
|
openDb()
|
|
465
|
-
.prepare(
|
|
469
|
+
.prepare("UPDATE executions SET process_identity = ? WHERE node_id = ? AND state != 'exited'")
|
|
466
470
|
.run('deliberately-mismatched-launch-identity', id);
|
|
467
471
|
headlessBrokerHost.teardown(id);
|
|
468
472
|
// No real broker listens on this fabricated node's view.sock, so
|