@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.151
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 +907 -0
- package/README.md +139 -10
- package/dist/build-info.json +8 -8
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +386 -0
- package/dist/cli/command-metadata.js +232 -0
- package/dist/cli/command-option-descriptions.js +192 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -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 +396 -19
- package/dist/cli/hook-ingress.js +102 -0
- package/dist/cli/input-preparation.js +165 -0
- package/dist/cli/memory-flow-admission.js +50 -0
- package/dist/cli/memory-init-invocation.js +85 -0
- package/dist/cli/memory-init.js +249 -0
- package/dist/cli/memory-storage-admission.js +54 -0
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +90 -0
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +72 -0
- package/dist/cli/run-cli.js +1734 -550
- 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 +150 -0
- package/dist/cli.js +89 -2
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -0
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/domain/stage-handler-identities.js +28 -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-error-boundary.mjs +79 -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/agy-native-inspection.mjs +216 -0
- package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
- package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +67 -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 +2 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1092 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +627 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1200 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +166 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +140 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -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 +698 -0
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/model-observations.mjs +113 -0
- package/dist/harness-runtime/lib/native-children.d.mts +34 -0
- package/dist/harness-runtime/lib/native-children.mjs +265 -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 +95 -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 +23 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +242 -0
- package/dist/harness-runtime/lib/runner-events.mjs +202 -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 +53 -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-decision.schema.json +9 -0
- 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/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +211 -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 +29 -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/mb-upgrade-stage-result.schema.json +863 -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 +56 -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 +12 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
- package/dist/schemas/release-impact.schema.json +3 -3
- 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/stage-start-response.schema.json +4 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/accepted-intent.js +139 -0
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +54 -15
- package/dist/services/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +277 -0
- package/dist/services/check-runner-proof.js +305 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +26 -63
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +1401 -119
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +16 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-child-capacity.js +71 -0
- package/dist/services/controller-child-observer.js +121 -0
- package/dist/services/controller-execution.js +94 -0
- package/dist/services/controller-fanout.js +226 -0
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +49 -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 +107 -0
- package/dist/services/descriptor-run-start.js +475 -0
- package/dist/services/descriptor-stage-coordinator.js +250 -0
- package/dist/services/descriptor-stage-settlement.js +223 -0
- package/dist/services/descriptor-stage-start.js +175 -0
- package/dist/services/diagnostic-evidence.js +73 -0
- package/dist/services/diagnostic-report-publication.js +72 -0
- package/dist/services/diagnostic-report.js +67 -0
- package/dist/services/diagnostic-run-bundle.js +74 -0
- package/dist/services/diagnostic-snapshot.js +159 -0
- package/dist/services/diagnostic-stage-handlers.js +108 -0
- package/dist/services/diagnostic-stage-results.js +132 -0
- package/dist/services/diagnostic-start.js +84 -0
- package/dist/services/diagnostic-work-batch.js +34 -0
- package/dist/services/diagnostic-work-collection.js +97 -0
- package/dist/services/diagnostic-work-registration.js +101 -0
- package/dist/services/diagnostic-work-results.js +93 -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 +354 -163
- package/dist/services/eval-snapshots.js +1316 -150
- package/dist/services/execution-bundles.js +275 -0
- package/dist/services/execution-policy.js +185 -0
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +310 -0
- package/dist/services/flow-catalog.js +30 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +387 -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/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +791 -326
- package/dist/services/hybrid-contracts.js +474 -0
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +226 -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 +2148 -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 +449 -69
- package/dist/services/memory-accepted-composition.js +78 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +328 -0
- package/dist/services/memory-compose-handler.js +400 -0
- package/dist/services/memory-composition-acceptance.js +375 -0
- package/dist/services/memory-composition-graph.js +165 -0
- package/dist/services/memory-composition-registration.js +161 -0
- package/dist/services/memory-composition-work-custody.js +156 -0
- package/dist/services/memory-composition-work-results.js +108 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +37 -0
- package/dist/services/memory-corrected-review-findings.js +108 -0
- package/dist/services/memory-corrected-review-inputs.js +215 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +178 -0
- package/dist/services/memory-corrected-review-registration.js +192 -0
- package/dist/services/memory-correction-check-receipt.js +235 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +457 -0
- package/dist/services/memory-finish-handler.js +229 -0
- package/dist/services/memory-finish-integration.js +450 -0
- package/dist/services/memory-finish-readiness.js +169 -0
- package/dist/services/memory-git-integration.js +220 -0
- package/dist/services/memory-historical-corrected-review.js +212 -0
- package/dist/services/memory-historical-original-review.js +143 -0
- package/dist/services/memory-historical-repair-registration.js +105 -0
- package/dist/services/memory-historical-second-review.js +147 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +131 -0
- package/dist/services/memory-init-controller-start.js +103 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +165 -0
- package/dist/services/memory-init-lint-capability.js +203 -0
- package/dist/services/memory-init-native-capacity.js +309 -0
- package/dist/services/memory-init-preflight.js +334 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +213 -0
- package/dist/services/memory-init-request-input.js +82 -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 +135 -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 +91 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +174 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +221 -0
- package/dist/services/memory-lint-config.js +194 -0
- package/dist/services/memory-native-review-copy.js +103 -0
- package/dist/services/memory-normal-flow-entry.js +87 -0
- package/dist/services/memory-ordinary-defaults.js +129 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +274 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +488 -0
- package/dist/services/memory-repair-history.js +496 -0
- package/dist/services/memory-repair-physical-binding.js +194 -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 +162 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +795 -0
- package/dist/services/memory-repair-registration.js +240 -0
- package/dist/services/memory-repair-work-baseline.js +415 -0
- package/dist/services/memory-repair-work-completion.js +117 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +108 -0
- package/dist/services/memory-repair-workspace.js +261 -0
- package/dist/services/memory-repair-writer-copy.js +736 -0
- package/dist/services/memory-result-binding.js +239 -0
- package/dist/services/memory-retained-review-authority.js +138 -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 +162 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +206 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +246 -0
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +117 -0
- package/dist/services/memory-review-work-custody.js +126 -0
- package/dist/services/memory-review-work-input.js +91 -0
- package/dist/services/memory-review-work-results.js +95 -0
- package/dist/services/memory-second-corrected-review-inputs.js +167 -0
- package/dist/services/memory-second-correction-plan.js +77 -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 +65 -0
- package/dist/services/memory-stage-results.js +290 -0
- package/dist/services/memory-tool-probe.js +25 -0
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +156 -0
- package/dist/services/memory-verification-checks.js +317 -0
- package/dist/services/memory-verification-receipt.js +160 -0
- package/dist/services/memory-verify-handler.js +137 -0
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +64 -0
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +163 -35
- package/dist/services/migrations.js +164 -114
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/native-daemon-history.js +50 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +17 -87
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/provider-capacity.js +463 -0
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +588 -0
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +150 -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 +31 -0
- package/dist/services/repair-receipt.js +13 -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 +428 -0
- package/dist/services/run-control.js +1092 -0
- package/dist/services/run-controller-adapter.js +400 -0
- package/dist/services/run-controller-capture.js +186 -0
- package/dist/services/run-controller-process.js +205 -0
- package/dist/services/run-controller-recovery.js +295 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1395 -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 +107 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +456 -0
- package/dist/services/run-start-custody.js +189 -0
- package/dist/services/runs.js +1023 -246
- package/dist/services/runtime-budget.js +446 -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 +605 -0
- package/dist/services/runtime-scope-stop.js +211 -0
- package/dist/services/runtime-scope-worker.js +314 -0
- package/dist/services/runtime-service.js +19 -14
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +36 -91
- package/dist/services/setup.js +427 -0
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +240 -18
- package/dist/services/stage-handlers.js +89 -0
- package/dist/services/stage-lifecycle.js +152 -116
- package/dist/services/stage-pause.js +202 -91
- 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 +393 -0
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code-review.js +235 -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 +355 -21
- package/dist/services/vnext-merge.js +332 -111
- package/dist/services/vnext-plan-review.js +287 -132
- package/dist/services/vnext-plan.js +228 -104
- package/dist/services/vnext-protocolize.js +533 -95
- package/dist/services/vnext-specify.js +123 -81
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +1706 -200
- package/dist/services/workspace-bootstrap.js +53 -15
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +105 -48
- package/dist/shared/entity-references.js +1 -0
- package/dist/shared/errors.js +27 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +930 -40
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +159 -0
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paired-writer-migration.js +424 -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 +96 -0
- package/dist/storage/writer-migration.js +346 -0
- package/dist/storage/writer-restore.js +217 -0
- package/dist/verification-components.json +299 -0
- package/package.json +51 -13
- package/tools/create-canon-release.mjs +33 -0
- package/tools/probe-codex-history.mjs +37 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { AppError } from "../shared/errors.js";
|
|
6
|
+
import { validateCheckEnvironment } from "./check-environment.js";
|
|
7
|
+
import { getResourceDatabase } from "../storage/database.js";
|
|
8
|
+
import { finishManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, processStartedAt, processTreeIsAlive, registerManagedProcess } from "./managed-processes.js";
|
|
9
|
+
import { setFlowRunRecoveryPaused } from "./runs.js";
|
|
10
|
+
import { assertRunMutationAllowed, assertRunRecoveryAllowed, recoveryGuard } from "./run-recovery.js";
|
|
11
|
+
import { requireRun, requireController, controllerReceipt, appendEvent, digest, errorRecord } from "./run-controller-state.js";
|
|
12
|
+
const launchId = (row) => `${row.controller_id}:owner-launch:${digest(row.owner_token)}`;
|
|
13
|
+
const processOperation = (context, row) => `controller:${digest({ database: context.db.path, project: row.project_id, run: row.run_id })}`;
|
|
14
|
+
const birth = () => ({ pid: process.pid, pid_started_at: processStartedAt(process.pid) });
|
|
15
|
+
const dead = (value) => Boolean(value && Number.isSafeInteger(value.pid) && value.pid > 0 && typeof value.pid_started_at === "string" && value.pid_started_at && !processIsAlive(value));
|
|
16
|
+
/** Called inside the RUN intent transaction, before the client can disappear. */
|
|
17
|
+
export function recordControllerLauncher(context, row, retired, identity = birth()) {
|
|
18
|
+
const request = { action: "controller_owner_launch", owner_id: launchId(row) };
|
|
19
|
+
const receipt = { ...identity, registration: "controller_self@1", ...(retired ? { retired_process: { id: retired.id, lease_token: retired.lease_token, pid: retired.pid, pid_started_at: retired.pid_started_at } } : {}) };
|
|
20
|
+
const now = context.now();
|
|
21
|
+
context.db.run("INSERT INTO run_controller_operations (operation_id, controller_id, generation, input_hash, input_json, status, receipt_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'completed', ?, ?, ?)", [launchId(row), row.controller_id, row.generation, digest(request), JSON.stringify(request), JSON.stringify(receipt), now, now]);
|
|
22
|
+
}
|
|
23
|
+
function launchRecord(context, row) {
|
|
24
|
+
const record = context.db.get("SELECT receipt_json FROM run_controller_operations WHERE operation_id = ?", [launchId(row)]);
|
|
25
|
+
if (!record)
|
|
26
|
+
return null;
|
|
27
|
+
const value = JSON.parse(record.receipt_json);
|
|
28
|
+
return value?.registration === "controller_self@1" ? { ...record, value } : null;
|
|
29
|
+
}
|
|
30
|
+
export function assertLaunchAllowed(context, row) {
|
|
31
|
+
const run = requireRun(context, row.project_id, row.run_id);
|
|
32
|
+
if (!["running", "paused", "waiting_for_user"].includes(run.status))
|
|
33
|
+
throw new AppError("invalid_run_state", "A terminal RUN cannot start a physical controller", 1);
|
|
34
|
+
if (JSON.parse(row.state_json).recovery)
|
|
35
|
+
assertControllerRecoveryAllowed(context, row);
|
|
36
|
+
else
|
|
37
|
+
assertRunMutationAllowed(context, row.project_id, row.run_id, row.generation);
|
|
38
|
+
}
|
|
39
|
+
/** Existing owners must retain the recovery fence while finishing terminal capture/settlement. */
|
|
40
|
+
export function assertControllerRecoveryAllowed(context, row) {
|
|
41
|
+
const state = JSON.parse(row.state_json);
|
|
42
|
+
if (!state.recovery) {
|
|
43
|
+
assertRunRecoveryAllowed(context, row.project_id, row.run_id, row.generation);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const guard = recoveryGuard(context, row.project_id, row.run_id);
|
|
47
|
+
if (guard?.generation !== row.generation || guard.recovery_id !== state.recovery.recovery_id || !["sealed", "resuming", "recovered"].includes(guard.status)
|
|
48
|
+
|| !context.db.get("SELECT 1 FROM run_controls WHERE control_id = ? AND recovery_id = ? AND status = 'resuming'", [state.recovery.control_id, state.recovery.recovery_id]))
|
|
49
|
+
throw new AppError("run_control_superseded", "Controller launch no longer owns the accepted recovery", 1);
|
|
50
|
+
}
|
|
51
|
+
/** An admitted physical owner may renew while closing a terminal RUN. This
|
|
52
|
+
* does not admit a new owner or release a recovery/generation fence. */
|
|
53
|
+
export function assertControllerLeaseAllowed(context, row) {
|
|
54
|
+
const current = requireController(context, row.controller_id);
|
|
55
|
+
if (current.owner_token !== row.owner_token || current.generation !== row.generation
|
|
56
|
+
|| current.owner_pid !== process.pid || current.process_id !== row.process_id
|
|
57
|
+
|| current.process_lease_token !== row.process_lease_token || !current.process_id || !current.process_lease_token
|
|
58
|
+
|| !["running", "capturing", "waiting_for_user", "waiting_for_context", "waiting_for_children", "waiting_for_capacity", "waiting_for_merge"].includes(current.status))
|
|
59
|
+
throw new AppError("controller_owner_mismatch", "Lease renewal no longer owns this physical controller", 1);
|
|
60
|
+
const run = requireRun(context, current.project_id, current.run_id);
|
|
61
|
+
if ((["done", "failed", "cancelled"].includes(run.status) || current.status === "capturing") && launchRecord(context, current)?.value.dispatch_ready === true) {
|
|
62
|
+
assertControllerRecoveryAllowed(context, current);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
assertLaunchAllowed(context, current);
|
|
66
|
+
}
|
|
67
|
+
function retire(context, row, identity) {
|
|
68
|
+
const record = managedProcessStatus(context).find(item => item.id === identity.id);
|
|
69
|
+
if (!record || record.kind !== "run-controller" || record.owner_id !== row.controller_id || record.project_id !== row.project_id || record.run_id !== row.run_id
|
|
70
|
+
|| managedProcessRuntimeHome(record, context) !== context.ddFlowHome || record.lease_token !== identity.lease_token || record.pid !== identity.pid || record.pid_started_at !== identity.pid_started_at || !dead(identity) || processTreeIsAlive(record)
|
|
71
|
+
|| !finishManagedProcess(context, { id: record.id, leaseToken: record.lease_token, state: "stopped", reason: "controller_launch_owner_retired" }))
|
|
72
|
+
throw new AppError("controller_owner_unsettled", "Prior controller identity is not proven dead and retired", 1);
|
|
73
|
+
}
|
|
74
|
+
/** Retry only before productive admission, with exact prior birth and journal CAS. */
|
|
75
|
+
function prepareSpawn(context, row) {
|
|
76
|
+
const launch = launchRecord(context, row);
|
|
77
|
+
if (!launch || launch.value.dispatch_ready || !["starting", "running", "recovery_required"].includes(row.status))
|
|
78
|
+
return null;
|
|
79
|
+
const resourceContext = { ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: JSON.parse(row.manifest_json).resource_home } };
|
|
80
|
+
const ours = launch.value.pid === process.pid && launch.value.pid_started_at === processStartedAt(process.pid);
|
|
81
|
+
let token = row.owner_token;
|
|
82
|
+
if (!ours) {
|
|
83
|
+
if (row.owner_pid) {
|
|
84
|
+
if (launch.value.owner?.pid !== row.owner_pid || !dead(launch.value.owner))
|
|
85
|
+
return null;
|
|
86
|
+
const records = managedProcessStatus(resourceContext).filter(item => item.operation_id === processOperation(context, row));
|
|
87
|
+
const owned = records.filter(item => { const metadata = JSON.parse(item.metadata_json); return metadata.owner_pid === row.owner_pid && metadata.owner_pid_started_at === launch.value.owner.pid_started_at; });
|
|
88
|
+
if (owned.length > 1 || row.process_id && owned[0]?.id !== row.process_id)
|
|
89
|
+
return null;
|
|
90
|
+
const record = owned[0];
|
|
91
|
+
if (records.some(item => item.id !== record?.id && (item.state !== "stopped" || !item.pid_started_at || processTreeIsAlive(item))))
|
|
92
|
+
return null;
|
|
93
|
+
if (record) {
|
|
94
|
+
if (record.kind !== "run-controller" || record.owner_id !== row.controller_id || record.project_id !== row.project_id || record.run_id !== row.run_id || managedProcessRuntimeHome(record, context) !== context.ddFlowHome || row.process_lease_token && record.lease_token !== row.process_lease_token || record.pid !== null && record.pid !== row.owner_pid || processTreeIsAlive(record))
|
|
95
|
+
return null;
|
|
96
|
+
if (record.pid === null) {
|
|
97
|
+
const db = getResourceDatabase(JSON.parse(row.manifest_json).resource_home);
|
|
98
|
+
if (db.run("UPDATE managed_processes SET pid = ?, pid_started_at = ? WHERE id = ? AND lease_token = ? AND pid IS NULL", [row.owner_pid, launch.value.owner.pid_started_at, record.id, record.lease_token]).changes !== 1)
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
retire(resourceContext, row, { id: record.id, lease_token: record.lease_token, ...launch.value.owner });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
if (row.process_id || row.process_lease_token || !dead(launch.value))
|
|
106
|
+
return null;
|
|
107
|
+
if (launch.value.child && !dead(launch.value.child))
|
|
108
|
+
return null;
|
|
109
|
+
if (managedProcessStatus(resourceContext).some(item => item.operation_id === processOperation(context, row) && item.id !== launch.value.retired_process?.id && (item.state !== "stopped" || !item.pid_started_at || processTreeIsAlive(item))))
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
token = crypto.randomUUID();
|
|
113
|
+
}
|
|
114
|
+
else if (row.owner_pid || launch.value.spawn_started)
|
|
115
|
+
return null;
|
|
116
|
+
if (launch.value.retired_process)
|
|
117
|
+
retire(resourceContext, row, launch.value.retired_process);
|
|
118
|
+
const launcher = birth();
|
|
119
|
+
return context.db.writeTransaction(() => {
|
|
120
|
+
assertLaunchAllowed(context, row);
|
|
121
|
+
const changed = context.db.run(`UPDATE run_controllers SET owner_token = ?, owner_pid = NULL, process_id = NULL, process_lease_token = NULL, status = 'starting', last_error_json = NULL, updated_at = ?
|
|
122
|
+
WHERE controller_id = ? AND owner_token = ? AND owner_pid IS ? AND process_id IS ? AND status = ? AND state_json = ?
|
|
123
|
+
AND (SELECT receipt_json FROM run_controller_operations WHERE operation_id = ?) = ?`, [token, context.now(), row.controller_id, row.owner_token, row.owner_pid, row.process_id, row.status, row.state_json, launchId(row), launch.receipt_json]);
|
|
124
|
+
if (changed.changes !== 1)
|
|
125
|
+
return null;
|
|
126
|
+
const current = requireController(context, row.controller_id);
|
|
127
|
+
if (!ours) {
|
|
128
|
+
recordControllerLauncher(context, current, undefined, launcher);
|
|
129
|
+
appendEvent(context, row.controller_id, "launch_owner_reconciled", { prior_launch: launchId(row), generation: row.generation, prior_process_id: row.process_id });
|
|
130
|
+
}
|
|
131
|
+
context.db.run("UPDATE run_controller_operations SET receipt_json = json_set(receipt_json, '$.spawn_started', json('true')), updated_at = ? WHERE operation_id = ?", [context.now(), launchId(current)]);
|
|
132
|
+
return current;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** The physical child records its own birth before resource registration. */
|
|
136
|
+
export function claimControllerProcess(context, row, owner = birth()) {
|
|
137
|
+
const launch = launchRecord(context, row);
|
|
138
|
+
if (!launch && (!row.process_id || !row.process_lease_token))
|
|
139
|
+
throw new AppError("controller_owner_mismatch", "Managed registration is not authorized", 1);
|
|
140
|
+
context.db.writeTransaction(() => {
|
|
141
|
+
// Recovery fences are reconciled by serve's retained failure boundary;
|
|
142
|
+
// only terminal admission must reject before claiming a physical owner.
|
|
143
|
+
if (["done", "failed", "cancelled"].includes(requireRun(context, row.project_id, row.run_id).status))
|
|
144
|
+
throw new AppError("invalid_run_state", "A terminal RUN cannot start a physical controller", 1);
|
|
145
|
+
const claimed = context.db.run("UPDATE run_controllers SET owner_pid = ?, status = 'running', updated_at = ? WHERE controller_id = ? AND owner_token = ? AND status = 'starting' AND owner_pid IS NULL", [process.pid, context.now(), row.controller_id, row.owner_token]);
|
|
146
|
+
if (claimed.changes !== 1)
|
|
147
|
+
throw new AppError("controller_already_owned", "Managed owner has already claimed this launch", 1);
|
|
148
|
+
if (launch)
|
|
149
|
+
context.db.run("UPDATE run_controller_operations SET receipt_json = json_set(receipt_json, '$.owner', json(?)), updated_at = ? WHERE operation_id = ?", [JSON.stringify(owner), context.now(), launchId(row)]);
|
|
150
|
+
});
|
|
151
|
+
row.owner_pid = process.pid;
|
|
152
|
+
if (!row.process_id) {
|
|
153
|
+
const manifest = JSON.parse(row.manifest_json);
|
|
154
|
+
const resourceContext = { ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: manifest.resource_home } };
|
|
155
|
+
const log = path.join(row.state_dir, "controller.log");
|
|
156
|
+
const record = registerManagedProcess(resourceContext, { kind: "run-controller", ownerId: row.controller_id, projectId: row.project_id, runId: row.run_id, operationId: processOperation(context, row), uniqueActiveOperation: true, ownerPid: process.pid, stdoutPath: log, stderrPath: log, metadata: { controller_id: row.controller_id, state_dir: row.state_dir, role: "controller", budget: manifest.budget } });
|
|
157
|
+
row.process_id = record.id;
|
|
158
|
+
row.process_lease_token = record.lease_token;
|
|
159
|
+
if (context.db.run("UPDATE run_controllers SET process_id = ?, process_lease_token = ?, updated_at = ? WHERE controller_id = ? AND owner_token = ? AND owner_pid = ? AND status = 'running'", [record.id, record.lease_token, context.now(), row.controller_id, row.owner_token, process.pid]).changes !== 1)
|
|
160
|
+
throw new AppError("controller_owner_mismatch", "Managed owner changed during registration", 1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export function markControllerDispatchReady(context, row) {
|
|
164
|
+
assertLaunchAllowed(context, row);
|
|
165
|
+
context.db.run("UPDATE run_controller_operations SET receipt_json = json_set(receipt_json, '$.dispatch_ready', json('true')), updated_at = ? WHERE operation_id = ?", [context.now(), launchId(row)]);
|
|
166
|
+
}
|
|
167
|
+
export async function spawnController(context, row) {
|
|
168
|
+
const admitted = prepareSpawn(context, row);
|
|
169
|
+
if (!admitted)
|
|
170
|
+
return controllerReceipt(requireController(context, row.controller_id));
|
|
171
|
+
row = admitted;
|
|
172
|
+
const manifest = JSON.parse(row.manifest_json);
|
|
173
|
+
const run = requireRun(context, row.project_id, row.run_id);
|
|
174
|
+
const controllerId = row.controller_id, root = row.state_dir, token = row.owner_token;
|
|
175
|
+
try {
|
|
176
|
+
const entry = (await import("./engines.js")).boundRunEngineEntrypoint(context, { projectRoot: run.project_root, runId: run.id });
|
|
177
|
+
const output = fs.openSync(path.join(root, "controller.log"), "a", 0o600);
|
|
178
|
+
try {
|
|
179
|
+
const child = spawn(process.execPath, [entry, "run", "drive", "serve", "--run", run.id, "--project-root", run.project_root, "--controller-id", controllerId, "--json"], { cwd: run.workspace_root, detached: true, stdio: ["ignore", output, output], env: { ...context.env, ...validateCheckEnvironment(manifest.check_environment), DD_FLOW_HOME: context.ddFlowHome, DD_FLOW_RESOURCE_HOME: manifest.resource_home, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_CONTROLLER_TOKEN: token } });
|
|
180
|
+
await new Promise((resolve, reject) => { child.once("spawn", resolve); child.once("error", reject); });
|
|
181
|
+
context.db.run("UPDATE run_controller_operations SET receipt_json = json_set(receipt_json, '$.child', json(?)), updated_at = ? WHERE operation_id = ?", [JSON.stringify({ pid: child.pid, pid_started_at: child.pid ? processStartedAt(child.pid) : null }), context.now(), launchId(row)]);
|
|
182
|
+
child.unref();
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
fs.closeSync(output);
|
|
186
|
+
}
|
|
187
|
+
return controllerReceipt(requireController(context, controllerId));
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
try {
|
|
191
|
+
context.db.writeTransaction(() => {
|
|
192
|
+
const changed = context.db.run("UPDATE run_controllers SET status = 'recovery_required', last_error_json = ?, updated_at = ? WHERE controller_id = ? AND owner_token = ? AND status = 'starting' AND owner_pid IS NULL", [JSON.stringify(errorRecord(error)), context.now(), controllerId, token]);
|
|
193
|
+
if (changed.changes && (recoveryGuard(context, row.project_id, row.run_id)?.generation ?? 0) === row.generation)
|
|
194
|
+
setFlowRunRecoveryPaused(context, row.project_id, row.run_id, true);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (failure) {
|
|
198
|
+
const secondary = { status_persistence_error: errorRecord(failure) };
|
|
199
|
+
if (error instanceof AppError)
|
|
200
|
+
throw new AppError(error.code, error.message, error.exitCode, { ...error.details, ...secondary });
|
|
201
|
+
throw new AppError("controller_spawn_failed", error instanceof Error ? error.message : String(error), 1, { cause: errorRecord(error), ...secondary });
|
|
202
|
+
}
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { nativeShutdownConfirmed } from "../harness-runtime/lib/native-children.mjs";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
import { AppError } from "../shared/errors.js";
|
|
7
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
8
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
9
|
+
import { loadExternalStageContext } from "./stage-context.js";
|
|
10
|
+
import { verifyRecoverySnapshot } from "./eval-snapshots.js";
|
|
11
|
+
import { recoveryGuard, resumeRunRecovery } from "./run-recovery.js";
|
|
12
|
+
import { prepareRunRecoveryDispatch, retainRecoveryCapacityIntent } from "./run-recovery-runtime.js";
|
|
13
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
14
|
+
import { selectControllerExecution } from "./controller-execution.js";
|
|
15
|
+
import { managedProcessBinding, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, processTreeIsAlive, processStartedAt } from "./managed-processes.js";
|
|
16
|
+
import { requireRun, requireController, controllerReceipt, appendEvent, saveState, digest, errorRecord } from "./run-controller-state.js";
|
|
17
|
+
import { controllerAdapter, controllerEnvironment, sessionArguments } from "./run-controller-adapter.js";
|
|
18
|
+
import { runHarnessAdapter } from "./harness-adapter.js";
|
|
19
|
+
import { continueProviderCapacity } from "./provider-capacity.js";
|
|
20
|
+
import { spawnController, recordControllerLauncher } from "./run-controller-process.js";
|
|
21
|
+
import { readNativeDaemonState, requireManagedDaemonBinding } from "./managed-daemon-binding.js";
|
|
22
|
+
import { managedInvocationContext } from "./lifecycle-invocations.js";
|
|
23
|
+
import { runControlStatus } from "./run-control.js";
|
|
24
|
+
import { runtimeBudgetStatus, waitForRuntimeScopeRecovery } from "./runtime-budget.js";
|
|
25
|
+
import { prepareControllerPublication } from "./run-controller-capture.js";
|
|
26
|
+
/** CLI observation is bounded; the physical controller owns the recovery work. */
|
|
27
|
+
export async function resumeRunController(context, input) {
|
|
28
|
+
if (input.waitMs !== undefined && (!Number.isSafeInteger(input.waitMs) || input.waitMs < 0 || input.waitMs > 60_000))
|
|
29
|
+
throw new AppError("usage", "--wait-ms must be between 0 and 60000", 2);
|
|
30
|
+
const intent = recordControllerResume(context, input);
|
|
31
|
+
const manifest = JSON.parse(intent.controller.manifest_json);
|
|
32
|
+
const resourceContext = { ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: manifest.resource_home } };
|
|
33
|
+
await spawnController(resourceContext, intent.controller);
|
|
34
|
+
const deadline = performance.now() + (input.waitMs ?? 0);
|
|
35
|
+
const status = () => context.db.get("SELECT status FROM run_controller_operations WHERE operation_id = ?", [intent.operationId]).status;
|
|
36
|
+
while (status() === "accepted" && performance.now() < deadline)
|
|
37
|
+
await delay(Math.min(100, Math.max(1, deadline - performance.now())));
|
|
38
|
+
const observation = runControlStatus(context, { ...input, action: "resume" });
|
|
39
|
+
return { ...observation, controller: controllerReceipt(requireController(context, intent.controller.controller_id)).controller, operation_id: intent.operationId, status: observation.resume_operation.status, ...(intent.retiredProcess ? {} : { reused: true }) };
|
|
40
|
+
}
|
|
41
|
+
/** Retain the logical controller and its receipts; replace only its dead owner. */
|
|
42
|
+
export function recordControllerResume(context, input) {
|
|
43
|
+
return prepareControllerResume(context, input)();
|
|
44
|
+
}
|
|
45
|
+
/** Verify immutable recovery evidence before a caller reserves the registry
|
|
46
|
+
* writer. The returned operation rechecks SQL ownership under the RUN CAS. */
|
|
47
|
+
export function prepareControllerResume(context, input) {
|
|
48
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(input.requestId) || !input.controlId.trim())
|
|
49
|
+
throw new AppError("usage", "Resume requires request-id and the exact source control", 2);
|
|
50
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
51
|
+
const run = requireRun(context, project.id, input.runId);
|
|
52
|
+
if (!context.db.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'run_controllers'"))
|
|
53
|
+
throw new AppError("controller_attach_required", "RUN has no retained managed controller", 1);
|
|
54
|
+
const request = { action: "control_resume", request_id: input.requestId, control_id: input.controlId };
|
|
55
|
+
const hash = digest(request);
|
|
56
|
+
const findPrior = () => {
|
|
57
|
+
const prior = context.db.get("SELECT o.controller_id, o.operation_id, o.input_hash FROM run_controller_operations o JOIN run_controllers c ON c.controller_id = o.controller_id WHERE c.project_id = ? AND c.run_id = ? AND json_extract(o.input_json, '$.action') = 'control_resume' AND json_extract(o.input_json, '$.request_id') = ?", [project.id, run.id, input.requestId]);
|
|
58
|
+
if (!prior)
|
|
59
|
+
return null;
|
|
60
|
+
if (prior.input_hash !== hash)
|
|
61
|
+
throw new AppError("runtime_request_conflict", "Resume request id has different inputs", 1);
|
|
62
|
+
return { controller: requireController(context, prior.controller_id), operationId: prior.operation_id };
|
|
63
|
+
};
|
|
64
|
+
const prior = findPrior();
|
|
65
|
+
if (prior)
|
|
66
|
+
return () => findPrior() ?? prior;
|
|
67
|
+
try {
|
|
68
|
+
const row = context.db.get("SELECT * FROM run_controllers WHERE project_id = ? AND run_id = ? AND status NOT IN ('completed','cancelled','superseded')", [project.id, run.id]);
|
|
69
|
+
if (!row)
|
|
70
|
+
throw new AppError("controller_attach_required", "RUN has no retained resumable controller", 1);
|
|
71
|
+
const operationId = `${row.controller_id}:resume:${input.requestId}`;
|
|
72
|
+
if (!["running", "paused", "waiting_for_user"].includes(run.status))
|
|
73
|
+
throw new AppError("invalid_run_state", "Resume cannot resurrect a terminal RUN", 1, { status: run.status });
|
|
74
|
+
const guard = recoveryGuard(context, project.id, run.id);
|
|
75
|
+
const control = context.db.get("SELECT recovery_id, status FROM run_controls WHERE control_id = ? AND project_id = ? AND run_id = ?", [input.controlId, project.id, run.id]);
|
|
76
|
+
if (!guard || guard.recovery_id !== control?.recovery_id || guard.status !== "sealed" || control.status !== "settled" || !guard.capture_path || JSON.parse(guard.settlement_json ?? "{}").settled !== true)
|
|
77
|
+
throw new AppError("run_control_not_resumable", "Resume requires the current settled control and its immutable recovery capture", 1);
|
|
78
|
+
const manifest = JSON.parse(row.manifest_json), state = JSON.parse(row.state_json);
|
|
79
|
+
selectControllerExecution(context, run, manifest);
|
|
80
|
+
if (!path.isAbsolute(manifest.resource_home) || !path.isAbsolute(manifest.flow_executable))
|
|
81
|
+
throw new AppError("controller_manifest_invalid", "Resume requires the original frozen controller manifest", 1);
|
|
82
|
+
if (state.current_session === null || !state.sessions[state.current_session]?.id)
|
|
83
|
+
throw new AppError("recovery_owner_missing", "Resume requires the retained coordinator Session", 1);
|
|
84
|
+
for (const [stage, source] of Object.entries(manifest.contexts))
|
|
85
|
+
loadExternalStageContext({ file: source.file, expectedSha256: source.sha256, stage });
|
|
86
|
+
verifyRecoverySnapshot(guard.capture_path, { projectRoot: project.root, runId: run.id, recoveryId: guard.recovery_id, workspaceRoot: run.workspace_root });
|
|
87
|
+
const resourceContext = { ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: manifest.resource_home } };
|
|
88
|
+
const previousOwner = managedProcessStatus(resourceContext).find(record => record.id === row.process_id);
|
|
89
|
+
if (!previousOwner || previousOwner.lease_token !== row.process_lease_token || previousOwner.kind !== "run-controller" || previousOwner.owner_id !== row.controller_id || previousOwner.project_id !== project.id || previousOwner.run_id !== run.id || managedProcessRuntimeHome(previousOwner, context) !== context.ddFlowHome || !previousOwner.pid || !previousOwner.pid_started_at || processTreeIsAlive(previousOwner))
|
|
90
|
+
throw new AppError("controller_owner_unsettled", "Resume requires proof that the previous physical controller has exited", 1);
|
|
91
|
+
const token = crypto.randomUUID(), now = context.now();
|
|
92
|
+
const launcher = { pid: process.pid, pid_started_at: processStartedAt(process.pid) };
|
|
93
|
+
return () => context.db.writeTransaction(() => {
|
|
94
|
+
const duplicate = context.db.get("SELECT input_hash, status FROM run_controller_operations WHERE operation_id = ?", [operationId]);
|
|
95
|
+
if (duplicate) {
|
|
96
|
+
if (duplicate.input_hash !== hash)
|
|
97
|
+
throw new AppError("runtime_request_conflict", "Resume request id has different inputs", 1);
|
|
98
|
+
return { controller: requireController(context, row.controller_id), operationId };
|
|
99
|
+
}
|
|
100
|
+
const current = recoveryGuard(context, project.id, run.id);
|
|
101
|
+
if (current?.recovery_id !== guard.recovery_id || current.generation !== guard.generation || current.status !== "sealed" || current.capture_path !== guard.capture_path)
|
|
102
|
+
throw new AppError("run_control_superseded", "Control changed before resume could be accepted", 1);
|
|
103
|
+
const currentOwner = managedProcessStatus(resourceContext).find(record => record.id === previousOwner.id);
|
|
104
|
+
if (!currentOwner || digest(managedProcessBinding(currentOwner, manifest.resource_home)) !== digest(managedProcessBinding(previousOwner, manifest.resource_home)))
|
|
105
|
+
throw new AppError("controller_owner_unsettled", "Retired controller binding changed while recovery was prepared", 1);
|
|
106
|
+
if (!["running", "paused", "waiting_for_user"].includes(requireRun(context, project.id, run.id).status))
|
|
107
|
+
throw new AppError("invalid_run_state", "RUN became terminal before resume could be accepted", 1);
|
|
108
|
+
const changed = context.db.run("UPDATE run_controls SET status = 'resuming', updated_at = ? WHERE control_id = ? AND recovery_id = ? AND status = 'settled'", [now, input.controlId, guard.recovery_id]);
|
|
109
|
+
if (changed.changes !== 1)
|
|
110
|
+
throw new AppError("run_control_superseded", "Control changed before resume could be accepted", 1);
|
|
111
|
+
state.recovery = { control_id: input.controlId, recovery_id: guard.recovery_id, operation_id: operationId };
|
|
112
|
+
const claimed = context.db.run("UPDATE run_controllers SET generation = ?, status = 'starting', owner_token = ?, owner_pid = NULL, process_id = NULL, process_lease_token = NULL, state_json = ?, last_error_json = NULL, updated_at = ? WHERE controller_id = ? AND owner_token = ? AND state_json = ? AND manifest_json = ? AND process_id = ?", [guard.generation, token, JSON.stringify(state), now, row.controller_id, row.owner_token, row.state_json, row.manifest_json, row.process_id]);
|
|
113
|
+
if (claimed.changes !== 1)
|
|
114
|
+
throw new AppError("controller_owner_mismatch", "Controller changed before resume could claim its next physical owner", 1);
|
|
115
|
+
context.db.run("INSERT INTO run_controller_operations (operation_id, controller_id, generation, input_hash, input_json, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'accepted', ?, ?)", [operationId, row.controller_id, guard.generation, hash, JSON.stringify(request), now, now]);
|
|
116
|
+
recordControllerLauncher(context, requireController(context, row.controller_id), previousOwner, launcher);
|
|
117
|
+
appendEvent(context, row.controller_id, "resume_requested", { operation_id: operationId, control_id: input.controlId, recovery_id: guard.recovery_id, generation: guard.generation, retired_process_id: previousOwner.id, retained_session_id: state.sessions[state.current_session].id });
|
|
118
|
+
return { controller: requireController(context, row.controller_id), operationId, retiredProcess: previousOwner };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// A concurrent identical client may commit between our first lookup and
|
|
123
|
+
// validation. Its durable request wins even if our preflight saw the fence.
|
|
124
|
+
const concurrent = findPrior();
|
|
125
|
+
if (concurrent)
|
|
126
|
+
return () => concurrent;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export async function resumeControllerSession(context, row, manifest, state, assertPhysicalOwner) {
|
|
131
|
+
const recovery = state.recovery;
|
|
132
|
+
const session = state.current_session === null ? undefined : state.sessions[state.current_session];
|
|
133
|
+
if (!session?.id)
|
|
134
|
+
throw new AppError("recovery_owner_missing", "Recovery lost its retained coordinator Session", 1);
|
|
135
|
+
let executionContext = { ...context, env: { ...context.env, DD_FLOW_BIN: manifest.flow_executable, DD_FLOW_RESOURCE_HOME: manifest.resource_home } };
|
|
136
|
+
const scopeControl = manifest.budget ? runtimeBudgetStatus(executionContext, manifest.budget.scope_id).control : null;
|
|
137
|
+
const assertRecoveryOwner = (physical = true) => {
|
|
138
|
+
if (physical)
|
|
139
|
+
assertPhysicalOwner();
|
|
140
|
+
const guard = recoveryGuard(context, row.project_id, row.run_id);
|
|
141
|
+
if (guard?.generation !== row.generation || guard.recovery_id !== recovery.recovery_id)
|
|
142
|
+
throw new AppError("run_recovery_generation_stale", "A newer control superseded this recovery owner", 1);
|
|
143
|
+
if (!["sealed", "resuming", "recovered"].includes(guard.status) || !context.db.get("SELECT 1 FROM run_controls WHERE control_id = ? AND recovery_id = ? AND status = 'resuming'", [recovery.control_id, recovery.recovery_id]))
|
|
144
|
+
throw new AppError("run_recovery_guarded", "Recovery no longer owns this operator intent", 1);
|
|
145
|
+
};
|
|
146
|
+
assertRecoveryOwner();
|
|
147
|
+
const readDaemon = () => readNativeDaemonState(session.state_dir);
|
|
148
|
+
const { state: retired, process: record } = requireManagedDaemonBinding(executionContext, { stateDir: session.state_dir, projectId: row.project_id, runId: row.run_id, ownerId: session.owner.owner_id });
|
|
149
|
+
const call = (args, operationId) => controllerAdapter(executionContext, row, manifest, session, args, assertRecoveryOwner, operationId);
|
|
150
|
+
if (processTreeIsAlive(record)) {
|
|
151
|
+
const stopped = await call(["daemon", "stop", "--state-dir", session.state_dir, "--json"]);
|
|
152
|
+
if (!retired.daemon_id || stopped.stopped !== true || !nativeShutdownConfirmed(stopped, { daemonId: retired.daemon_id }))
|
|
153
|
+
throw new AppError("recovery_native_shutdown_unproven", "Recovery requires a clean native close before replacing its daemon", 1);
|
|
154
|
+
const deadline = Date.now() + 30_000;
|
|
155
|
+
while (processTreeIsAlive(record) && Date.now() < deadline) {
|
|
156
|
+
await delay(100);
|
|
157
|
+
assertRecoveryOwner();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (processTreeIsAlive(record) || readDaemon().shutdown_state !== "clean")
|
|
161
|
+
throw new AppError("recovery_native_shutdown_unproven", "The previous native daemon is not proven clean and closed", 1);
|
|
162
|
+
assertRecoveryOwner();
|
|
163
|
+
session.stopped = false;
|
|
164
|
+
saveState(context, row, state);
|
|
165
|
+
const started = await call(["daemon", "start", "--session-id", session.id, ...sessionArguments(context, manifest, session)]);
|
|
166
|
+
const daemon = readDaemon(), daemonId = daemon.daemon_id ?? daemon.config?.daemonId;
|
|
167
|
+
if (!daemonId || daemonId === (retired.daemon_id ?? retired.config?.daemonId) || started.daemon_id !== daemonId || started.already_running === true)
|
|
168
|
+
throw new AppError("recovery_daemon_stale", "Recovery requires a fresh daemon, not reuse of the retired physical owner", 1);
|
|
169
|
+
executionContext = managedInvocationContext(executionContext, { harness: runtimeHarness(session.profile), stateDir: session.state_dir, sessionId: session.id,
|
|
170
|
+
projectRoot: manifest.request.project_root, runId: row.run_id, generation: row.generation });
|
|
171
|
+
assertRecoveryOwner();
|
|
172
|
+
const scope = { projectRoot: manifest.request.project_root, runId: row.run_id, recoveryId: recovery.recovery_id };
|
|
173
|
+
resumeRunRecovery(executionContext, { ...scope, daemonId, harness: runtimeHarness(session.profile), nativeSessionId: session.id });
|
|
174
|
+
const dispatch = prepareRunRecoveryDispatch(executionContext, { ...scope, stateDir: session.state_dir, operationId: `${recovery.operation_id}:ack` });
|
|
175
|
+
const inspected = await call(["session", "resume", "--session-id", session.id, ...sessionArguments(context, manifest, session)], dispatch.resume_operation_id);
|
|
176
|
+
if (inspected.settled !== true)
|
|
177
|
+
throw new AppError("recovery_session_unsettled", "Retained native Session is not ready for same-Session recovery", 1);
|
|
178
|
+
assertRecoveryOwner();
|
|
179
|
+
const prepared = { prepared: true, recovery_id: recovery.recovery_id, session_id: session.id, daemon_id: daemonId, process_id: dispatch.process_id, resume_operation_id: dispatch.resume_operation_id };
|
|
180
|
+
const binding = requireManagedDaemonBinding(executionContext, { stateDir: session.state_dir, projectId: row.project_id, runId: row.run_id, ownerId: session.owner.owner_id });
|
|
181
|
+
if (binding.process.id !== dispatch.process_id || binding.state.daemon_id !== daemonId || binding.process.state !== "running" || !processIsAlive(binding.process))
|
|
182
|
+
throw new AppError("recovery_runtime_mismatch", "Prepared recovery lost its physical Session owner", 1);
|
|
183
|
+
const assertPreparedOwner = prepareControllerPublication(executionContext, row, assertPhysicalOwner);
|
|
184
|
+
context.db.writeTransaction(() => {
|
|
185
|
+
assertPreparedOwner();
|
|
186
|
+
assertRecoveryOwner(false);
|
|
187
|
+
const currentNative = managedProcessStatus(executionContext).find(item => item.id === binding.process.id);
|
|
188
|
+
if (!currentNative || currentNative.state !== "running" || currentNative.lease_expires_at <= context.now() || digest(managedProcessBinding(currentNative, manifest.resource_home)) !== digest(managedProcessBinding(binding.process, manifest.resource_home)))
|
|
189
|
+
throw new AppError("recovery_runtime_mismatch", "Prepared recovery lost its current native lease", 1);
|
|
190
|
+
const recorded = context.db.run("UPDATE run_controller_operations SET receipt_json = ?, updated_at = ? WHERE operation_id = ? AND controller_id = ? AND generation = ? AND status = 'accepted'", [JSON.stringify(prepared), context.now(), recovery.operation_id, row.controller_id, row.generation]);
|
|
191
|
+
if (recorded.changes !== 1)
|
|
192
|
+
throw new AppError("run_recovery_generation_stale", "Recovery preparation no longer owns its operation", 1);
|
|
193
|
+
appendEvent(context, row.controller_id, "recovery_prepared", prepared);
|
|
194
|
+
});
|
|
195
|
+
if (scopeControl && manifest.budget) {
|
|
196
|
+
await waitForRuntimeScopeRecovery(executionContext, { scopeId: manifest.budget.scope_id, generation: scopeControl.generation, controllerId: row.controller_id, operationId: recovery.operation_id }, assertRecoveryOwner);
|
|
197
|
+
}
|
|
198
|
+
const file = path.join(row.state_dir, `turn-${String(state.turns + 1).padStart(5, "0")}.md`);
|
|
199
|
+
fs.writeFileSync(file, dispatch.prompt, { flag: "wx", mode: 0o600 });
|
|
200
|
+
state.turns += 1;
|
|
201
|
+
saveState(context, row, state);
|
|
202
|
+
const sendAck = (operationId, prompt) => {
|
|
203
|
+
let promptFile = file;
|
|
204
|
+
if (operationId !== dispatch.operation_id) {
|
|
205
|
+
promptFile = path.join(row.state_dir, `turn-${String(state.turns + 1).padStart(5, "0")}.md`);
|
|
206
|
+
fs.writeFileSync(promptFile, prompt, { flag: "wx", mode: 0o600 });
|
|
207
|
+
state.turns += 1;
|
|
208
|
+
saveState(context, row, state);
|
|
209
|
+
}
|
|
210
|
+
return call(["session", "prompt", "--session-id", session.id, "--prompt-file", promptFile, ...sessionArguments(context, manifest, session)], operationId);
|
|
211
|
+
};
|
|
212
|
+
let receipt;
|
|
213
|
+
try {
|
|
214
|
+
receipt = await sendAck(dispatch.operation_id, dispatch.prompt);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const continuationText = `Continue the same retained Session and recovery generation. First inspect the current RUN recovery state; only if the exact ACK still awaits acceptance, execute the exact standalone acceptance command below once. Never deliver it again after acceptance. Then stop at the original Stage or Work-graph boundary.\n\n${dispatch.prompt}`;
|
|
218
|
+
const inspect = (args, operationId) => runHarnessAdapter({ executable: manifest.adapters[runtimeHarness(session.profile)].executable,
|
|
219
|
+
args, env: controllerEnvironment(executionContext, manifest, session), runtimeOwner: session.owner, operationId, timeoutMs: 30_000 });
|
|
220
|
+
const resumed = await continueProviderCapacity({ error, sessionId: session.id, operationId: dispatch.operation_id, allowSettledDescendants: true, continuationText,
|
|
221
|
+
retainedIntents: () => {
|
|
222
|
+
const binding = context.db.get("SELECT runtime_dispatch_json FROM run_recovery_bindings WHERE recovery_id = ?", [recovery.recovery_id]);
|
|
223
|
+
return binding?.runtime_dispatch_json ? JSON.parse(binding.runtime_dispatch_json).capacity_intents ?? [] : [];
|
|
224
|
+
},
|
|
225
|
+
retainedFailures: () => {
|
|
226
|
+
const binding = context.db.get("SELECT runtime_dispatch_json FROM run_recovery_bindings WHERE recovery_id = ?", [recovery.recovery_id]);
|
|
227
|
+
return binding?.runtime_dispatch_json ? JSON.parse(binding.runtime_dispatch_json).capacity_failures ?? [] : [];
|
|
228
|
+
},
|
|
229
|
+
retainFailure: (_id, refusal) => {
|
|
230
|
+
assertRecoveryOwner();
|
|
231
|
+
context.db.writeTransaction(() => {
|
|
232
|
+
const binding = context.db.get("SELECT runtime_dispatch_json FROM run_recovery_bindings WHERE recovery_id = ? AND daemon_id = ? AND status = 'prepared'", [recovery.recovery_id, daemonId]);
|
|
233
|
+
if (!binding)
|
|
234
|
+
throw new AppError("recovery_capacity_owner_changed", "Recovery binding changed before refusal retention", 1);
|
|
235
|
+
const retained = JSON.parse(binding.runtime_dispatch_json);
|
|
236
|
+
retained.capacity_failures = [...(retained.capacity_failures ?? []), refusal];
|
|
237
|
+
const changed = context.db.run("UPDATE run_recovery_bindings SET runtime_dispatch_json = ? WHERE recovery_id = ? AND daemon_id = ? AND status = 'prepared' AND runtime_dispatch_json = ?", [JSON.stringify(retained), recovery.recovery_id, daemonId, binding.runtime_dispatch_json]);
|
|
238
|
+
if (changed.changes !== 1)
|
|
239
|
+
throw new AppError("recovery_capacity_owner_changed", "Recovery binding changed during refusal retention", 1);
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
inspectOperation: id => inspect(["daemon", "operation", "--state-dir", session.state_dir, "--operation-id", id, "--json"], `${id}:capacity-observe`),
|
|
243
|
+
inspectSession: turnId => inspect(["session", "inspect", "--session-id", session.id, ...(turnId ? ["--turn-id", turnId] : []), ...sessionArguments(context, manifest, session)], `${dispatch.operation_id}:capacity-inspect`),
|
|
244
|
+
assertCurrent: () => {
|
|
245
|
+
assertRecoveryOwner();
|
|
246
|
+
const guard = recoveryGuard(context, row.project_id, row.run_id);
|
|
247
|
+
if (guard?.status === "recovered")
|
|
248
|
+
return;
|
|
249
|
+
const binding = context.db.get("SELECT status, daemon_id FROM run_recovery_bindings WHERE recovery_id = ?", [recovery.recovery_id]);
|
|
250
|
+
const ack = context.db.get("SELECT status FROM run_controller_operations WHERE operation_id = ? AND controller_id = ? AND generation = ?", [recovery.operation_id, row.controller_id, row.generation]);
|
|
251
|
+
if (guard?.status !== "resuming" || binding?.status !== "prepared" || binding.daemon_id !== daemonId || ack?.status !== "accepted")
|
|
252
|
+
throw new AppError("recovery_capacity_owner_changed", "Recovery ACK was accepted or superseded during capacity wait", 1);
|
|
253
|
+
const current = requireManagedDaemonBinding(executionContext, { stateDir: session.state_dir, projectId: row.project_id, runId: row.run_id, ownerId: session.owner.owner_id });
|
|
254
|
+
const metadata = JSON.parse(current.process.metadata_json);
|
|
255
|
+
if (current.process.id !== dispatch.process_id || current.state.daemon_id !== daemonId || current.process.state !== "running"
|
|
256
|
+
|| current.process.lease_expires_at <= context.now() || metadata.run_generation !== row.generation || !processIsAlive(current.process))
|
|
257
|
+
throw new AppError("recovery_capacity_owner_changed", "Replacement native owner changed during capacity wait", 1);
|
|
258
|
+
},
|
|
259
|
+
decision: (boundary) => {
|
|
260
|
+
if (boundary && (boundary.lifecycle_operation !== "recovery_accept" || boundary.recovery_id !== recovery.recovery_id
|
|
261
|
+
|| !context.db.get("SELECT 1 FROM run_recovery_bindings b JOIN hook_events h ON h.event_key = b.hook_event_key WHERE b.recovery_id = ? AND b.status = 'accepted' AND b.daemon_id = ? AND b.provider_session_id = ? AND h.project_id = ? AND h.status = 'claimed' AND h.event_key = ?", [recovery.recovery_id, daemonId, session.id, row.project_id, boundary.native_hook_event_key])))
|
|
262
|
+
return "reconciliation_blocked";
|
|
263
|
+
return recoveryGuard(context, row.project_id, row.run_id)?.status === "recovered" ? "completed" : "continue";
|
|
264
|
+
},
|
|
265
|
+
retainIntent: intent => retainRecoveryCapacityIntent(executionContext, { projectId: row.project_id, runId: row.run_id, recoveryId: recovery.recovery_id,
|
|
266
|
+
controllerId: row.controller_id, generation: row.generation, daemonId, intent, prompt: continuationText }),
|
|
267
|
+
prompt: (operationId, prompt) => sendAck(operationId, prompt) });
|
|
268
|
+
if (!resumed)
|
|
269
|
+
throw error;
|
|
270
|
+
receipt = resumed;
|
|
271
|
+
}
|
|
272
|
+
assertRecoveryOwner();
|
|
273
|
+
if (recoveryGuard(context, row.project_id, row.run_id)?.status !== "recovered")
|
|
274
|
+
throw new AppError("trusted_recovery_acceptance_required", "Recovery Turn ended without its native acknowledgement", 1);
|
|
275
|
+
const assertAcceptedOwner = prepareControllerPublication(executionContext, row, assertPhysicalOwner);
|
|
276
|
+
context.db.writeTransaction(() => {
|
|
277
|
+
assertAcceptedOwner();
|
|
278
|
+
assertRecoveryOwner(false);
|
|
279
|
+
context.db.run("UPDATE run_controls SET status = 'resumed', updated_at = ? WHERE control_id = ? AND status = 'resuming'", [context.now(), recovery.control_id]);
|
|
280
|
+
context.db.run("UPDATE run_controller_operations SET status = 'completed', receipt_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'accepted'", [JSON.stringify({ resumed: true, recovery_id: recovery.recovery_id, session_id: session.id, daemon_id: daemonId }), context.now(), recovery.operation_id]);
|
|
281
|
+
state.last_receipt = receipt;
|
|
282
|
+
state.last_continuation = null;
|
|
283
|
+
delete state.recovery;
|
|
284
|
+
saveState(context, row, state);
|
|
285
|
+
appendEvent(context, row.controller_id, "recovery_resumed", { control_id: recovery.control_id, recovery_id: recovery.recovery_id, session_id: session.id, daemon_id: daemonId, generation: row.generation });
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
export function failControllerRecovery(context, row, state, error) {
|
|
289
|
+
if (state.recovery) {
|
|
290
|
+
// Native ACK may already have opened this generation before its Turn lost
|
|
291
|
+
// observation. Retain the accepted evidence but close further dispatch.
|
|
292
|
+
context.db.run("UPDATE run_recovery_guards SET status = 'resuming', updated_at = ? WHERE recovery_id = ? AND status = 'recovered' AND generation = (SELECT MAX(generation) FROM run_recovery_guards WHERE project_id = ? AND run_id = ?) AND EXISTS (SELECT 1 FROM run_controls WHERE control_id = ? AND recovery_id = run_recovery_guards.recovery_id AND status = 'resuming')", [context.now(), state.recovery.recovery_id, row.project_id, row.run_id, state.recovery.control_id]);
|
|
293
|
+
context.db.run("UPDATE run_controller_operations SET status = 'failed', error_json = ?, updated_at = ? WHERE operation_id = ? AND status = 'accepted'", [JSON.stringify(errorRecord(error)), context.now(), state.recovery.operation_id]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { AppError, errorRecord as structuredErrorRecord } from "../shared/errors.js";
|
|
3
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
4
|
+
import { managedProcessRuntimeHome, managedProcessStatus, processTreeIsAlive } from "./managed-processes.js";
|
|
5
|
+
export function requireRun(context, projectId, runId) {
|
|
6
|
+
const run = context.db.get("SELECT id, project_id, flow_kind, project_root, workspace_root, run_root, status, index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
|
|
7
|
+
if (!run?.run_root)
|
|
8
|
+
throw new AppError("not_found", "Managed RUN has no materialized root", 1, { run_id: runId });
|
|
9
|
+
return run;
|
|
10
|
+
}
|
|
11
|
+
export function requireController(context, id) {
|
|
12
|
+
const row = context.db.get("SELECT * FROM run_controllers WHERE controller_id = ?", [id]);
|
|
13
|
+
if (!row)
|
|
14
|
+
throw new AppError("not_found", "Managed controller was not found", 1, { controller_id: id });
|
|
15
|
+
return row;
|
|
16
|
+
}
|
|
17
|
+
export function controllerReceipt(row) {
|
|
18
|
+
const state = JSON.parse(row.state_json);
|
|
19
|
+
const manifest = JSON.parse(row.manifest_json);
|
|
20
|
+
return { ok: true, schema_id: "dd-flow/run-controller-receipt@1", controller: { controller_id: row.controller_id, request_id: row.request_id, project_id: row.project_id, run_id: row.run_id, generation: row.generation, runtime_budget: manifest.budget ?? null, status: row.status, process_id: row.process_id, owner_pid: row.owner_pid, state_dir: row.state_dir, stage: state.stage, sessions: state.sessions.map(session => ({ session_id: session.id, harness: runtimeHarness(session.profile), profile_id: session.profile.id, state_dir: session.state_dir, journal: session.journal, stopped: session.stopped === true })), error: row.last_error_json ? JSON.parse(row.last_error_json) : null } };
|
|
21
|
+
}
|
|
22
|
+
/** Read-only causal diagnostics, not a settlement or capture authorization. */
|
|
23
|
+
export function terminalControllerFinalization(context, project, runId, runStatus) {
|
|
24
|
+
if (!["done", "failed", "cancelled"].includes(runStatus) || !context.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'run_controllers'"))
|
|
25
|
+
return null;
|
|
26
|
+
const row = context.db.get("SELECT * FROM run_controllers WHERE project_id = ? AND run_id = ? ORDER BY created_at DESC, controller_id DESC LIMIT 1", [project.id, runId]);
|
|
27
|
+
if (!row?.last_error_json || !["recovery_required", "capture_failed"].includes(row.status))
|
|
28
|
+
return null;
|
|
29
|
+
const error = JSON.parse(row.last_error_json);
|
|
30
|
+
if (error.details?.phase !== "terminal_finalization" || error.details.project_root !== project.root || error.details.run_id !== runId || error.details.controller_id !== row.controller_id)
|
|
31
|
+
return null;
|
|
32
|
+
let owner = "unknown";
|
|
33
|
+
try {
|
|
34
|
+
const manifest = JSON.parse(row.manifest_json);
|
|
35
|
+
const records = managedProcessStatus({ ...context, env: { ...context.env, DD_FLOW_RESOURCE_HOME: manifest.resource_home } });
|
|
36
|
+
const record = records.find(item => item.id === row.process_id);
|
|
37
|
+
if (record && record.kind === "run-controller" && record.owner_id === row.controller_id && record.project_id === project.id && record.run_id === runId
|
|
38
|
+
&& record.lease_token === row.process_lease_token && record.pid === row.owner_pid && record.pid && record.pid_started_at
|
|
39
|
+
&& managedProcessRuntimeHome(record, context) === context.ddFlowHome) {
|
|
40
|
+
if (processTreeIsAlive(record))
|
|
41
|
+
owner = "live";
|
|
42
|
+
else if (["stopped", "failed"].includes(record.state) && record.finished_at)
|
|
43
|
+
owner = "absent";
|
|
44
|
+
}
|
|
45
|
+
// Any other still-owned controller record makes absence unproven.
|
|
46
|
+
if (owner === "absent" && records.some(item => item.id !== row.process_id && item.kind === "run-controller" && item.project_id === project.id && item.run_id === runId
|
|
47
|
+
&& managedProcessRuntimeHome(item, context) === context.ddFlowHome && (!item.pid_started_at || !["stopped", "failed"].includes(item.state) || processTreeIsAlive(item))))
|
|
48
|
+
owner = "unknown";
|
|
49
|
+
}
|
|
50
|
+
catch { /* Missing registry or unreadable identity is uncertainty, never death. */ }
|
|
51
|
+
const control = context.db.get("SELECT 1 FROM sqlite_schema WHERE name = 'run_controls'")
|
|
52
|
+
&& context.db.get("SELECT 1 FROM run_controls WHERE project_id = ? AND run_id = ? AND status IN ('requested','settled','resuming')", [project.id, runId]);
|
|
53
|
+
const capture = context.db.get("SELECT 1 FROM run_controller_operations WHERE controller_id = ? AND generation = ? AND status = 'completed' AND json_extract(input_json, '$.action') = 'boundary_capture' AND json_type(input_json, '$.stage_entry') = 'null'", [row.controller_id, row.generation]);
|
|
54
|
+
return { schema_id: "dd-flow/terminal-finalization@1", issuer: "dd-flow", phase: "terminal_finalization", project_root: project.root, run_id: runId,
|
|
55
|
+
controller_id: row.controller_id, generation: row.generation, controller_status: row.status, run_status: runStatus,
|
|
56
|
+
status: !control && owner === "absent" ? "blocked" : "pending", owner, capture: capture ? "recorded" : "missing", error };
|
|
57
|
+
}
|
|
58
|
+
export function appendEvent(context, id, type, data) { context.db.run("INSERT INTO run_controller_events (controller_id, event_type, data_json, created_at) VALUES (?, ?, ?, ?)", [id, type, JSON.stringify(data), context.now()]); }
|
|
59
|
+
export function saveState(context, row, state) {
|
|
60
|
+
const changed = context.db.run("UPDATE run_controllers SET state_json = ?, updated_at = ? WHERE controller_id = ? AND owner_token = ? AND generation = ? AND status NOT IN ('completed','cancelled','superseded')", [JSON.stringify(state), context.now(), row.controller_id, row.owner_token, row.generation]);
|
|
61
|
+
if (changed.changes !== 1)
|
|
62
|
+
throw new AppError("controller_owner_mismatch", "Controller state owner or generation changed", 1);
|
|
63
|
+
}
|
|
64
|
+
export function retainControllerCapacityIntent(context, row, state, intent, sessionId, kind, assertCurrent) {
|
|
65
|
+
const root = intent.root_operation_id;
|
|
66
|
+
if (!root || intent.session_id !== sessionId || !intent.refusal)
|
|
67
|
+
throw new AppError("capacity_continuation_conflict", "Capacity intent has no exact controller root/Session/refusal", 1);
|
|
68
|
+
const pending = { root_operation_id: root, predecessor: intent.predecessor, successor: intent.operation_id, session_id: sessionId, generation: row.generation, kind };
|
|
69
|
+
context.db.writeTransaction(() => {
|
|
70
|
+
assertCurrent();
|
|
71
|
+
const owner = context.db.get("SELECT status FROM run_controllers WHERE controller_id = ? AND owner_token = ? AND generation = ?", [row.controller_id, row.owner_token, row.generation]);
|
|
72
|
+
if (!owner || !["running", "waiting_for_user", "waiting_for_children", "waiting_for_capacity", "waiting_for_merge"].includes(owner.status))
|
|
73
|
+
throw new AppError("controller_owner_mismatch", "Controller no longer owns continuation preparation", 1);
|
|
74
|
+
const changed = context.db.run("UPDATE run_controller_operations SET status = 'failed', receipt_json = ?, updated_at = ? WHERE operation_id = ? AND controller_id = ? AND generation = ? AND status = 'outcome_unknown'", [JSON.stringify({ capacity_root: root, capacity_failure: intent.refusal, capacity_intent: intent }), context.now(), intent.predecessor, row.controller_id, row.generation]);
|
|
75
|
+
if (changed.changes !== 1)
|
|
76
|
+
throw new AppError("capacity_continuation_blocked", "Failed native operation changed before continuation", 1);
|
|
77
|
+
saveState(context, row, { ...state, pending_capacity: pending });
|
|
78
|
+
appendEvent(context, row.controller_id, "capacity_continuation", intent);
|
|
79
|
+
});
|
|
80
|
+
state.pending_capacity = pending;
|
|
81
|
+
}
|
|
82
|
+
export function retainControllerContinuation(state, fingerprint, error) {
|
|
83
|
+
if (state.last_continuation === fingerprint)
|
|
84
|
+
throw new AppError(error.code, error.message, 1, error.details);
|
|
85
|
+
state.last_continuation = fingerprint;
|
|
86
|
+
}
|
|
87
|
+
export function digest(value) { return digestBytes(JSON.stringify(value)); }
|
|
88
|
+
export function digestBytes(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
|
|
89
|
+
export function errorRecord(error) {
|
|
90
|
+
const record = structuredErrorRecord(error);
|
|
91
|
+
let typed = false;
|
|
92
|
+
try {
|
|
93
|
+
typed = typeof error?.code === "string";
|
|
94
|
+
}
|
|
95
|
+
catch { /* Preserve a serializable controller failure for unreadable diagnostics. */ }
|
|
96
|
+
return { ...record, code: typed ? record.code : "controller_failed" };
|
|
97
|
+
}
|