@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,256 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/human-deliver.test.ts
|
|
2
|
-
//
|
|
3
|
-
// Crouter creates bridge tickets, while crtrd owns terminal ticket delivery.
|
|
4
|
-
import { test, describe, before, after, beforeEach, afterEach } from 'node:test';
|
|
5
|
-
import assert from 'node:assert/strict';
|
|
6
|
-
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
7
|
-
import { tmpdir } from 'node:os';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import { ApiError } from '../../api/index.js';
|
|
10
|
-
import { createApiServer } from '../../daemon/api/server.js';
|
|
11
|
-
import { cancelHumanTicket, deliverTerminalResult, resolvePageTicket, settleFeedbackCompanion } from '../../daemon/human/finish.js';
|
|
12
|
-
import { humanSend as buildHumanSend } from '../../commands/human/prompts.js';
|
|
13
|
-
const humanSend = buildHumanSend();
|
|
14
|
-
import { getNode, listNodes, closeDb } from '../canvas/index.js';
|
|
15
|
-
import { coalesce, readInboxSince } from '../feed/inbox.js';
|
|
16
|
-
import { createPageFeedbackComment } from '../human/feedback.js';
|
|
17
|
-
import { resolveRef } from '../canvas/history.js';
|
|
18
|
-
import { transition } from '../runtime/lifecycle.js';
|
|
19
|
-
import { reviveNode } from '../runtime/revive.js';
|
|
20
|
-
import { apiSocketPath } from '../canvas/paths.js';
|
|
21
|
-
import { spawnNode } from '../runtime/nodes.js';
|
|
22
|
-
import { bindTicketReplyRoute } from '../human/convention.js';
|
|
23
|
-
import { ticketDir } from '../human/root.js';
|
|
24
|
-
import { pageFeedbackPath } from '../human/feedback.js';
|
|
25
|
-
import { parseCard } from '../../shared/generated-context.js';
|
|
26
|
-
import { readTicketResult, submitPage } from '../human/tickets.js';
|
|
27
|
-
let home;
|
|
28
|
-
let priorHome;
|
|
29
|
-
let server;
|
|
30
|
-
async function waitForSocket(path) {
|
|
31
|
-
for (let i = 0; i < 200; i++) {
|
|
32
|
-
if (existsSync(path))
|
|
33
|
-
return;
|
|
34
|
-
await new Promise((r) => setTimeout(r, 25));
|
|
35
|
-
}
|
|
36
|
-
throw new Error(`api socket never bound at ${path}`);
|
|
37
|
-
}
|
|
38
|
-
async function withMutedStdout(fn) {
|
|
39
|
-
const originalWrite = process.stdout.write;
|
|
40
|
-
process.stdout.write = (() => true);
|
|
41
|
-
try {
|
|
42
|
-
return await fn();
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
process.stdout.write = originalWrite;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function bridgeUnder() {
|
|
49
|
-
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
50
|
-
const bridge = spawnNode({ kind: 'human', parent, creator: parent, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
51
|
-
return { parent, bridge };
|
|
52
|
-
}
|
|
53
|
-
function submitBridgePage(bridge) {
|
|
54
|
-
const dir = ticketDir(bridge);
|
|
55
|
-
const sourceDir = mkdtempSync(join(tmpdir(), 'crtr-deliver-page-'));
|
|
56
|
-
const sourceFile = join(sourceDir, 'page.tsx');
|
|
57
|
-
writeFileSync(sourceFile, 'export default () => (\n <Page title="Ship it?" subtitle="Decide whether to ship now so the release can proceed.">\n <UserText id="question" initialText="" />\n </Page>\n);\n');
|
|
58
|
-
try {
|
|
59
|
-
bindTicketReplyRoute(dir, bridge);
|
|
60
|
-
submitPage({ dir, sourceFile, delivery: { placement: 'inline', inbox: true, reply: true } });
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
rmSync(sourceDir, { recursive: true, force: true });
|
|
64
|
-
}
|
|
65
|
-
return dir;
|
|
66
|
-
}
|
|
67
|
-
function reportBody(ref) {
|
|
68
|
-
const resolved = resolveRef(ref);
|
|
69
|
-
if (resolved === null || resolved.source !== 'report')
|
|
70
|
-
throw new Error(`push report ref did not resolve to a report: ${ref}`);
|
|
71
|
-
return resolved.body;
|
|
72
|
-
}
|
|
73
|
-
before(() => {
|
|
74
|
-
priorHome = process.env['HOME'];
|
|
75
|
-
});
|
|
76
|
-
beforeEach(async () => {
|
|
77
|
-
closeDb();
|
|
78
|
-
home = mkdtempSync(join(tmpdir(), 'crtr-deliver-home-'));
|
|
79
|
-
process.env['CRTR_HOME'] = home;
|
|
80
|
-
process.env['HOME'] = home;
|
|
81
|
-
delete process.env['CRTR_NODE_ID'];
|
|
82
|
-
server = createApiServer();
|
|
83
|
-
await waitForSocket(apiSocketPath());
|
|
84
|
-
});
|
|
85
|
-
afterEach(async () => {
|
|
86
|
-
await server?.close();
|
|
87
|
-
rmSync(home, { recursive: true, force: true });
|
|
88
|
-
});
|
|
89
|
-
after(() => {
|
|
90
|
-
closeDb();
|
|
91
|
-
if (priorHome !== undefined)
|
|
92
|
-
process.env['HOME'] = priorHome;
|
|
93
|
-
else
|
|
94
|
-
delete process.env['HOME'];
|
|
95
|
-
delete process.env['CRTR_HOME'];
|
|
96
|
-
delete process.env['CRTR_NODE_ID'];
|
|
97
|
-
});
|
|
98
|
-
describe('enqueue is producer-only', () => {
|
|
99
|
-
test('send preserves title and subtitle from the authored page', async () => {
|
|
100
|
-
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
101
|
-
process.env['CRTR_NODE_ID'] = parent;
|
|
102
|
-
const page = join(home, 'release-decision.tsx');
|
|
103
|
-
writeFileSync(page, 'export default () => (\n <Page title="Release decision" subtitle="Decide whether to release now so the launch can proceed.">\n <p>This first line stays page content.</p>\n <UserText id="question" initialText="" />\n </Page>\n);\n');
|
|
104
|
-
await assert.rejects(() => humanSend.run({}), /a page source is required/);
|
|
105
|
-
await assert.rejects(() => humanSend.run({ page, document: 'export default () => <Page title="Dup" />;' }), /received both --page and a piped page document/);
|
|
106
|
-
const result = (await humanSend.run({ page, document: '' }));
|
|
107
|
-
assert.equal(typeof result['page_id'], 'string');
|
|
108
|
-
assert.equal(result['reply'], true);
|
|
109
|
-
assert.equal(typeof result['dir'], 'string');
|
|
110
|
-
for (const forbidden of ['pane_id', 'render_pane_id', 'pid', 'worker_pid'])
|
|
111
|
-
assert.ok(!(forbidden in result), `send result must not expose ${forbidden}`);
|
|
112
|
-
const manifest = JSON.parse(readFileSync(join(result['dir'], 'page.json'), 'utf8'));
|
|
113
|
-
assert.equal(manifest.title, 'Release decision');
|
|
114
|
-
assert.equal(manifest.subtitle, 'Decide whether to release now so the launch can proceed.');
|
|
115
|
-
const document = readFileSync(join(result['dir'], 'page.tsx'), 'utf8');
|
|
116
|
-
assert.ok(document.includes('This first line stays page content.'));
|
|
117
|
-
assert.equal(manifest.slots.filter((slot) => slot.kind === 'text').length, 1);
|
|
118
|
-
});
|
|
119
|
-
test('send materializes a piped page before validation and publication', async () => {
|
|
120
|
-
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
121
|
-
process.env['CRTR_NODE_ID'] = parent;
|
|
122
|
-
const document = 'export default () => (\n <Page title="Piped question" subtitle="Answer the question to unblock this task.">\n <UserText id="question" initialText="" />\n </Page>\n);\n';
|
|
123
|
-
const result = (await humanSend.run({ document }));
|
|
124
|
-
const manifest = JSON.parse(readFileSync(join(result['dir'], 'page.json'), 'utf8'));
|
|
125
|
-
assert.equal(manifest.title, 'Piped question');
|
|
126
|
-
assert.equal(readFileSync(join(result['dir'], 'page.tsx'), 'utf8'), document);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
describe('terminal ticket delivery', () => {
|
|
130
|
-
test("resolvePageTicket pushFinals the person's words once and wakes the subscribed parent", async () => {
|
|
131
|
-
const { parent, bridge } = bridgeUnder();
|
|
132
|
-
const dir = submitBridgePage(bridge);
|
|
133
|
-
const result = await resolvePageTicket(bridge, { question: { text: 'yes, ship', edited: true } });
|
|
134
|
-
assert.equal(result.kind, 'page');
|
|
135
|
-
closeDb();
|
|
136
|
-
assert.equal(getNode(bridge)?.status, 'done');
|
|
137
|
-
const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
|
|
138
|
-
assert.equal(finals.length, 1, 'exactly one pushFinal reached the parent');
|
|
139
|
-
const body = reportBody(finals[0].ref);
|
|
140
|
-
assert.equal(body, 'yes, ship');
|
|
141
|
-
assert.equal(finals[0].disposition, 'human-answer');
|
|
142
|
-
assert.ok(!body.includes(join(dir, 'response.json')));
|
|
143
|
-
});
|
|
144
|
-
test('settlement delivers feedback outside the human-answer entry', async () => {
|
|
145
|
-
const { parent, bridge } = bridgeUnder();
|
|
146
|
-
const dir = submitBridgePage(bridge);
|
|
147
|
-
writeFileSync(pageFeedbackPath(dir), JSON.stringify({
|
|
148
|
-
schema: 'crtr.page-feedback/v1',
|
|
149
|
-
companionNodeId: 'feedback-companion',
|
|
150
|
-
firstCommentedSha256: 'stale-page-hash',
|
|
151
|
-
comments: [],
|
|
152
|
-
}));
|
|
153
|
-
await resolvePageTicket(bridge, { question: { text: 'yes, ship', edited: true } });
|
|
154
|
-
closeDb();
|
|
155
|
-
const entries = readInboxSince(parent).filter((entry) => entry.from === bridge);
|
|
156
|
-
const final = entries.find((entry) => entry.kind === 'final');
|
|
157
|
-
const feedback = entries.find((entry) => entry.kind === 'update');
|
|
158
|
-
assert.ok(final);
|
|
159
|
-
assert.ok(feedback);
|
|
160
|
-
assert.equal(final.disposition, 'human-answer');
|
|
161
|
-
assert.match(reportBody(feedback.ref), /The page changed during feedback/);
|
|
162
|
-
const deliveries = coalesce(entries);
|
|
163
|
-
assert.equal(deliveries.length, 1);
|
|
164
|
-
const card = parseCard({ role: 'user', content: deliveries[0].text });
|
|
165
|
-
assert.ok(card);
|
|
166
|
-
const answer = card.senders.flatMap((sender) => sender.entries).find((entry) => entry.disposition === 'human-answer');
|
|
167
|
-
assert.deepEqual(answer, { kind: 'final', disposition: 'human-answer', body: `yes, ship\n\nRead this report with \`crtr canvas history read ${final.ref}\`.`, ref: final.ref });
|
|
168
|
-
});
|
|
169
|
-
test('a replay against a terminal bridge is a no-op', async () => {
|
|
170
|
-
const { parent, bridge } = bridgeUnder();
|
|
171
|
-
submitBridgePage(bridge);
|
|
172
|
-
await resolvePageTicket(bridge, { question: { text: 'yes', edited: true } });
|
|
173
|
-
assert.equal(await deliverTerminalResult(bridge), true);
|
|
174
|
-
closeDb();
|
|
175
|
-
const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
|
|
176
|
-
assert.equal(finals.length, 1, 'replay after a terminal bridge pushes nothing further');
|
|
177
|
-
});
|
|
178
|
-
test('approved companions receive one canonical final while dismissed companions remain canceled', async () => {
|
|
179
|
-
const { bridge } = bridgeUnder();
|
|
180
|
-
const dir = submitBridgePage(bridge);
|
|
181
|
-
const companion = spawnNode({ kind: 'general', parent: bridge, creator: bridge, cwd: home, name: 'feedback companion', lifecycle: 'resident' }).node_id;
|
|
182
|
-
createPageFeedbackComment(dir, { quote: 'Ship it?', note: 'Confirm the release.', companionNodeId: companion });
|
|
183
|
-
transition(companion, 'finish');
|
|
184
|
-
await resolvePageTicket(bridge, { question: { text: 'yes', edited: true } });
|
|
185
|
-
const finalized = getNode(companion);
|
|
186
|
-
assert.equal(finalized.status, 'done');
|
|
187
|
-
assert.equal(finalized.intent, 'done');
|
|
188
|
-
assert.ok(finalized.final_report);
|
|
189
|
-
assert.ok(finalized.finalized_at);
|
|
190
|
-
const finalReport = finalized.final_report;
|
|
191
|
-
const finalizedAt = finalized.finalized_at;
|
|
192
|
-
await deliverTerminalResult(bridge);
|
|
193
|
-
assert.equal(getNode(companion)?.final_report, finalReport, 'terminal replay preserves the first canonical final');
|
|
194
|
-
assert.equal(getNode(companion)?.finalized_at, finalizedAt, 'terminal replay preserves the first finalization time');
|
|
195
|
-
await assert.rejects(reviveNode(companion, { resume: true, capacity: 'freeze' }), /finalization latch/);
|
|
196
|
-
const { bridge: dismissedBridge } = bridgeUnder();
|
|
197
|
-
const dismissedDir = submitBridgePage(dismissedBridge);
|
|
198
|
-
const dismissedCompanion = spawnNode({ kind: 'general', parent: dismissedBridge, creator: dismissedBridge, cwd: home, name: 'dismissed feedback companion', lifecycle: 'resident' }).node_id;
|
|
199
|
-
createPageFeedbackComment(dismissedDir, { quote: 'Ship it?', note: 'Do not ship.', companionNodeId: dismissedCompanion });
|
|
200
|
-
await cancelHumanTicket(dismissedBridge, { reason: 'dismissed', actor: 'human' });
|
|
201
|
-
assert.equal(getNode(dismissedCompanion)?.status, 'canceled');
|
|
202
|
-
assert.equal(getNode(dismissedCompanion)?.final_report, null);
|
|
203
|
-
const dismissedResult = readTicketResult(dismissedDir);
|
|
204
|
-
await settleFeedbackCompanion(dismissedBridge, dismissedResult.kind === 'canceled' ? 'canceled' : 'approved');
|
|
205
|
-
assert.equal(getNode(dismissedCompanion)?.status, 'canceled');
|
|
206
|
-
assert.equal(getNode(dismissedCompanion)?.final_report, null);
|
|
207
|
-
});
|
|
208
|
-
test('cancelHumanTicket finishes the bridge and notifies non-actor subscribers', async () => {
|
|
209
|
-
const actor = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'actor' }).node_id;
|
|
210
|
-
const bridge = spawnNode({ kind: 'human', parent: actor, creator: actor, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
211
|
-
const other = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'other' }).node_id;
|
|
212
|
-
const { subscribe } = await import('../canvas/canvas.js');
|
|
213
|
-
subscribe(other, bridge, true);
|
|
214
|
-
submitBridgePage(bridge);
|
|
215
|
-
await cancelHumanTicket(bridge, { reason: 'stale', actor });
|
|
216
|
-
closeDb();
|
|
217
|
-
assert.equal(getNode(bridge)?.status, 'done');
|
|
218
|
-
assert.equal(readInboxSince(actor).length, 0, 'the canceling actor is not messaged');
|
|
219
|
-
const notes = readInboxSince(other);
|
|
220
|
-
assert.equal(notes.length, 1, 'the other waiter gets one waking note');
|
|
221
|
-
assert.equal(notes[0].tier, 'normal');
|
|
222
|
-
assert.match(notes[0].label, /canceled/);
|
|
223
|
-
});
|
|
224
|
-
test('cancellation reaches subscribers when the bridge is already done, and retries conflict rather than fail', async () => {
|
|
225
|
-
const actor = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'actor' }).node_id;
|
|
226
|
-
const bridge = spawnNode({ kind: 'human', parent: actor, creator: actor, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
227
|
-
const other = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'other' }).node_id;
|
|
228
|
-
const { subscribe } = await import('../canvas/canvas.js');
|
|
229
|
-
subscribe(other, bridge, true);
|
|
230
|
-
submitBridgePage(bridge);
|
|
231
|
-
// The bridge is parked done with no canonical final while its page is still
|
|
232
|
-
// pending — done is where cancellation wants it, so cancelling must deliver.
|
|
233
|
-
transition(bridge, 'finish');
|
|
234
|
-
const result = await cancelHumanTicket(bridge, { reason: 'stale', actor });
|
|
235
|
-
assert.equal(result.kind, 'canceled');
|
|
236
|
-
assert.equal(getNode(bridge)?.status, 'done');
|
|
237
|
-
assert.equal(readInboxSince(other).length, 1, 'the other waiter is told no answer is coming');
|
|
238
|
-
// The recorded cancellation is final: a retry is an already-resolved
|
|
239
|
-
// conflict, never a delivery failure that keeps repeating.
|
|
240
|
-
await assert.rejects(() => cancelHumanTicket(bridge, { reason: 'stale', actor }), (error) => error instanceof ApiError && error.status === 409 && error.code === 'ticket_already_resolved');
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
test('a display-only page publishes passively and creates no canvas node', async () => {
|
|
244
|
-
const page = join(home, 'build-done.tsx');
|
|
245
|
-
writeFileSync(page, 'export default () => (\n <Page title="Build done" subtitle="The build passed, so the change is ready to review.">\n <p>Build output is ready.</p>\n </Page>\n);\n');
|
|
246
|
-
const before = listNodes().length;
|
|
247
|
-
const result = (await humanSend.run({ page }));
|
|
248
|
-
assert.equal(result['reply'], false);
|
|
249
|
-
assert.equal(result['inbox'], false);
|
|
250
|
-
assert.equal(typeof result['page_id'], 'string');
|
|
251
|
-
assert.equal(typeof result['dir'], 'string');
|
|
252
|
-
assert.ok(existsSync(join(result['dir'], 'page.json')));
|
|
253
|
-
assert.ok(readTicketResult(result['dir']) === null);
|
|
254
|
-
closeDb();
|
|
255
|
-
assert.equal(listNodes().length, before);
|
|
256
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/human-node-not-supervised.test.ts
|
|
2
|
-
//
|
|
3
|
-
// BUG REGRESSION (issue #2 — "Headless node can't reach human-ask: human-kind
|
|
4
|
-
// TUI dies at boot"): a headless orchestrator's every reply-bearing `crtr human send` bridge died
|
|
5
|
-
// at boot with "pi vehicle exited before the session came up".
|
|
6
|
-
//
|
|
7
|
-
// ROOT CAUSE: a kind:'human' row is NOT a broker-hosted agent node — it is a
|
|
8
|
-
// bookkeeping row for the `crtr human` bridge. Nothing runs in it: the human
|
|
9
|
-
// answers from humanloop's inbox, and the row's lifecycle is driven entirely by
|
|
10
|
-
// humanloop's completion handler (`crtr human _deliver`) calling `pushFinal` (or
|
|
11
|
-
// `human cancel`). So it never records a `pi_pid` or `pi_session_id`. After the
|
|
12
|
-
// headless hard-cut made the broker the universal node host, the daemon
|
|
13
|
-
// supervised EVERY active|idle row as a broker engine: it read the human row
|
|
14
|
-
// (pid==null, session==null) as a never-booted broker and boot-failed it —
|
|
15
|
-
// killing the human prompt before the person could ever answer.
|
|
16
|
-
//
|
|
17
|
-
// THE FIX under exit-event authority is superviseTick's one filter, driven
|
|
18
|
-
// here: it drops kind:'human' rows before every row pass, including the
|
|
19
|
-
// node-lifecycle tick that classifies a row with no live broker as known-dead.
|
|
20
|
-
// An unfiltered human row (session null, no fork claim) would land in policy
|
|
21
|
-
// row 8 (boot failure), the modern re-incarnation of the exact bug.
|
|
22
|
-
import { test, before, after, beforeEach } from 'node:test';
|
|
23
|
-
import assert from 'node:assert/strict';
|
|
24
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
25
|
-
import { tmpdir } from 'node:os';
|
|
26
|
-
import { join } from 'node:path';
|
|
27
|
-
import { createNode, getNode, subscribe } from '../canvas/canvas.js';
|
|
28
|
-
import { closeDb } from '../canvas/db.js';
|
|
29
|
-
import { readInboxSince } from '../feed/inbox.js';
|
|
30
|
-
import { superviseTick } from '../../daemon/crtrd.js';
|
|
31
|
-
import { bindTestFleet } from './helpers/fleet.js';
|
|
32
|
-
import { resetFleetForTests } from '../runtime/fleet.js';
|
|
33
|
-
let home;
|
|
34
|
-
let tf;
|
|
35
|
-
function node(id, over = {}) {
|
|
36
|
-
return {
|
|
37
|
-
node_id: id,
|
|
38
|
-
name: id,
|
|
39
|
-
created: new Date().toISOString(),
|
|
40
|
-
cwd: '/tmp/work',
|
|
41
|
-
kind: 'general',
|
|
42
|
-
mode: 'base',
|
|
43
|
-
lifecycle: 'terminal',
|
|
44
|
-
status: 'active',
|
|
45
|
-
...over,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
before(() => {
|
|
49
|
-
home = mkdtempSync(join(tmpdir(), 'crtr-human-not-supervised-'));
|
|
50
|
-
process.env['CRTR_HOME'] = home;
|
|
51
|
-
tf = bindTestFleet(); // superviseTick requires a bound fleet (D-2)
|
|
52
|
-
});
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
closeDb();
|
|
55
|
-
rmSync(home, { recursive: true, force: true });
|
|
56
|
-
});
|
|
57
|
-
after(() => {
|
|
58
|
-
closeDb();
|
|
59
|
-
rmSync(home, { recursive: true, force: true });
|
|
60
|
-
delete process.env['CRTR_HOME'];
|
|
61
|
-
tf.fleet.dispose();
|
|
62
|
-
resetFleetForTests();
|
|
63
|
-
});
|
|
64
|
-
test('a kind:"human" bridge row is NEVER boot-failed by the daemon (it has no broker engine)', async () => {
|
|
65
|
-
// The exact shape spawnNode({kind:'human'}) mints: active, host_kind broker,
|
|
66
|
-
// but NO pi engine ever booted — so pi_pid and pi_session_id stay null.
|
|
67
|
-
createNode(node('HUMAN', {
|
|
68
|
-
kind: 'human',
|
|
69
|
-
lifecycle: 'terminal',
|
|
70
|
-
host_kind: 'broker',
|
|
71
|
-
pi_pid: null,
|
|
72
|
-
pi_session_id: null,
|
|
73
|
-
intent: null,
|
|
74
|
-
}));
|
|
75
|
-
// The asking node is auto-subscribed to its human bridge node at spawn, so a
|
|
76
|
-
// surfaceBootFailure (pushUrgent) would land in ASKER's inbox. Park it as a
|
|
77
|
-
// dormant idle-release row (policy row 4 / pass-2 no-op) so both the sweep and
|
|
78
|
-
// the tick leave IT alone — it is only here to catch a push.
|
|
79
|
-
createNode(node('ASKER', { status: 'idle', intent: 'idle-release', pi_session_id: 'booted' }));
|
|
80
|
-
subscribe('ASKER', 'HUMAN', true);
|
|
81
|
-
// Two well-spaced ticks: the row passes (controller-wait settle, inbox wake,
|
|
82
|
-
// and the node-lifecycle classification of a row with no live broker) must
|
|
83
|
-
// all drop the human row before reading it.
|
|
84
|
-
const t0 = Date.now();
|
|
85
|
-
await superviseTick(t0);
|
|
86
|
-
await superviseTick(t0 + 10_000_000);
|
|
87
|
-
assert.equal(getNode('HUMAN').status, 'active', 'human bridge row stays alive — never crash-reaped as a dead broker');
|
|
88
|
-
assert.equal(readInboxSince('ASKER').length, 0, 'no surfaceBootFailure urgent push — the asker is never told its human prompt "never started"');
|
|
89
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// Run with: npm run test:integration
|
|
2
|
-
//
|
|
3
|
-
// Drive a terminal ticket through the public crtrd ticket route. Terminal
|
|
4
|
-
// delivery is daemon-owned; no subprocess handler or delivery receipt exists.
|
|
5
|
-
import { test, before, beforeEach, afterEach, after } from 'node:test';
|
|
6
|
-
import assert from 'node:assert/strict';
|
|
7
|
-
import { mkdtempSync, rmSync, existsSync, writeFileSync } from 'node:fs';
|
|
8
|
-
import { tmpdir } from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import { ApiError, CrtrClient } from '../../../api/index.js';
|
|
11
|
-
import { createApiServer } from '../../../daemon/api/server.js';
|
|
12
|
-
import { apiSocketPath } from '../../canvas/paths.js';
|
|
13
|
-
import { claimTicket, readTicketClaim } from '../../human/claim.js';
|
|
14
|
-
import { bindTicketReplyRoute } from '../../human/convention.js';
|
|
15
|
-
import { ticketDir } from '../../human/root.js';
|
|
16
|
-
import { submitPage, submitReview } from '../../human/tickets.js';
|
|
17
|
-
import { spawnNode } from '../../runtime/nodes.js';
|
|
18
|
-
import { getNode, closeDb } from '../../canvas/index.js';
|
|
19
|
-
import { readInboxSince } from '../../feed/inbox.js';
|
|
20
|
-
import { resolveRef } from '../../canvas/history.js';
|
|
21
|
-
let home;
|
|
22
|
-
let priorHome;
|
|
23
|
-
let server;
|
|
24
|
-
let client;
|
|
25
|
-
async function waitForSocket(path) {
|
|
26
|
-
for (let i = 0; i < 200; i++) {
|
|
27
|
-
if (existsSync(path))
|
|
28
|
-
return;
|
|
29
|
-
await new Promise((r) => setTimeout(r, 25));
|
|
30
|
-
}
|
|
31
|
-
throw new Error(`api socket never bound at ${path}`);
|
|
32
|
-
}
|
|
33
|
-
before(() => {
|
|
34
|
-
priorHome = process.env['HOME'];
|
|
35
|
-
});
|
|
36
|
-
beforeEach(async () => {
|
|
37
|
-
closeDb();
|
|
38
|
-
home = mkdtempSync(join(tmpdir(), 'crtr-e2e-home-'));
|
|
39
|
-
process.env['CRTR_HOME'] = home;
|
|
40
|
-
process.env['HOME'] = home;
|
|
41
|
-
delete process.env['CRTR_NODE_ID'];
|
|
42
|
-
server = createApiServer();
|
|
43
|
-
await waitForSocket(apiSocketPath());
|
|
44
|
-
client = new CrtrClient({ socketPath: apiSocketPath(), autostart: false });
|
|
45
|
-
});
|
|
46
|
-
afterEach(async () => {
|
|
47
|
-
await server?.close();
|
|
48
|
-
server = undefined;
|
|
49
|
-
closeDb();
|
|
50
|
-
});
|
|
51
|
-
after(() => {
|
|
52
|
-
closeDb();
|
|
53
|
-
if (priorHome !== undefined)
|
|
54
|
-
process.env['HOME'] = priorHome;
|
|
55
|
-
else
|
|
56
|
-
delete process.env['HOME'];
|
|
57
|
-
delete process.env['CRTR_HOME'];
|
|
58
|
-
delete process.env['CRTR_NODE_ID'];
|
|
59
|
-
});
|
|
60
|
-
async function expectBadResolve(nodeId, body) {
|
|
61
|
-
await assert.rejects(() => client.request('POST', `/v1/human/tickets/${nodeId}/resolve`, body), (error) => error instanceof ApiError && error.status === 400 && error.code === 'invalid_request' && !error.message.includes('POST /v1/human/reviews/:review_id/submit'));
|
|
62
|
-
}
|
|
63
|
-
async function expectReviewSubmitGuidance(nodeId, body) {
|
|
64
|
-
await assert.rejects(() => client.request('POST', `/v1/human/tickets/${nodeId}/resolve`, body), (error) => error instanceof ApiError && error.status === 400 && error.code === 'invalid_request' && error.message.includes('POST /v1/human/reviews/:review_id/submit'));
|
|
65
|
-
}
|
|
66
|
-
/** Publish a one-text-slot page ticket into the bridge's ticket directory. */
|
|
67
|
-
function submitBridgePage(bridge) {
|
|
68
|
-
const dir = ticketDir(bridge);
|
|
69
|
-
const sourceDir = mkdtempSync(join(tmpdir(), 'crtr-e2e-page-'));
|
|
70
|
-
const sourceFile = join(sourceDir, 'page.tsx');
|
|
71
|
-
writeFileSync(sourceFile, 'export default () => (\n <Page title="Ship it?" subtitle="Decide whether to ship now so the release can proceed.">\n <UserText id="question" initialText="" />\n </Page>\n);\n');
|
|
72
|
-
try {
|
|
73
|
-
bindTicketReplyRoute(dir, bridge);
|
|
74
|
-
// The delivery `human send` derives for a terminal bridge page: a
|
|
75
|
-
// response-bearing slot gives it a reply route, and a terminal sender's page
|
|
76
|
-
// always projects to the inbox.
|
|
77
|
-
submitPage({ dir, sourceFile, delivery: { placement: 'inline', inbox: true, reply: true } });
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
rmSync(sourceDir, { recursive: true, force: true });
|
|
81
|
-
}
|
|
82
|
-
return dir;
|
|
83
|
-
}
|
|
84
|
-
function reportBody(ref) {
|
|
85
|
-
const resolved = resolveRef(ref);
|
|
86
|
-
if (resolved === null || resolved.source !== 'report')
|
|
87
|
-
throw new Error(`push report ref did not resolve to a report: ${ref}`);
|
|
88
|
-
return resolved.body;
|
|
89
|
-
}
|
|
90
|
-
test('malformed resolves leave the mounted ticket claim in place', async () => {
|
|
91
|
-
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
92
|
-
const pageBridge = spawnNode({ kind: 'human', parent, creator: parent, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
93
|
-
const pageDir = submitBridgePage(pageBridge);
|
|
94
|
-
const pageClaim = claimTicket(pageDir);
|
|
95
|
-
assert.ok(pageClaim !== null);
|
|
96
|
-
for (const body of [{ responses: [] }, { response: {} }, { responses: {}, debug: true }]) {
|
|
97
|
-
await expectBadResolve(pageBridge, body);
|
|
98
|
-
assert.equal(readTicketClaim(pageDir)?.token, pageClaim.token, 'malformed page input must not take over the live claim');
|
|
99
|
-
}
|
|
100
|
-
for (const body of [{ responses: {}, feedback: {} }, { feedback: [] }]) {
|
|
101
|
-
await expectReviewSubmitGuidance(pageBridge, body);
|
|
102
|
-
assert.equal(readTicketClaim(pageDir)?.token, pageClaim.token, 'feedback-shaped input must not take over the live claim');
|
|
103
|
-
}
|
|
104
|
-
const reviewBridge = spawnNode({ kind: 'human', parent, creator: parent, cwd: home, name: 'human-review', lifecycle: 'terminal' }).node_id;
|
|
105
|
-
const reviewDir = ticketDir(reviewBridge);
|
|
106
|
-
const file = join(home, 'review.md');
|
|
107
|
-
writeFileSync(file, '# Review\n');
|
|
108
|
-
bindTicketReplyRoute(reviewDir, reviewBridge);
|
|
109
|
-
submitReview({ dir: reviewDir, review: { title: 'Review', subtitle: 'Review this file so implementation can proceed safely.', source: {}, file } });
|
|
110
|
-
const reviewClaim = claimTicket(reviewDir);
|
|
111
|
-
assert.ok(reviewClaim !== null);
|
|
112
|
-
await expectReviewSubmitGuidance(reviewBridge, { responses: {} });
|
|
113
|
-
assert.equal(readTicketClaim(reviewDir)?.token, reviewClaim.token, 'review ticket resolve must not take over the live claim');
|
|
114
|
-
});
|
|
115
|
-
test('the ticket resolve route pushFinals the readable answer to the parent', async () => {
|
|
116
|
-
const parent = spawnNode({ kind: 'general', parent: null, cwd: home, name: 'asker' }).node_id;
|
|
117
|
-
const bridge = spawnNode({ kind: 'human', parent, creator: parent, cwd: home, name: 'human-page', lifecycle: 'terminal' }).node_id;
|
|
118
|
-
const dir = submitBridgePage(bridge);
|
|
119
|
-
const result = await client.resolveHumanTicket(bridge, { responses: { question: { text: 'yes, ship', edited: true } } });
|
|
120
|
-
assert.deepEqual(result, { completed: true, reply_delivered: true, kind: 'page' });
|
|
121
|
-
assert.equal(getNode(bridge)?.status, 'done', 'bridge finalized by crtrd');
|
|
122
|
-
const finals = readInboxSince(parent).filter((entry) => entry.from === bridge && entry.kind === 'final');
|
|
123
|
-
assert.equal(finals.length, 1, 'exactly one pushFinal reached the parent');
|
|
124
|
-
// A typed answer's final is purely the person's words, attributed by the
|
|
125
|
-
// human-answer disposition — no record path, no "Answered …" trailer.
|
|
126
|
-
assert.equal(finals[0].disposition, 'human-answer');
|
|
127
|
-
const body = reportBody(finals[0].ref);
|
|
128
|
-
assert.ok(body.includes('yes, ship'));
|
|
129
|
-
assert.ok(!body.includes(join(dir, 'response.json')));
|
|
130
|
-
assert.ok(!body.includes('humanloop.response/v2'));
|
|
131
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { test, before, after, beforeEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { closeDb, openDb } from '../canvas/db.js';
|
|
7
|
-
import { approveReviewLocked, bindBridgeNode, cancelReviewRow, commitBindingReview, getReview, getReviewByBridge, getReviewByCompanion, listReviews, markDelivered, markDeliveryError, markProjected, markProjectionError, openReviewRow, readAnchorBase, } from '../review/store.js';
|
|
8
|
-
let home;
|
|
9
|
-
function row(overrides = {}) {
|
|
10
|
-
return {
|
|
11
|
-
review_id: 'review-one',
|
|
12
|
-
idempotency_key: 'request-one',
|
|
13
|
-
origin_kind: 'inline',
|
|
14
|
-
origin_node_id: 'origin-one',
|
|
15
|
-
bridge_node_id: null,
|
|
16
|
-
companion_node_id: 'companion-one',
|
|
17
|
-
review_dir: '/tmp/review-one',
|
|
18
|
-
file: '/tmp/does-not-exist.txt',
|
|
19
|
-
origin_session_file: '/tmp/origin.jsonl',
|
|
20
|
-
origin_session_id: 'session-one',
|
|
21
|
-
origin_leaf_id: 'leaf-one',
|
|
22
|
-
model_spec: 'anthropic/claude:test',
|
|
23
|
-
branch_file: '/tmp/review-one/branch-point.jsonl',
|
|
24
|
-
baseline_sha256: 'baseline-hash',
|
|
25
|
-
baseline_bytes: 3,
|
|
26
|
-
anchor_base_sha256: 'anchor-hash',
|
|
27
|
-
anchor_base_bytes: Buffer.from('abc'),
|
|
28
|
-
state: 'binding',
|
|
29
|
-
created: '2026-01-01T00:00:00.000Z',
|
|
30
|
-
captured_at: '2026-01-01T00:00:00.000Z',
|
|
31
|
-
opened_at: null,
|
|
32
|
-
submit_requested_at: null,
|
|
33
|
-
output_path: null,
|
|
34
|
-
subtitle: null,
|
|
35
|
-
approved_at: null,
|
|
36
|
-
approved_sha256: null,
|
|
37
|
-
changed: null,
|
|
38
|
-
comments_total: null,
|
|
39
|
-
comments_unresolved: null,
|
|
40
|
-
result_json: null,
|
|
41
|
-
canceled_at: null,
|
|
42
|
-
cancel_reason: null,
|
|
43
|
-
cancel_actor: null,
|
|
44
|
-
delivery_entry_id: null,
|
|
45
|
-
delivered_at: null,
|
|
46
|
-
delivery_error: null,
|
|
47
|
-
projected_at: null,
|
|
48
|
-
projection_error: null,
|
|
49
|
-
...overrides,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
before(() => {
|
|
53
|
-
home = mkdtempSync(join(tmpdir(), 'crtr-phase4-review-store-'));
|
|
54
|
-
process.env['CRTR_HOME'] = home;
|
|
55
|
-
});
|
|
56
|
-
beforeEach(() => {
|
|
57
|
-
closeDb();
|
|
58
|
-
rmSync(home, { recursive: true, force: true });
|
|
59
|
-
});
|
|
60
|
-
after(() => {
|
|
61
|
-
closeDb();
|
|
62
|
-
rmSync(home, { recursive: true, force: true });
|
|
63
|
-
delete process.env['CRTR_HOME'];
|
|
64
|
-
});
|
|
65
|
-
test('reviews DAO owns idempotent binding and conditional lifecycle writes', () => {
|
|
66
|
-
const first = row();
|
|
67
|
-
assert.equal(existsSync(first.review_dir), false, 'the birth commit does not create filesystem state');
|
|
68
|
-
assert.deepEqual(commitBindingReview(first), { review_id: 'review-one', won: true });
|
|
69
|
-
assert.deepEqual(commitBindingReview(row({ review_id: 'review-two', companion_node_id: 'companion-two' })), {
|
|
70
|
-
review_id: 'review-one',
|
|
71
|
-
won: false,
|
|
72
|
-
});
|
|
73
|
-
assert.equal(listReviews({}).length, 0, 'binding rows are hidden from ordinary review lists');
|
|
74
|
-
assert.equal(getReview(first.review_id)?.source_missing, true, 'source_missing is computed from the current filesystem');
|
|
75
|
-
assert.equal(getReviewByCompanion(first.companion_node_id), null);
|
|
76
|
-
assert.equal(bindBridgeNode(first.review_id, 'bridge-one'), true);
|
|
77
|
-
assert.equal(bindBridgeNode(first.review_id, 'bridge-two'), false);
|
|
78
|
-
assert.equal(getReviewByBridge('bridge-one'), null, 'binding rows are not exposed by bridge lookup');
|
|
79
|
-
assert.equal(openReviewRow(first.review_id, '2026-01-01T00:01:00.000Z'), true);
|
|
80
|
-
assert.equal(openReviewRow(first.review_id, '2026-01-01T00:02:00.000Z'), false);
|
|
81
|
-
assert.equal(getReviewByBridge('bridge-one')?.review_id, first.review_id);
|
|
82
|
-
const db = openDb();
|
|
83
|
-
assert.equal(approveReviewLocked(db, {
|
|
84
|
-
review_id: first.review_id,
|
|
85
|
-
approved_at: '2026-01-01T00:03:00.000Z',
|
|
86
|
-
approved_sha256: 'approved-hash',
|
|
87
|
-
changed: true,
|
|
88
|
-
comments_total: 2,
|
|
89
|
-
comments_unresolved: 1,
|
|
90
|
-
result_json: '{"approved":true}',
|
|
91
|
-
}), true);
|
|
92
|
-
assert.equal(approveReviewLocked(db, {
|
|
93
|
-
review_id: first.review_id,
|
|
94
|
-
approved_at: '2026-01-01T00:04:00.000Z',
|
|
95
|
-
approved_sha256: 'approved-hash-2',
|
|
96
|
-
changed: false,
|
|
97
|
-
comments_total: 0,
|
|
98
|
-
comments_unresolved: 0,
|
|
99
|
-
result_json: '{}',
|
|
100
|
-
}), false, 'a second approval cannot win the conditional write');
|
|
101
|
-
markProjectionError(first.review_id, 'temporary projection failure');
|
|
102
|
-
markProjected(first.review_id, '2026-01-01T00:05:00.000Z');
|
|
103
|
-
markDeliveryError(first.review_id, 'temporary delivery failure');
|
|
104
|
-
markDelivered(first.review_id, 'receipt-one', '2026-01-01T00:06:00.000Z');
|
|
105
|
-
const approved = getReview(first.review_id);
|
|
106
|
-
assert.equal(approved?.projection_error, null);
|
|
107
|
-
assert.equal(approved?.delivery_error, null);
|
|
108
|
-
assert.equal(approved?.delivered_at, '2026-01-01T00:06:00.000Z');
|
|
109
|
-
assert.deepEqual(readAnchorBase(first.review_id), { bytes: Buffer.from('abc'), sha256: 'anchor-hash' });
|
|
110
|
-
});
|
|
111
|
-
test('stale ledger errors cannot overwrite recorded success', () => {
|
|
112
|
-
const review = row({ review_id: 'review-ledgers', idempotency_key: 'request-ledgers', companion_node_id: 'companion-ledgers' });
|
|
113
|
-
commitBindingReview(review);
|
|
114
|
-
markProjected(review.review_id, '2026-01-01T00:05:00.000Z');
|
|
115
|
-
markProjectionError(review.review_id, 'stale projection failure');
|
|
116
|
-
markDelivered(review.review_id, 'receipt-ledgers', '2026-01-01T00:06:00.000Z');
|
|
117
|
-
markDeliveryError(review.review_id, 'stale delivery failure');
|
|
118
|
-
const recorded = getReview(review.review_id);
|
|
119
|
-
assert.equal(recorded?.projected_at, '2026-01-01T00:05:00.000Z');
|
|
120
|
-
assert.equal(recorded?.projection_error, null);
|
|
121
|
-
assert.equal(recorded?.delivered_at, '2026-01-01T00:06:00.000Z');
|
|
122
|
-
assert.equal(recorded?.delivery_error, null);
|
|
123
|
-
});
|
|
124
|
-
test('approval cannot win after cancellation and companion ids remain unique', () => {
|
|
125
|
-
const canceled = row({ review_id: 'review-canceled', idempotency_key: 'request-canceled', companion_node_id: 'companion-canceled' });
|
|
126
|
-
commitBindingReview(canceled);
|
|
127
|
-
assert.equal(openReviewRow(canceled.review_id, '2026-01-01T00:59:00.000Z'), true);
|
|
128
|
-
assert.equal(cancelReviewRow(canceled.review_id, { at: '2026-01-01T01:00:00.000Z', reason: 'not needed', actor: 'human' }), true);
|
|
129
|
-
assert.equal(approveReviewLocked(openDb(), {
|
|
130
|
-
review_id: canceled.review_id,
|
|
131
|
-
approved_at: '2026-01-01T01:01:00.000Z',
|
|
132
|
-
approved_sha256: 'hash',
|
|
133
|
-
changed: false,
|
|
134
|
-
comments_total: 0,
|
|
135
|
-
comments_unresolved: 0,
|
|
136
|
-
result_json: '{}',
|
|
137
|
-
}), false);
|
|
138
|
-
assert.throws(() => commitBindingReview(row({
|
|
139
|
-
review_id: 'review-duplicate-companion',
|
|
140
|
-
idempotency_key: 'request-duplicate-companion',
|
|
141
|
-
companion_node_id: 'companion-canceled',
|
|
142
|
-
state: 'binding',
|
|
143
|
-
})), /UNIQUE constraint failed: reviews\.companion_node_id/);
|
|
144
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|