@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.141
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/CHANGELOG.md +740 -0
- package/README.md +143 -1
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +376 -0
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +328 -18
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +1689 -545
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +52 -0
- package/dist/cli/runtime-process.js +153 -0
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +71 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +112 -0
- package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +728 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +140 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +557 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +363 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +751 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +683 -0
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +16 -0
- package/dist/harness-runtime/lib/native-children.mjs +113 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
- package/dist/harness-runtime/lib/process-json.d.mts +5 -0
- package/dist/harness-runtime/lib/process-json.mjs +201 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
- package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
- package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
- package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
- package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
- package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
- package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
- package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
- package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
- package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
- package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
- package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
- package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
- package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +28 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +250 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +25 -62
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1168 -107
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +115 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +202 -0
- package/dist/services/dashboard.js +143 -58
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +294 -153
- package/dist/services/eval-snapshots.js +1215 -116
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +308 -0
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +355 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +771 -325
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +1 -288
- package/dist/services/lifecycle-contract.js +1 -0
- package/dist/services/lifecycle-invocations.js +2007 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-lease-monitor.js +133 -0
- package/dist/services/managed-processes.js +475 -55
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +296 -0
- package/dist/services/memory-init-preflight.js +330 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +181 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +178 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +161 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +37 -17
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +385 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +560 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +165 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +1056 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +208 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1205 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-health.js +149 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +77 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +489 -0
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +1019 -247
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +210 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +193 -19
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +152 -116
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +234 -6
- package/dist/services/stage-work-graph.js +52 -0
- package/dist/services/tooling.js +41 -30
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +375 -0
- package/dist/services/vnext-code-review.js +223 -88
- package/dist/services/vnext-code.js +327 -226
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +80 -5
- package/dist/services/vnext-fanout.js +311 -19
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +279 -127
- package/dist/services/vnext-plan.js +228 -104
- package/dist/services/vnext-protocolize.js +531 -95
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1627 -199
- package/dist/services/workspace-bootstrap.js +52 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +105 -48
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +27 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +891 -39
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +89 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +43 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -86,7 +86,7 @@ function isRunEngineBinding(value) {
|
|
|
86
86
|
&& typeof record.run_id === "string"
|
|
87
87
|
&& typeof record.project_root === "string"
|
|
88
88
|
&& typeof record.bound_at === "string"
|
|
89
|
-
&& ["run_creation", "legacy_recovery"].includes(String(record.source))
|
|
89
|
+
&& ["run_creation", "legacy_recovery", "fork_upgrade"].includes(String(record.source))
|
|
90
90
|
&& typeof record.reason === "string"
|
|
91
91
|
&& Boolean(engine && typeof engine === "object" && !Array.isArray(engine)
|
|
92
92
|
&& typeof engine.package_name === "string"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createContext } from "../runtime/context.js";
|
|
5
|
+
import { AppError } from "../shared/errors.js";
|
|
6
|
+
import { canonicalPath } from "../storage/paths.js";
|
|
7
|
+
import { importGuardPath, rearmCompletedSnapshotImport } from "../storage/import-guard.js";
|
|
8
|
+
import { installCurrentEngine, upgradeForkedRunEngine, prepareForkEngine } from "./engines.js";
|
|
9
|
+
import { restoreEvalRunSnapshot, prepareEvalRunSnapshot, prepareEvalRunRestore, prepareSnapshotOperationalDecision, restoredWorkspaceRoot } from "./eval-snapshots.js";
|
|
10
|
+
export function prepareRunFork(input) {
|
|
11
|
+
const requestId = requireId(input.requestId, "request-id");
|
|
12
|
+
const snapshot = canonicalPath(input.snapshot, false);
|
|
13
|
+
const output = canonicalPath(input.output, false);
|
|
14
|
+
if (snapshot === output || snapshot.startsWith(`${output}${path.sep}`) || output.startsWith(`${snapshot}${path.sep}`)) {
|
|
15
|
+
throw new AppError("fork_path_conflict", "Fork output must not contain or be contained by its source snapshot", 2, { snapshot, output });
|
|
16
|
+
}
|
|
17
|
+
const manifestFile = path.join(snapshot, "snapshot.json");
|
|
18
|
+
const intentFile = path.join(output, ".fork-intent.json");
|
|
19
|
+
const receiptFile = path.join(output, "fork.json");
|
|
20
|
+
const engineVersion = requireText(input.engineVersion, "engine-version");
|
|
21
|
+
if (fs.existsSync(receiptFile)) {
|
|
22
|
+
const receipt = readJson(receiptFile);
|
|
23
|
+
const prior = receipt?.intent;
|
|
24
|
+
if (!prior || prior.request_id !== requestId || prior.snapshot !== snapshot || prior.engine_version !== engineVersion || prior.integrity_checksum !== (input.integrityChecksum ?? null)
|
|
25
|
+
|| (prior.operational_decision_sha256 ?? null) !== (input.operationalDecisionFile ? fileHash(input.operationalDecisionFile) : null))
|
|
26
|
+
throw new AppError("fork_request_conflict", "Fork output already belongs to another request", 1, { output });
|
|
27
|
+
return { receipt, output };
|
|
28
|
+
}
|
|
29
|
+
prepareForkEngine(engineVersion, input.integrityChecksum);
|
|
30
|
+
const manifest = prepareEvalRunSnapshot(snapshot);
|
|
31
|
+
if (manifest.schema_id !== "dd-flow/eval-run-snapshot@5" || manifest.purpose !== "stage_entry" || typeof manifest.stage_entry !== "string" || typeof manifest.run_id !== "string") {
|
|
32
|
+
throw new AppError("fork_checkpoint_invalid", "Fork requires a sealed stage-entry snapshot", 2, { snapshot });
|
|
33
|
+
}
|
|
34
|
+
const operational = prepareSnapshotOperationalDecision({ snapshot, runId: manifest.run_id, projectRoot: path.join(output, "project"), now: new Date().toISOString(), ...(input.operationalDecisionFile ? { file: input.operationalDecisionFile } : {}) });
|
|
35
|
+
const intent = {
|
|
36
|
+
schema_id: "dd-flow/run-fork-intent@1",
|
|
37
|
+
request_id: requestId,
|
|
38
|
+
snapshot,
|
|
39
|
+
snapshot_sha256: fileHash(manifestFile),
|
|
40
|
+
engine_version: engineVersion,
|
|
41
|
+
integrity_checksum: input.integrityChecksum ?? null,
|
|
42
|
+
...(operational ? { operational_decision_sha256: crypto.createHash("sha256").update(operational.bytes).digest("hex") } : {}),
|
|
43
|
+
target_stage: manifest.stage_entry
|
|
44
|
+
};
|
|
45
|
+
assertForkOutput(output, intentFile, intent);
|
|
46
|
+
return { manifest, snapshot, output, intent, intentFile, receiptFile, requestId };
|
|
47
|
+
}
|
|
48
|
+
function assertForkOutput(output, intentFile, intent) {
|
|
49
|
+
if (fs.existsSync(output) && !fs.statSync(output).isDirectory())
|
|
50
|
+
throw new AppError("fork_output_not_empty", "Fork output must be a directory", 2, { output });
|
|
51
|
+
if (fs.existsSync(output) && fs.readdirSync(output).length > 0) {
|
|
52
|
+
if (!fs.existsSync(intentFile) || hash(readJson(intentFile).intent) !== hash(intent))
|
|
53
|
+
throw new AppError("fork_output_not_empty", "Fork output must be empty", 2, { output });
|
|
54
|
+
const guardName = path.basename(importGuardPath(path.join(output, "dd-flow-home")));
|
|
55
|
+
const unknown = fs.readdirSync(output).filter(name => ![".fork-intent.json", "project", "dd-flow-home", guardName].includes(name));
|
|
56
|
+
if (unknown.length)
|
|
57
|
+
throw new AppError("fork_retry_inspect_required", "Fork output contains data not owned by the interrupted fork", 1, { output, unknown });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function forkRunFromSnapshot(context, input, prepared = prepareRunFork(input)) {
|
|
61
|
+
if ("receipt" in prepared)
|
|
62
|
+
return prepared.receipt;
|
|
63
|
+
const { manifest, snapshot, output, intent, intentFile, receiptFile, requestId } = prepared;
|
|
64
|
+
if (input.operationalDecisionFile && fileHash(input.operationalDecisionFile) !== intent.operational_decision_sha256)
|
|
65
|
+
throw new AppError("operational_decision_changed", "Accepted fork decision changed after preparation", 2);
|
|
66
|
+
assertForkOutput(output, intentFile, intent);
|
|
67
|
+
const projectRoot = path.join(output, "project");
|
|
68
|
+
const home = path.join(output, "dd-flow-home");
|
|
69
|
+
const prearmedImportId = rearmCompletedSnapshotImport({ home, projectRoot,
|
|
70
|
+
workspaceRoot: restoredWorkspaceRoot(manifest, projectRoot, home), sourceManifestSha256: intent.snapshot_sha256 });
|
|
71
|
+
for (const name of ["project", "dd-flow-home"])
|
|
72
|
+
fs.rmSync(path.join(output, name), { recursive: true, force: true });
|
|
73
|
+
fs.mkdirSync(output, { recursive: true, mode: 0o700 });
|
|
74
|
+
writeJsonAtomic(intentFile, { schema_id: "dd-flow/run-fork-intent@1", intent });
|
|
75
|
+
fs.mkdirSync(projectRoot, { recursive: true, mode: 0o700 });
|
|
76
|
+
const targetContext = createContext({ ...context.env, DD_FLOW_HOME: home, DD_FLOW_ENGINE_MODE: "1" }, "initialize", prearmedImportId ?? undefined);
|
|
77
|
+
const restoreInput = { snapshot, projectRoot, ...(input.operationalDecisionFile ? { operationalDecisionFile: input.operationalDecisionFile } : {}) };
|
|
78
|
+
const restored = restoreEvalRunSnapshot(targetContext, restoreInput, prepareEvalRunRestore(targetContext, restoreInput, manifest), prearmedImportId ?? undefined);
|
|
79
|
+
const restoredContext = createContext({ ...context.env, DD_FLOW_HOME: home, DD_FLOW_ENGINE_MODE: "1" });
|
|
80
|
+
try {
|
|
81
|
+
const runHome = String(restored.run_home);
|
|
82
|
+
const previousBinding = readJson(path.join(runHome, "engine-binding.json"));
|
|
83
|
+
const lineage = {
|
|
84
|
+
schema_id: "dd-flow/run-fork-lineage@1",
|
|
85
|
+
request_id: requestId,
|
|
86
|
+
source_snapshot: snapshot,
|
|
87
|
+
source_snapshot_sha256: intent.snapshot_sha256,
|
|
88
|
+
source_run_id: manifest.run_id,
|
|
89
|
+
target_stage: manifest.stage_entry,
|
|
90
|
+
inherited_engine: previousBinding,
|
|
91
|
+
created_at: restoredContext.now()
|
|
92
|
+
};
|
|
93
|
+
writeJsonAtomic(path.join(runHome, "fork-lineage.json"), lineage);
|
|
94
|
+
// Install the immutable artifact belonging to the currently executing
|
|
95
|
+
// CLI into the new home. Its exact version/checksum were checked before
|
|
96
|
+
// any interrupted-copy cleanup; snapshots exclude installed engines.
|
|
97
|
+
installCurrentEngine(restoredContext, { forRouting: true });
|
|
98
|
+
const binding = await upgradeForkedRunEngine(restoredContext, {
|
|
99
|
+
projectRoot,
|
|
100
|
+
runId: String(restored.run_id),
|
|
101
|
+
engineVersion: intent.engine_version,
|
|
102
|
+
...(intent.integrity_checksum ? { integrityChecksum: intent.integrity_checksum } : {}),
|
|
103
|
+
reason: `Fork ${requestId} from sealed checkpoint ${intent.snapshot_sha256}.`
|
|
104
|
+
});
|
|
105
|
+
const receipt = {
|
|
106
|
+
ok: true,
|
|
107
|
+
schema_id: "dd-flow/run-fork@1",
|
|
108
|
+
status: "ready",
|
|
109
|
+
intent,
|
|
110
|
+
fork: {
|
|
111
|
+
project_root: projectRoot,
|
|
112
|
+
dd_flow_home: home,
|
|
113
|
+
run_id: restored.run_id,
|
|
114
|
+
run_home: runHome,
|
|
115
|
+
workspace_root: restored.workspace_root,
|
|
116
|
+
target_stage: manifest.stage_entry,
|
|
117
|
+
engine: binding.engine,
|
|
118
|
+
lineage_file: path.join(runHome, "fork-lineage.json")
|
|
119
|
+
},
|
|
120
|
+
next_command: `dd-flow stage start ${restored.run_id} --stage ${manifest.stage_entry} --project-root ${JSON.stringify(projectRoot)} --json`
|
|
121
|
+
};
|
|
122
|
+
writeJsonAtomic(receiptFile, receipt);
|
|
123
|
+
return receipt;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
restoredContext.db.close?.();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function readJson(file) {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
throw new AppError("fork_checkpoint_invalid", "Fork metadata is not valid JSON", 2, { file, cause: String(error) });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function writeJsonAtomic(file, value) {
|
|
138
|
+
const temporary = `${file}.tmp-${process.pid}-${crypto.randomUUID()}`;
|
|
139
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
140
|
+
fs.renameSync(temporary, file);
|
|
141
|
+
}
|
|
142
|
+
function hash(value) { return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); }
|
|
143
|
+
function fileHash(file) { return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); }
|
|
144
|
+
function requireText(value, name) { const text = value.trim(); if (!text)
|
|
145
|
+
throw new AppError("usage", `Missing required value: ${name}`, 2); return text; }
|
|
146
|
+
function requireId(value, name) { const text = requireText(value, name); if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(text))
|
|
147
|
+
throw new AppError("usage", `Invalid ${name}`, 2); return text; }
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { managedProcessBinding, managedProcessRuntimeHome, managedProcessStatus, managedProcessObservation, processTreeIsAlive } from "./managed-processes.js";
|
|
3
|
+
import { reconcileStoredFlowRun } from "./runs.js";
|
|
4
|
+
import { recoveryGuard } from "./run-recovery.js";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
function physicalInventory() {
|
|
7
|
+
const reply = spawnSync('ps', ['-axo', 'pid=,pgid=,stat='], { encoding: 'utf8', timeout: 5000 });
|
|
8
|
+
if (reply.status !== 0)
|
|
9
|
+
return null;
|
|
10
|
+
return reply.stdout.trim().split('\n').map(line => line.trim().split(/\s+/)).filter(parts => parts.length === 3).map(parts => ({ pid: Number(parts[0]), group: Number(parts[1]), zombie: parts[2].startsWith('Z') }));
|
|
11
|
+
}
|
|
12
|
+
const terminalRun = (status) => ["done", "failed", "cancelled", "discarded"].includes(status);
|
|
13
|
+
const waiting = new Set(["waiting_for_user", "waiting_for_context", "waiting_for_capacity", "waiting_for_children", "waiting_for_merge", "stop_target_reached", "paused", "control_requested"]);
|
|
14
|
+
const table = (context, name) => Boolean(context.db.get("SELECT 1 FROM sqlite_schema WHERE name = ?", [name]));
|
|
15
|
+
const latest = (values) => values.filter((value) => typeof value === "string" && Number.isFinite(Date.parse(value))).sort((a, b) => Date.parse(b) - Date.parse(a))[0] ?? null;
|
|
16
|
+
/** Evidence only: lease expiry and quiet providers never prove an exit. */
|
|
17
|
+
export function assessRunHealth(input) {
|
|
18
|
+
const phase = input.phase ?? null;
|
|
19
|
+
let state = "unknown", reason = "owner_identity_missing";
|
|
20
|
+
if (terminalRun(input.status)) {
|
|
21
|
+
state = "settled";
|
|
22
|
+
reason = "saved_terminal_result";
|
|
23
|
+
}
|
|
24
|
+
else if (input.childrenAlive && input.owner === "absent") {
|
|
25
|
+
state = "owner_lost";
|
|
26
|
+
reason = "native_children_still_active";
|
|
27
|
+
}
|
|
28
|
+
else if (phase === 'recovery_required' || phase === 'capture_failed') {
|
|
29
|
+
state = input.owner === 'absent' ? 'owner_lost' : 'unknown';
|
|
30
|
+
reason = phase;
|
|
31
|
+
}
|
|
32
|
+
else if (phase && waiting.has(phase) || ["waiting_for_user", "blocked"].includes(input.status)) {
|
|
33
|
+
state = "waiting";
|
|
34
|
+
reason = phase ?? input.status;
|
|
35
|
+
}
|
|
36
|
+
else if (input.owner === "absent") {
|
|
37
|
+
state = "owner_lost";
|
|
38
|
+
reason = "physical_owner_retired";
|
|
39
|
+
}
|
|
40
|
+
else if (input.owner === "alive") {
|
|
41
|
+
const age = input.lastProgress ? Date.parse(input.now) - Date.parse(input.lastProgress) : NaN;
|
|
42
|
+
state = Number.isFinite(age) && age > input.noProgressMs ? "no_progress" : "active";
|
|
43
|
+
reason = state === "no_progress" ? "progress_overdue" : "physical_owner_observed";
|
|
44
|
+
}
|
|
45
|
+
else if (input.status === "paused") {
|
|
46
|
+
state = "waiting";
|
|
47
|
+
reason = "saved_pause";
|
|
48
|
+
}
|
|
49
|
+
return { schema_id: "dd-flow/run-health@1", state, reason, phase, owner_state: input.owner, last_progress_at: input.lastProgress ?? null, last_heartbeat_at: input.heartbeat ?? null, observed_at: input.now, controller_id: input.controllerId ?? null, generation: input.generation ?? null, no_progress_ms: input.noProgressMs };
|
|
50
|
+
}
|
|
51
|
+
/** No dispatch, cancellation, process signals or provider calls. All changes
|
|
52
|
+
* are fenced by the retained RUN revision, controller and process binding. */
|
|
53
|
+
export function reconcileFlowRunHealth(context, input = {}) {
|
|
54
|
+
const noProgressMs = input.noProgressMs ?? 30 * 60_000;
|
|
55
|
+
if (!Number.isSafeInteger(noProgressMs) || noProgressMs < 1_000)
|
|
56
|
+
throw new AppError("usage", "no-progress-ms must be an integer of at least 1000", 2);
|
|
57
|
+
const runs = context.db.all(`SELECT * FROM runs${input.runId ? " WHERE (id = ? OR short_id = ?)" : ""}`, input.runId ? [input.runId, input.runId] : []).filter(run => !input.projectRoot || run.project_root === input.projectRoot);
|
|
58
|
+
if (input.runId && runs.length !== 1)
|
|
59
|
+
throw new AppError("not_found", "RUN scope is missing or ambiguous", 1);
|
|
60
|
+
const processes = physicalInventory();
|
|
61
|
+
const alive = (record) => {
|
|
62
|
+
if (!record.pid)
|
|
63
|
+
return !['stopped', 'failed'].includes(record.state);
|
|
64
|
+
const physical = processes?.find(item => item.pid === record.pid);
|
|
65
|
+
if (processes && (!physical || physical.zombie)) {
|
|
66
|
+
const metadata = JSON.parse(record.metadata_json);
|
|
67
|
+
// A retired group leader may still own physical children. Merely ending
|
|
68
|
+
// the controller does not settle those children or remote provider work.
|
|
69
|
+
return Boolean(metadata.process_group_id && processes.some(item => item.group === metadata.process_group_id && !item.zombie));
|
|
70
|
+
}
|
|
71
|
+
return !record.pid_started_at || processTreeIsAlive(record);
|
|
72
|
+
};
|
|
73
|
+
return { ok: true, results: runs.map(run => {
|
|
74
|
+
const index = JSON.parse(run.index_json);
|
|
75
|
+
const controllers = table(context, "run_controllers") ? context.db.all("SELECT * FROM run_controllers WHERE project_id = ? AND run_id = ? ORDER BY generation DESC, created_at DESC", [run.project_id, run.id]) : [];
|
|
76
|
+
const controller = controllers[0];
|
|
77
|
+
const manifest = controller ? JSON.parse(controller.manifest_json) : null;
|
|
78
|
+
const resourceContext = manifest?.resource_home ? { ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: manifest.resource_home } } : context;
|
|
79
|
+
let records = [], inventoryComplete = false;
|
|
80
|
+
try {
|
|
81
|
+
records = managedProcessStatus(resourceContext).filter(record => record.project_id === run.project_id && record.run_id === run.id && managedProcessRuntimeHome(record, context) === context.ddFlowHome);
|
|
82
|
+
inventoryComplete = true;
|
|
83
|
+
}
|
|
84
|
+
catch { /* Missing registry is uncertainty, never retirement proof. */ }
|
|
85
|
+
const physical = controller && records.find(record => record.id === controller.process_id && record.lease_token === controller.process_lease_token && record.owner_id === controller.controller_id && record.kind === "run-controller");
|
|
86
|
+
const owner = physical?.pid && physical.pid_started_at ? processes && !processes.some(item => item.pid === physical.pid && !item.zombie) && !alive(physical) ? 'absent' : managedProcessObservation(physical) : "unknown";
|
|
87
|
+
const retireController = Boolean(controller && owner === 'absent' && !['completed', 'cancelled', 'superseded', 'stop_target_reached', 'recovery_required', 'capture_failed', 'waiting_for_user', 'waiting_for_context', 'control_requested', 'paused'].includes(controller.status));
|
|
88
|
+
const phase = retireController ? 'recovery_required' : controller?.status ?? null;
|
|
89
|
+
const childrenAlive = records.some(record => record.id !== physical?.id && alive(record)) || Boolean(physical && owner === 'absent' && alive(physical));
|
|
90
|
+
const progressEvent = controller && table(context, "run_controller_events") ? context.db.get("SELECT MAX(created_at) AS at FROM run_controller_events WHERE controller_id = ? AND event_type NOT IN ('health_reconciled','heartbeat')", [controller.controller_id])?.at : null;
|
|
91
|
+
const workProgress = table(context, "works") ? context.db.get("SELECT MAX(at) AS at FROM (SELECT created_at AS at FROM works WHERE project_id=? AND run_id=? UNION ALL SELECT started_at FROM works WHERE project_id=? AND run_id=? UNION ALL SELECT completed_at FROM works WHERE project_id=? AND run_id=?)", [run.project_id, run.id, run.project_id, run.id, run.project_id, run.id])?.at : null;
|
|
92
|
+
let hookProgress = null;
|
|
93
|
+
if (table(context, 'hook_events') && table(context, 'sessions'))
|
|
94
|
+
try {
|
|
95
|
+
hookProgress = context.db.get("SELECT MAX(h.created_at) AS at FROM hook_events h JOIN sessions s ON s.session_id=h.session_id AND s.project_id=h.project_id WHERE s.project_id=? AND s.run_id=? AND h.event_name IN ('PostToolUse','UserPromptSubmit','SubagentStop','Stop','SessionStart')", [run.project_id, run.id])?.at ?? null;
|
|
96
|
+
}
|
|
97
|
+
catch { /* Legacy hooks have no certified Run membership. */ }
|
|
98
|
+
const lastProgress = latest([run.created_at, progressEvent, workProgress, hookProgress, ...index.stage_runs.flatMap(stage => [stage.started_at, stage.completed_at])]);
|
|
99
|
+
const policy = input.noProgressMs ?? (phase === 'starting' ? 120_000 : phase === 'capturing' ? 300_000 : 30 * 60_000);
|
|
100
|
+
const health = assessRunHealth({ status: run.status, phase, owner, childrenAlive, lastProgress, heartbeat: physical?.updated_at ?? null, now: context.now(), noProgressMs: policy, controllerId: controller?.controller_id ?? null, generation: controller?.generation ?? null });
|
|
101
|
+
if (terminalRun(run.status) && (childrenAlive || owner === "alive")) {
|
|
102
|
+
health.state = "unknown";
|
|
103
|
+
health.reason = "terminal_result_has_live_runtime";
|
|
104
|
+
}
|
|
105
|
+
const sessionState = controller ? JSON.parse(controller.state_json) : null;
|
|
106
|
+
const nativeSettled = Boolean(sessionState?.sessions.length && sessionState.sessions.every(session => session.stopped === true));
|
|
107
|
+
const outcomeUnknown = controller && table(context, "run_controller_operations") && context.db.get("SELECT 1 FROM run_controller_operations WHERE controller_id = ? AND generation = ? AND status IN ('accepted','outcome_unknown') LIMIT 1", [controller.controller_id, controller.generation]);
|
|
108
|
+
let transition;
|
|
109
|
+
const guard = table(context, 'run_recovery_guards') ? recoveryGuard(context, run.project_id, run.id) : null;
|
|
110
|
+
const currentGeneration = !guard || !controller || controller.generation === guard.generation;
|
|
111
|
+
if (!terminalRun(run.status) && currentGeneration) {
|
|
112
|
+
if (controller && ["recovery_required", "capture_failed"].includes(controller.status))
|
|
113
|
+
transition = "paused";
|
|
114
|
+
else if (controller?.status === "stop_target_reached" && owner !== "alive" && nativeSettled && inventoryComplete && !childrenAlive)
|
|
115
|
+
transition = "paused";
|
|
116
|
+
else if (health.state === "owner_lost" && owner === "absent") {
|
|
117
|
+
transition = controller?.last_error_json && nativeSettled && inventoryComplete && !childrenAlive && !outcomeUnknown && !guard ? "failed" : "paused";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const nextStatus = transition ?? run.status;
|
|
121
|
+
if (transition === 'failed') {
|
|
122
|
+
health.state = 'settled';
|
|
123
|
+
health.reason = 'saved_terminal_result';
|
|
124
|
+
}
|
|
125
|
+
const unchanged = index.health && JSON.stringify({ ...index.health, observed_at: null }) === JSON.stringify({ ...health, observed_at: null }) && nextStatus === run.status;
|
|
126
|
+
let changed = false;
|
|
127
|
+
if (input.apply && !unchanged) {
|
|
128
|
+
// Verify the same physical inventory immediately before publishing. A
|
|
129
|
+
// replacement/renewal invalidates this observation, even with the same PID.
|
|
130
|
+
const binding = (items) => JSON.stringify(items.map(item => ({ ...managedProcessBinding(item, manifest?.resource_home ?? context.ddFlowHome), state: item.state, updated_at: item.updated_at, lease_expires_at: item.lease_expires_at })));
|
|
131
|
+
if (inventoryComplete) {
|
|
132
|
+
const fresh = managedProcessStatus(resourceContext).filter(record => record.project_id === run.project_id && record.run_id === run.id && managedProcessRuntimeHome(record, context) === context.ddFlowHome);
|
|
133
|
+
if (binding(fresh) !== binding(records) || physical && owner === "absent" && managedProcessObservation(physical) !== 'absent')
|
|
134
|
+
throw new AppError("run_health_changed", "Runtime ownership changed during reconciliation", 1);
|
|
135
|
+
}
|
|
136
|
+
context.db.writeTransaction(() => {
|
|
137
|
+
const current = controller && context.db.get("SELECT * FROM run_controllers WHERE controller_id = ?", [controller.controller_id]);
|
|
138
|
+
if (controller && JSON.stringify(current) !== JSON.stringify(controller))
|
|
139
|
+
throw new AppError("run_health_changed", "Controller changed during reconciliation", 1);
|
|
140
|
+
reconcileStoredFlowRun(context, run, health, transition, inventoryComplete && owner !== "alive" && !childrenAlive && nativeSettled);
|
|
141
|
+
if (controller && retireController) {
|
|
142
|
+
context.db.run("UPDATE run_controllers SET status = 'recovery_required', updated_at = ? WHERE controller_id = ? AND owner_token = ? AND generation = ?", [context.now(), controller.controller_id, controller.owner_token, controller.generation]);
|
|
143
|
+
}
|
|
144
|
+
changed = true;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return { project_id: run.project_id, run_id: run.id, previous_status: run.status, status: nextStatus, health, changed, would_change: !unchanged };
|
|
148
|
+
}) };
|
|
149
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ToolObservations, replayToolJournal } from "../harness-runtime/lib/tool-observations.mjs";
|
|
4
|
+
import { harnessKey } from "./harness-config.js";
|
|
5
|
+
/** Evidence has ancestry, not executable ownership. Never open a source home. */
|
|
6
|
+
export function runObservations(context, projectId, runId) {
|
|
7
|
+
const home = fs.realpathSync(context.ddFlowHome), sources = new Map(), reasons = [];
|
|
8
|
+
const sessions = context.db.all("SELECT session_id, provider_session_id, harness, transcript_path FROM sessions WHERE project_id = ? AND run_id = ?", [projectId, runId]);
|
|
9
|
+
const inside = (root, file) => { const p = path.relative(root, file); return p !== "" && p !== ".." && !p.startsWith(`..${path.sep}`) && !path.isAbsolute(p); };
|
|
10
|
+
const add = (id, nativeId, journal, origin, provenance, harness) => {
|
|
11
|
+
if (sources.has(id))
|
|
12
|
+
return;
|
|
13
|
+
const candidates = sessions.filter(s => (s.provider_session_id ?? s.session_id) === nativeId && (!harness || s.harness === harness));
|
|
14
|
+
if (candidates.length > 1) {
|
|
15
|
+
reasons.push("journal_session_ambiguous");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const owner = candidates[0];
|
|
19
|
+
if (!owner)
|
|
20
|
+
return;
|
|
21
|
+
const source = { id, provenance, session: { harness_id: owner.harness, session_id: nativeId }, path: null, status: "unavailable" };
|
|
22
|
+
sources.set(id, source);
|
|
23
|
+
if (typeof journal !== "string" || !path.isAbsolute(journal) || !inside(origin, journal)) {
|
|
24
|
+
source.reason = "journal_outside_owned_home";
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
source.path = path.relative(origin, journal);
|
|
28
|
+
const file = path.resolve(home, source.path);
|
|
29
|
+
try {
|
|
30
|
+
if (!inside(home, fs.realpathSync(file))) {
|
|
31
|
+
source.reason = "journal_outside_owned_home";
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!fs.statSync(file).isFile()) {
|
|
35
|
+
source.reason = "journal_not_file";
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
source.status = "available";
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
source.reason = "journal_missing";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const controllers = (rows, origin, provenance) => {
|
|
45
|
+
for (const row of rows) {
|
|
46
|
+
if (row.project_id !== projectId || row.run_id !== runId)
|
|
47
|
+
continue;
|
|
48
|
+
try {
|
|
49
|
+
for (const session of JSON.parse(row.state_json).sessions ?? [])
|
|
50
|
+
add(`${row.controller_id}:${session.id}`, session.id, session.journal, origin, provenance, session.profile?.harness ? harnessKey(session.profile.harness) : undefined);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
reasons.push("controller_evidence_invalid");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
if (context.db.all("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = 'run_controllers'").length) {
|
|
58
|
+
controllers(context.db.all("SELECT controller_id, project_id, run_id, state_json FROM run_controllers WHERE project_id = ? AND run_id = ?", [projectId, runId]), home, "current");
|
|
59
|
+
}
|
|
60
|
+
const imports = path.join(home, "snapshot-imports");
|
|
61
|
+
if (fs.existsSync(imports))
|
|
62
|
+
for (const file of fs.readdirSync(imports).filter(name => name.endsWith(".json")).sort()) {
|
|
63
|
+
try {
|
|
64
|
+
const location = path.join(imports, file);
|
|
65
|
+
if (!inside(home, fs.realpathSync(location))) {
|
|
66
|
+
reasons.push("lineage_outside_owned_home");
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const archive = JSON.parse(fs.readFileSync(location, "utf8"));
|
|
70
|
+
if (archive.schema_id !== "dd-flow/snapshot-runtime-lineage@1" || archive.run_id !== runId)
|
|
71
|
+
continue;
|
|
72
|
+
if (typeof archive.source_runtime !== "string" || !path.isAbsolute(archive.source_runtime)) {
|
|
73
|
+
reasons.push("lineage_source_invalid");
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
controllers(archive.run_tables?.run_controllers ?? [], archive.source_runtime, "inherited");
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
reasons.push("lineage_evidence_invalid");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const reducer = new ToolObservations();
|
|
83
|
+
for (const reason of reasons)
|
|
84
|
+
reducer.gaps.add(reason);
|
|
85
|
+
const expectedCodexIds = sessions.filter(session => session.harness === "codex-desktop").map(session => session.provider_session_id ?? session.session_id);
|
|
86
|
+
const replay = (source, sessionId) => {
|
|
87
|
+
const file = path.join(home, source.path);
|
|
88
|
+
let fd = null;
|
|
89
|
+
try {
|
|
90
|
+
const actual = fs.realpathSync(file);
|
|
91
|
+
if (!inside(home, actual))
|
|
92
|
+
throw new Error("journal escaped owned home");
|
|
93
|
+
fd = fs.openSync(actual, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
94
|
+
if (!fs.fstatSync(fd).isFile())
|
|
95
|
+
throw new Error("journal is not a regular file");
|
|
96
|
+
replayToolJournal(file, { harness: source.session.harness_id, rootSessionId: sessionId, text: fs.readFileSync(fd, "utf8"), ...(source.session.harness_id === "codex-desktop" ? { expectedSessionIds: expectedCodexIds } : {}) }, reducer);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
source.status = "unavailable";
|
|
100
|
+
source.reason = "journal_unavailable_during_read";
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
if (fd !== null)
|
|
104
|
+
fs.closeSync(fd);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
for (const source of sources.values()) {
|
|
108
|
+
if (source.status !== "available")
|
|
109
|
+
continue;
|
|
110
|
+
replay(source, source.session.session_id);
|
|
111
|
+
}
|
|
112
|
+
const covered = () => new Set(reducer.summary().sessions.map(item => JSON.stringify(item.session)));
|
|
113
|
+
// A managed root journal can contain all of its children's native events.
|
|
114
|
+
// Select transcript fallback only after replay reveals an actual gap.
|
|
115
|
+
for (const session of sessions) {
|
|
116
|
+
const nativeId = session.provider_session_id ?? session.session_id;
|
|
117
|
+
const sessionSummary = reducer.summary(nativeId);
|
|
118
|
+
if (!session.transcript_path || covered().has(JSON.stringify({ harness_id: session.harness, session_id: nativeId }))
|
|
119
|
+
&& sessionSummary.completeness === "complete" && sessionSummary.outcome_completeness === "complete")
|
|
120
|
+
continue;
|
|
121
|
+
const id = `transcript:${session.harness}:${nativeId}`;
|
|
122
|
+
add(id, nativeId, session.transcript_path, home, "current", session.harness);
|
|
123
|
+
const source = sources.get(id);
|
|
124
|
+
if (source?.status === "available")
|
|
125
|
+
replay(source, nativeId);
|
|
126
|
+
}
|
|
127
|
+
for (const source of sources.values())
|
|
128
|
+
if (source.status !== "available"
|
|
129
|
+
&& !covered().has(JSON.stringify(source.session)))
|
|
130
|
+
reducer.gaps.add(source.reason ?? "journal_unavailable");
|
|
131
|
+
const expected = [...new Map(sessions.map(s => { const session = { harness_id: s.harness, session_id: s.provider_session_id ?? s.session_id }; return [JSON.stringify(session), session]; })).values()];
|
|
132
|
+
const expectedIds = new Set(expected.map(s => JSON.stringify(s)));
|
|
133
|
+
for (const [key, event] of reducer.calls)
|
|
134
|
+
if (!expectedIds.has(JSON.stringify(event.session))) {
|
|
135
|
+
reducer.calls.delete(key);
|
|
136
|
+
}
|
|
137
|
+
for (const [key, event] of reducer.turns)
|
|
138
|
+
if (!expectedIds.has(JSON.stringify(event.session))) {
|
|
139
|
+
reducer.turns.delete(key);
|
|
140
|
+
}
|
|
141
|
+
for (const [key, event] of reducer.sources)
|
|
142
|
+
if (!expectedIds.has(JSON.stringify(event.session)))
|
|
143
|
+
reducer.sources.delete(key);
|
|
144
|
+
const summary = reducer.summary();
|
|
145
|
+
const observed = new Set(summary.sessions.map(s => JSON.stringify(s.session)));
|
|
146
|
+
const missing = expected.filter(s => !observed.has(JSON.stringify(s)));
|
|
147
|
+
const completeness = missing.length || summary.completeness !== "complete" || summary.outcome_completeness !== "complete" ? "partial" : "complete";
|
|
148
|
+
return { schema_id: "dd-flow/run-observations@1", home, sources: [...sources.values()], tools: { ...summary,
|
|
149
|
+
completeness, status: observed.size === 0 ? "unavailable" : completeness === "complete" ? "measured" : "partial",
|
|
150
|
+
observed_sessions: expected.length - missing.length, expected_sessions: expected.length, unavailable_sessions: missing.map(s => s.session_id), missing_sessions: missing,
|
|
151
|
+
reasons: [...new Set([...summary.reasons, ...(missing.length ? ["sessions_without_tool_observations"] : [])])] } };
|
|
152
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { reconcileSessionCoverageRows } from "../domain/session-coverage.js";
|
|
4
3
|
import { publicSessionIdentity } from "./session-identity.js";
|
|
4
|
+
import { publishRunProjections } from "./runs.js";
|
|
5
5
|
export function refreshRunSessionProjection(context, projectId, runId) {
|
|
6
|
+
// Review children can finish concurrently in separate processes. Reserve the
|
|
7
|
+
// writer before reading the RUN index so a sibling cannot invalidate it.
|
|
8
|
+
context.db.writeTransaction(() => refreshRunSessionProjectionLocked(context, projectId, runId), "run-projection.refresh");
|
|
9
|
+
}
|
|
10
|
+
function refreshRunSessionProjectionLocked(context, projectId, runId) {
|
|
6
11
|
const run = context.db.get("SELECT id, flow_kind, status, project_root, subject_type, subject_id, runtime_path, run_index_path, index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
|
|
7
12
|
if (!run)
|
|
8
13
|
return;
|
|
@@ -27,7 +32,7 @@ export function refreshRunSessionProjection(context, projectId, runId) {
|
|
|
27
32
|
missing: coverage.missing_unit_ids,
|
|
28
33
|
diagnostics: coverage.diagnostics
|
|
29
34
|
};
|
|
30
|
-
const plan = run.subject_type === "protocol" ? planProjection(context, projectId, run.subject_id, run.project_root) : {};
|
|
35
|
+
const plan = run.subject_type === "protocol" ? planProjection(context, projectId, run.subject_id, run.project_root, index) : {};
|
|
31
36
|
const workers = workerProjection(context, projectId, runId);
|
|
32
37
|
const vnext = run.flow_kind === "vnext_specify" || run.flow_kind === "vnext_protocolize";
|
|
33
38
|
const hadLegacyCoverage = vnext && ("session_coverage" in index || "usage_coverage" in index);
|
|
@@ -42,8 +47,8 @@ export function refreshRunSessionProjection(context, projectId, runId) {
|
|
|
42
47
|
&& JSON.stringify(index.works ?? []) === JSON.stringify(works)
|
|
43
48
|
&& index.root_work_id === rootWorkId
|
|
44
49
|
&& (vnext || JSON.stringify(index.session_coverage) === JSON.stringify(sessionCoverage))
|
|
45
|
-
&& JSON.stringify(index.plan_ref) === JSON.stringify(plan.plan_ref)
|
|
46
|
-
&& JSON.stringify(index.plan_progress) === JSON.stringify(plan.plan_progress)
|
|
50
|
+
&& (!plan.plan_ref || JSON.stringify(index.plan_ref) === JSON.stringify(plan.plan_ref))
|
|
51
|
+
&& (!plan.plan_progress || JSON.stringify(index.plan_progress) === JSON.stringify(plan.plan_progress))
|
|
47
52
|
&& (vnext || JSON.stringify(index.workers ?? {}) === JSON.stringify(workers))
|
|
48
53
|
&& !hadLegacyCoverage)
|
|
49
54
|
return;
|
|
@@ -59,44 +64,23 @@ export function refreshRunSessionProjection(context, projectId, runId) {
|
|
|
59
64
|
if (!vnext)
|
|
60
65
|
index.workers = workers;
|
|
61
66
|
index.updated_at = context.now();
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
: 1;
|
|
68
|
-
index.runtime_revision = runtimeRevision;
|
|
69
|
-
const authoritative = { ...(runtime ?? {}), ...index, schema_id: String(index.schema_id ?? runtime?.schema_id ?? "dd-flow/flow-run@3") };
|
|
70
|
-
authoritative.sessions = sessions;
|
|
71
|
-
authoritative.works = works;
|
|
72
|
-
authoritative.root_work_id = rootWorkId;
|
|
73
|
-
if (vnext) {
|
|
74
|
-
delete authoritative.session_coverage;
|
|
75
|
-
delete authoritative.usage_coverage;
|
|
76
|
-
delete authoritative.workers;
|
|
77
|
-
delete authoritative.current_stage;
|
|
78
|
-
delete authoritative.attempts;
|
|
79
|
-
}
|
|
80
|
-
else
|
|
81
|
-
authoritative.session_coverage = sessionCoverage;
|
|
82
|
-
if (plan.plan_ref)
|
|
83
|
-
authoritative.plan_ref = plan.plan_ref;
|
|
84
|
-
if (plan.plan_progress)
|
|
85
|
-
authoritative.plan_progress = plan.plan_progress;
|
|
86
|
-
if (!vnext)
|
|
87
|
-
authoritative.workers = workers;
|
|
88
|
-
authoritative.updated_at = index.updated_at;
|
|
89
|
-
authoritative.runtime_revision = runtimeRevision;
|
|
90
|
-
if (run.status !== "discarded" || fs.existsSync(run.runtime_path))
|
|
91
|
-
writeJson(run.runtime_path, authoritative);
|
|
92
|
-
context.db.run("UPDATE runs SET index_json = ?, updated_at = ? WHERE project_id = ? AND id = ?", [JSON.stringify(index), index.updated_at, projectId, runId]);
|
|
67
|
+
index.runtime_revision = typeof index.runtime_revision === "number" ? index.runtime_revision + 1 : 1;
|
|
68
|
+
const updated = context.db.run("UPDATE runs SET index_json = ?, updated_at = ? WHERE project_id = ? AND id = ? AND index_json = ?", [JSON.stringify(index), index.updated_at, projectId, runId, run.index_json]);
|
|
69
|
+
if (updated.changes !== 1)
|
|
70
|
+
throw new Error("RUN projection changed during preparation");
|
|
71
|
+
context.db.afterCommit(() => publishRunProjections(context, projectId, runId));
|
|
93
72
|
}
|
|
94
|
-
function planProjection(context, projectId, protocolId, projectRoot) {
|
|
95
|
-
const binding = context.db.get("SELECT plan_path, plan_revision, plan_sha256 FROM plan_bindings WHERE project_id = ? AND protocol_id = ?", [projectId, protocolId]);
|
|
73
|
+
function planProjection(context, projectId, protocolId, projectRoot, index) {
|
|
74
|
+
const binding = context.db.get("SELECT plan_id, plan_path, plan_revision, plan_sha256 FROM plan_bindings WHERE project_id = ? AND protocol_id = ?", [projectId, protocolId]);
|
|
96
75
|
if (!binding)
|
|
97
76
|
return {};
|
|
98
77
|
const planPath = path.relative(projectRoot, binding.plan_path).split(path.sep).join("/");
|
|
99
|
-
const
|
|
78
|
+
const priorRef = index.plan_ref && typeof index.plan_ref === "object" && !Array.isArray(index.plan_ref) ? index.plan_ref : {};
|
|
79
|
+
const planId = binding.plan_id ?? (typeof priorRef.plan_id === "string" ? priorRef.plan_id : null);
|
|
80
|
+
// A pre-migration binding without a RUN plan_ref needs canonical rebind before
|
|
81
|
+
// this projection can be updated; never invent a plan identifier.
|
|
82
|
+
if (!planId)
|
|
83
|
+
return {};
|
|
100
84
|
const progress = Object.fromEntries(context.db.all("SELECT item_id, status, summary, evidence_json FROM plan_progress WHERE project_id = ? AND protocol_id = ? ORDER BY item_id", [projectId, protocolId]).map((row) => [row.item_id, {
|
|
101
85
|
status: row.status,
|
|
102
86
|
...(row.summary ? { summary: row.summary } : {}),
|
|
@@ -112,18 +96,6 @@ function planProjection(context, projectId, protocolId, projectRoot) {
|
|
|
112
96
|
plan_progress: progress
|
|
113
97
|
};
|
|
114
98
|
}
|
|
115
|
-
function readPlanId(planPath) {
|
|
116
|
-
try {
|
|
117
|
-
const value = JSON.parse(fs.readFileSync(planPath, "utf8"));
|
|
118
|
-
if (value && typeof value === "object" && !Array.isArray(value) && typeof value.plan_id === "string") {
|
|
119
|
-
return value.plan_id;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// The canonical validator owns malformed-plan errors; projection stays bounded.
|
|
124
|
-
}
|
|
125
|
-
return "unknown-plan";
|
|
126
|
-
}
|
|
127
99
|
function workerProjection(context, projectId, runId) {
|
|
128
100
|
return Object.fromEntries(context.db.all(`SELECT j.job_id, j.plan_item_id, j.status, j.worker_session_id, s.harness, s.provider_session_id
|
|
129
101
|
FROM flow_jobs j LEFT JOIN sessions s ON s.project_id = j.project_id AND s.session_id = j.worker_session_id
|
|
@@ -182,17 +154,3 @@ function parseCoverageUnits(value) {
|
|
|
182
154
|
return [];
|
|
183
155
|
}
|
|
184
156
|
}
|
|
185
|
-
function readJson(file) {
|
|
186
|
-
try {
|
|
187
|
-
return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : undefined;
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
return undefined;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function writeJson(file, value) {
|
|
194
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
195
|
-
const temporary = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
196
|
-
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`);
|
|
197
|
-
fs.renameSync(temporary, file);
|
|
198
|
-
}
|